diff options
-rw-r--r-- | TODO | 16 | ||||
-rw-r--r-- | editlessonlistscreen.py | 300 | ||||
-rw-r--r-- | editlessonscreen.py | 406 | ||||
-rwxr-xr-x | lessonbuilder.py | 7 | ||||
-rw-r--r-- | lessonscreen.py | 68 | ||||
-rw-r--r-- | mainscreen.py | 25 | ||||
-rw-r--r-- | port/chooser.py | 66 | ||||
-rwxr-xr-x | typingturtle.py | 59 |
8 files changed, 817 insertions, 130 deletions
@@ -1,13 +1,26 @@ Typing Turtle +Lesson Editor +- Fix Delete Lesson +- Wire up step editing keys +- Aesthetic improvements to lesson editor +- Better help text +- Saving and loading from Journal +- Import and export from JSON data in Journal +- Graphical lesson builder ++ Automatic step and wordlist generation. + First Release ++ Export PDF of lesson history and progress. + Status message on the main screen. "You unlocked a new lesson!" for example. Eventually have the turtle 'say' it. + Graphical WPM and accuracy meters. + Sound effects. + Welcome to the activity sound. + Speed up / slow down sounds when WPM crosses threshold: Slow, Medium, Fast. + Medal award sounds for each medal type: None, Bronze, Silver, Gold. Applause sound. - + Incorrect key pressed tick sound. + + Incorrect key pressed tick sound. ++ Lesson option to specify that incorrect keys are ignored. ++ Fix the keyboard. Future Release + Highlight regions of keyboard, color by finger used. @@ -16,7 +29,6 @@ Future Release + History screen: List of lessons completed with statistics. + Progress screen: Line graphs of Accuracy, WPM over time. + Some sort of award when all the lessons are complete, followed by general skill work. -+ Lesson editor standalone PyGTK app. Balloon Game diff --git a/editlessonlistscreen.py b/editlessonlistscreen.py new file mode 100644 index 0000000..4388f24 --- /dev/null +++ b/editlessonlistscreen.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python +# vi: sw=4 et +# Copyright 2008 by Kate Scheppke and Wade Brainerd. +# This file is part of Typing Turtle. +# +# Typing Turtle is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Typing Turtle is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Typing Turtle. If not, see <http://www.gnu.org/licenses/>. + +# Import standard Python modules. +import logging, os, math, time, copy, locale, datetime, random, re +from gettext import gettext as _ +from port import json +from port import chooser + +# Import PyGTK. +import gobject, pygtk, gtk, pango + +# Import Sugar UI modules. +import sugar.activity.activity +import sugar.graphics.style +import sugar.mime +import sugar.datastore.datastore + +# Import activity modules. +import editlessonscreen + +class EditLessonListScreen(gtk.VBox): + def __init__(self, activity, lessons): + gtk.VBox.__init__(self) + + self.activity = activity + self.lessons = lessons + + # Add the header. + title = gtk.Label() + title.set_markup("<span size='20000'><b>" + _("Edit Lessons") + "</b></span>") + title.set_alignment(1.0, 0.0) + + stoplabel = gtk.Label(_('Go Back')) + stopbtn = gtk.Button() + stopbtn.add(stoplabel) + stopbtn.connect('clicked', self.stop_clicked_cb) + + titlebox = gtk.HBox() + titlebox.pack_start(stopbtn, False, False, 10) + titlebox.pack_end(title, False, False, 10) + + # Add the lesson list. + self.treeview = gtk.TreeView() + self.treeview.set_rules_hint(True) + self.treeview.set_enable_search(False) + + self.treeview.connect('cursor-changed', self.lesson_selected_cb) + self.treeview.connect('row-activated', self.lesson_activated_cb) + + # Note that the only thing we store in our liststore is the lesson id. + # All the actual data is in the lessons list. + self.liststore = gtk.ListStore(gobject.TYPE_INT) + self.treeview.set_model(self.liststore) + + # Construct the columns. + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Name'), renderer) + col.set_cell_data_func(renderer, self.name_render_cb) + self.treeview.append_column(col) + + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Description'), renderer) + col.set_cell_data_func(renderer, self.description_render_cb) + col.set_expand(True) + self.treeview.append_column(col) + + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Type'), renderer) + col.set_cell_data_func(renderer, self.type_render_cb) + col.set_expand(False) + self.treeview.append_column(col) + + scroll = gtk.ScrolledWindow() + scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scroll.add(self.treeview) + + importlabel = gtk.Label(_('Import Lessons from Journal')) + self.importbtn = gtk.Button() + self.importbtn.add(importlabel) + self.importbtn.connect('clicked', self.import_clicked_cb) + + exportlabel = gtk.Label(_('Export Lessons to Journal')) + self.exportbtn = gtk.Button() + self.exportbtn.add(exportlabel) + self.exportbtn.connect('clicked', self.export_clicked_cb) + + self.addbtn = gtk.Button() + self.addbtn.add(sugar.graphics.icon.Icon(icon_name='list-add')) + self.addbtn.connect('clicked', self.add_lesson_clicked_cb) + self.delbtn = gtk.Button() + self.delbtn.add(sugar.graphics.icon.Icon(icon_name='list-remove')) + self.delbtn.connect('clicked', self.del_lesson_clicked_cb) + self.delbtn.set_sensitive(False) + self.moveupbtn = gtk.Button() + self.moveupbtn.add(sugar.graphics.icon.Icon(icon_name='go-up')) + self.moveupbtn.connect('clicked', self.move_lesson_up_clicked_cb) + self.moveupbtn.set_sensitive(False) + self.movedownbtn = gtk.Button() + self.movedownbtn.add(sugar.graphics.icon.Icon(icon_name='go-down')) + self.movedownbtn.connect('clicked', self.move_lesson_down_clicked_cb) + self.movedownbtn.set_sensitive(False) + + btnbox = gtk.HBox() + btnbox.pack_start(self.importbtn, False, False, 10) + btnbox.pack_start(self.exportbtn, False, False) + btnbox.pack_end(self.addbtn, False, False) + btnbox.pack_end(self.delbtn, False, False) + btnbox.pack_end(self.moveupbtn, False, False) + btnbox.pack_end(self.movedownbtn, False, False) + + self.pack_start(titlebox, False, False, 10) + self.pack_start(gtk.HSeparator(), False, False, 0) + self.pack_start(scroll, True, True, 10) + self.pack_start(btnbox, False, False, 10) + + self.build() + + self.show_all() + + def build(self): + # Fill the lesson list. + self.liststore.clear() + for t in range(0, len(self.lessons)): + self.liststore.append((0,)) + + def name_render_cb(self, column, cell_renderer, model, iter): + id = model.get_path(iter)[0] + t = self.lessons[id] + cell_renderer.set_property('text', t['name']) + + def description_render_cb(self, column, cell_renderer, model, iter): + id = model.get_path(iter)[0] + t = self.lessons[id] + cell_renderer.set_property('text', t['description']) + + def type_render_cb(self, column, cell_renderer, model, iter): + id = model.get_path(iter)[0] + t = self.lessons[id] + if t['type'] == 'normal': + cell_renderer.set_property('text', _('Text')) + if t['type'] == 'balloon': + cell_renderer.set_property('text', _('Balloon Game')) + + def stop_clicked_cb(self, btn): + # Assign lesson order. + num = 0 + for l in self.lessons: + l['order'] = num + num = num + 1 + + # Refresh the main screen given the new lesson data. + if self.activity.mainscreen.lesson_index >= len(self.lessons): + self.activity.mainscreen.lesson_index = len(self.lessons) - 1 + self.activity.mainscreen.show_lesson(self.activity.mainscreen.lesson_index) + + self.activity.pop_screen() + + def add_lesson_clicked_cb(self, btn): + lesson = {} + lesson['name'] = '' + lesson['description'] = '' + lesson['type'] = 'normal' + lesson['steps'] = [ { 'instructions':'', 'text':'' } ] + lesson['medals'] = [ + { 'name': 'bronze', 'wpm': 15, 'accuracy': 70, 'score': 3000 }, + { 'name': 'silver', 'wpm': 20, 'accuracy': 80, 'score': 4500 }, + { 'name': 'gold', 'wpm': 25, 'accuracy': 90, 'score': 6000 }, + ] + self.lessons.append(lesson) + self.activity.push_screen(editlessonscreen.EditLessonScreen(self.activity, lesson)) + self.liststore.append() + + def del_lesson_clicked_cb(self, btn): + if len(self.lessons) > 1: + path = self.treeview.get_cursor()[0] + if path: + id = path[0] + self.lessons.pop(id) + del self.liststore[id] + self.treeview.get_selection().select_path(id) + self.treeview.grab_focus() + self.update_sensitivity() + + def move_lesson_up_clicked_cb(self, btn): + path = self.treeview.get_cursor()[0] + if path: + id = path[0] + if id > 0: + lesson = self.lessons.pop(id) + self.lessons.insert(id - 1, lesson) + self.liststore.swap(self.liststore.get_iter(id), self.liststore.get_iter(id - 1)) + self.treeview.get_selection().select_path(id - 1) + self.treeview.grab_focus() + self.update_sensitivity() + + def move_lesson_down_clicked_cb(self, btn): + path = self.treeview.get_cursor()[0] + if path: + id = path[0] + if id < len(self.lessons) - 1: + lesson = self.lessons.pop(id) + self.lessons.insert(id + 1, lesson) + self.liststore.swap(self.liststore.get_iter(id), self.liststore.get_iter(id + 1)) + self.treeview.get_selection().select_path(id + 1) + self.treeview.grab_focus() + self.update_sensitivity() + + def lesson_selected_cb(self, treeview): + self.update_sensitivity() + + def lesson_activated_cb(self, treeview, path, column): + id = path[0] + lesson = self.lessons[id] + self.activity.push_screen(editlessonscreen.EditLessonScreen(self.activity, lesson)) + + def enter(self): + self.update_sensitivity() + + def update_sensitivity(self): + path = self.treeview.get_cursor()[0] + + if path: + self.delbtn.set_sensitive(True) + + if path[0] > 0: + self.moveupbtn.set_sensitive(True) + else: + self.moveupbtn.set_sensitive(False) + + if path[0] < len(self.lessons) - 1: + self.movedownbtn.set_sensitive(True) + else: + self.movedownbtn.set_sensitive(False) + + else: + self.delbtn.set_sensitive(False) + self.moveupbtn.set_sensitive(False) + self.movedownbtn.set_sensitive(False) + + + def import_clicked_cb(self, btn): + jobject = chooser.pick(None, None, self, 'text/x-typing-turtle-lessons') + + if jobject and jobject.file_path: + fd = open(jobject.file_path, 'r') + + try: + data = json.loads(fd.read()) + + # Replace lessons without destroying the object. + while len(self.lessons): + self.lessons.pop() + for l in data['lessons']: + self.lessons.append(l) + self.build() + + finally: + fd.close() + + def export_clicked_cb(self, btn): + # Create the new journal entry + fileObject = sugar.datastore.datastore.create() + + meta = self.activity.metadata + fileObject.metadata['title'] = meta['title'] + _(' (Exported Lessons)') + fileObject.metadata['title_set_by_user'] = meta['title_set_by_user'] + fileObject.metadata['mime_type'] = 'text/x-typing-turtle-lessons' + fileObject.metadata['icon-color'] = meta['icon-color'] + fileObject.file_path = os.path.join(self.activity.get_activity_root(), 'instance', '%i' % time.time()) + + fd = open(fileObject.file_path, 'w') + + try: + data = { 'lessons': self.lessons } + fd.write(json.dumps(data)) + + finally: + fd.close() + + sugar.datastore.datastore.write(fileObject, transfer_ownership=True) + fileObject.destroy() + del fileObject + +
\ No newline at end of file diff --git a/editlessonscreen.py b/editlessonscreen.py index 9a2ce59..bf9677a 100644 --- a/editlessonscreen.py +++ b/editlessonscreen.py @@ -26,93 +26,383 @@ import gobject, pygtk, gtk, pango # Import Sugar UI modules. import sugar.activity.activity import sugar.graphics.style +import sugar.graphics.icon class EditLessonScreen(gtk.VBox): - def add_step_cb(w): - pass + def __init__(self, activity, lesson): + gtk.VBox.__init__(self) + self.set_border_width(10) + + self.activity = activity + self.lesson = lesson + + self.in_build = False + + # Add the header. + title = gtk.Label() + title.set_markup("<span size='20000'><b>" + _("Edit a Lesson") + "</b></span>") + title.set_alignment(1.0, 0.0) + + stoplabel = gtk.Label(_('Go Back')) + stopbtn = gtk.Button() + stopbtn.add(stoplabel) + stopbtn.connect('clicked', self.stop_clicked_cb) + + titlebox = gtk.HBox() + titlebox.pack_start(stopbtn, False, False, 10) + titlebox.pack_end(title, False, False, 10) + + self.vp = gtk.Viewport() + + self.scroll = gtk.ScrolledWindow() + self.scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.scroll.add(self.vp) + + self.pack_start(titlebox, False, False, 10) + self.pack_start(gtk.HSeparator(), False, False, 0) + self.pack_start(self.scroll, True, True, 0) + + self.build() + + self.show_all() - def make_step(self): + def build_step(self, step, idx): stepbox = gtk.VBox() - stepbox.pack_start(gtk.HSeparator()) + steplabel = gtk.Label() + steplabel.set_markup("<span size='x-large' weight='bold'>" + (_('Step #%d') % (idx+1)) + "</span>") + steplabel.set_alignment(0.0, 0.5) + steplabel.set_padding(10, 0) + #generatelabel = gtk.Label() + #generatelabel.set_markup(_('Generate')) + #generatebtn = gtk.Button() + #generatebtn.add(generatelabel) delstepbtn = gtk.Button() - delstepbtn.add(gtk.Label('Delete')) - + delstepbtn.add(sugar.graphics.icon.Icon(icon_name='list-remove')) + delstepbtn.connect('clicked', self.del_step_clicked_cb, idx) addstepbtn = gtk.Button() - addstepbtn.add(gtk.Label('Add')) - + addstepbtn.add(sugar.graphics.icon.Icon(icon_name='list-add')) + addstepbtn.connect('clicked', self.add_step_clicked_cb, idx) moveupbtn = gtk.Button() - moveupbtn.add(gtk.Label('Move Up')) - + moveupbtn.add(sugar.graphics.icon.Icon(icon_name='go-up')) + moveupbtn.connect('clicked', self.move_step_up_clicked_cb, idx) movedownbtn = gtk.Button() - movedownbtn.add(gtk.Label('Move Down')) + movedownbtn.add(sugar.graphics.icon.Icon(icon_name='go-down')) + movedownbtn.connect('clicked', self.move_step_down_clicked_cb, idx) + + if idx == 0: + moveupbtn.set_sensitive(False) + if idx == len(self.lesson['steps']) - 1: + movedownbtn.set_sensitive(False) btnbox = gtk.HBox() - btnbox.pack_start(gtk.Label('Step')) + btnbox.pack_start(steplabel, False, False) btnbox.pack_end(addstepbtn, False, False) btnbox.pack_end(delstepbtn, False, False) btnbox.pack_end(moveupbtn, False, False) btnbox.pack_end(movedownbtn, False, False) + #btnbox.pack_end(generatebtn, False, False) + + instlabel = gtk.Label() + instlabel.set_markup("<span size='large' weight='bold'>" + _('Instructions') + "</span>") + instlabel.set_alignment(0.0, 0.5) + instlabel.set_padding(20, 0) + + self.labelsizegroup.add_widget(instlabel) + + stepbox.insttext = gtk.TextView(gtk.TextBuffer()) + stepbox.insttext.props.wrap_mode = gtk.WRAP_WORD + instscroll = gtk.ScrolledWindow() + instscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + instscroll.add(stepbox.insttext) + instscroll.set_size_request(-1, 75) + stepbox.insttext.get_buffer().set_text(step['instructions']) - stepbox.pack_start(btnbox) - - stepbox.pack_start(gtk.Label(_('Instructions'))) - inst_text = gtk.TextView(gtk.TextBuffer()) - inst_text.props.wrap_mode = gtk.WRAP_WORD - inst_scroll = gtk.ScrolledWindow() - inst_scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - inst_scroll.add(inst_text) - inst_scroll.set_size_request(-1, 100) - stepbox.pack_start(inst_scroll) - - stepbox.pack_start(gtk.Label(_('Text'))) - text_text = gtk.TextView(gtk.TextBuffer()) - text_text.props.wrap_mode = gtk.WRAP_WORD - text_scroll = gtk.ScrolledWindow() - text_scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - text_scroll.add(text_text) - text_scroll.set_size_request(-1, 200) - stepbox.pack_start(text_scroll) + instbox = gtk.HBox() + instbox.pack_start(instlabel, False, False) + instbox.pack_start(instscroll, True, True) + textlabel = gtk.Label() + textlabel.set_markup("<span size='large' weight='bold'>" + _('Text') + "</span>") + textlabel.set_alignment(0.0, 0.5) + textlabel.set_padding(20, 0) + + self.labelsizegroup.add_widget(textlabel) + + stepbox.texttext = gtk.TextView(gtk.TextBuffer()) + stepbox.texttext.props.wrap_mode = gtk.WRAP_WORD + textscroll = gtk.ScrolledWindow() + textscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + textscroll.add(stepbox.texttext) + textscroll.set_size_request(-1, 100) + stepbox.texttext.get_buffer().set_text(step['text']) + + textbox = gtk.HBox() + textbox.pack_start(textlabel, False, False) + textbox.pack_start(textscroll, True, True) + + sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + sizegroup.add_widget(instlabel) + sizegroup.add_widget(textlabel) + + stepbox.pack_start(btnbox, False, False, 10) + stepbox.pack_start(instbox, False, False, 10) + stepbox.pack_start(textbox, False, False, 10) + return stepbox - def make_lesson(self, lesson): - table = gtk.Table(8, 2) + def build_medal(self, medal, name): + box = gtk.HBox() - table.attach(gtk.Label(_('Name')), 0, 1, 0, 1) - nameent = gtk.Entry() - table.attach(nameent, 1, 2, 0, 1) + label = gtk.Label() + label.set_markup("<span size='large' weight='bold'>" + name + "</span>") + label.set_alignment(0.0, 0.5) + label.set_padding(20, 0) - table.attach(gtk.Label(_('Description')), 0, 1, 1, 2) - descent = gtk.Entry() - table.attach(descent, 1, 2, 1, 2) + self.labelsizegroup.add_widget(label) + + box.pack_start(label, False, False) - table.attach(gtk.Label(_('Type')), 0, 1, 2, 3) - type_drop = gtk.combo_box_new_text() - type_drop.append_text(_('Text')) - type_drop.append_text(_('Game')) - table.attach(type_drop, 1, 2, 2, 3) + if self.lesson['type'] == 'normal': + acclabel = gtk.Label(_('Accuracy')) + wpmlabel = gtk.Label(_('WPM')) + + box.accent = gtk.Entry() + box.wpment = gtk.Entry() - vbox = gtk.VBox() - vbox.pack_start(table, False, False) + box.accent.set_text(str(medal['accuracy'])) + box.wpment.set_text(str(medal['wpm'])) + + box.pack_start(acclabel, False, False, 10) + box.pack_start(box.accent, False, False) + box.pack_start(wpmlabel, False, False, 10) + box.pack_start(box.wpment, False, False) + + elif self.lesson['type'] == 'balloon': + scorelabel = gtk.Label(_('Score')) + + box.scoreent = gtk.Entry() + box.scoreent.set_text(str(medal['score'])) + + box.pack_start(scorelabel, False, False, 10) + box.pack_start(box.scoreent, False, False) + + return box + + def build(self): + self.in_build = True + + self.vbox = gtk.VBox() + self.vbox.set_border_width(20) + + self.labelsizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) - vbox.pack_start(self.make_step_widgets()) - vbox.pack_start(self.make_step_widgets()) + # Lesson details widgets. + detailslabel = gtk.Label() + detailslabel.set_markup("<span size='x-large'><b>" + _('Lesson Details') + "</b></span>") + detailslabel.set_alignment(0.0, 0.5) + detailslabel.set_padding(10, 0) - vp = gtk.Viewport() - vp.add(vbox) + namelabel = gtk.Label() + namelabel.set_markup("<span size='large' weight='bold'>" + _('Name') + "</span>") + namelabel.set_alignment(0.0, 0.5) + namelabel.set_padding(20, 0) - scroll = gtk.ScrolledWindow() - scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - scroll.add(vp) + self.nameent = gtk.Entry() + self.nameent.set_text(self.lesson['name']) + namebox = gtk.HBox() + namebox.pack_start(namelabel, False, False) + namebox.pack_start(self.nameent, True, True) + + typelabel = gtk.Label() + typelabel.set_markup("<span size='large' weight='bold'>" + _('Type') + "</span>") + typelabel.set_alignment(0.0, 0.5) + typelabel.set_padding(20, 0) + self.textradio = gtk.RadioButton(None, _('Normal Lesson')) + self.textradio.connect('toggled', self.type_toggled_cb) + + self.balloonradio = gtk.RadioButton(self.textradio, _('Balloon Game')) + self.balloonradio.connect('toggled', self.type_toggled_cb) + + self.textradio.set_active(self.lesson['type'] == 'normal') + self.balloonradio.set_active(self.lesson['type'] == 'balloon') - def __init__(self, activity): - gtk.VBox.__init__(self) + typebox = gtk.HBox() + typebox.pack_start(typelabel, False, False) + typebox.pack_start(self.textradio, False, False) + typebox.pack_start(self.balloonradio, False, False) + + desclabel = gtk.Label() + desclabel.set_markup("<span size='large' weight='bold'>" + _('Description') + "</span>") + desclabel.set_alignment(0.0, 0.5) + desclabel.set_padding(20, 0) - # Build the lesson editor. - self.pack_start(scroll) + self.desctext = gtk.TextView(gtk.TextBuffer()) + self.desctext.props.wrap_mode = gtk.WRAP_WORD + descscroll = gtk.ScrolledWindow() + descscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + descscroll.add(self.desctext) + descscroll.set_size_request(-1, 75) + self.desctext.get_buffer().set_text(self.lesson['description']) - self.show_all() + descbox = gtk.HBox() + descbox.pack_start(desclabel, False, False) + descbox.pack_start(descscroll, True, True) + + self.labelsizegroup.add_widget(namelabel) + self.labelsizegroup.add_widget(typelabel) + self.labelsizegroup.add_widget(desclabel) + + self.vbox.pack_start(detailslabel, False, False, 10) + self.vbox.pack_start(namebox, False, False, 10) + self.vbox.pack_start(typebox, False, False, 10) + self.vbox.pack_start(descbox, False, False, 10) + self.vbox.pack_start(gtk.HSeparator(), False, False, 0) + + # Steps or words widgets. + if self.lesson['type'] == 'normal': + if not self.lesson.has_key('steps') or len(self.lesson['steps']) == 0: + step = { 'instructions': '', 'text': '' } + self.lesson['steps'] = [ step ] + + self.vbox.pack_start(gtk.HSeparator(), False, False, 0) + + self.stepboxes = [] + + for step in self.lesson['steps']: + stepbox = self.build_step(step, len(self.stepboxes)) + self.stepboxes.append(stepbox) + + self.vbox.pack_start(stepbox, False, False, 0) + + if self.lesson['type'] == 'balloon': + if not self.lesson.has_key('words') or len(self.lesson['words']) == 0: + self.lesson['words'] = [] + + self.vbox.pack_start(gtk.HSeparator(), False, False, 0) + + textlabel = gtk.Label() + textlabel.set_markup("<span size='large' weight='bold'>" + _('Words') + "</span>") + textlabel.set_alignment(0.0, 0.5) + textlabel.set_padding(20, 0) + + self.labelsizegroup.add_widget(textlabel) + + self.wordstext = gtk.TextView(gtk.TextBuffer()) + self.wordstext.props.wrap_mode = gtk.WRAP_WORD + textscroll = gtk.ScrolledWindow() + textscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + textscroll.add(self.wordstext) + textscroll.set_size_request(-1, 200) + self.wordstext.get_buffer().set_text(' '.join(self.lesson['words'])) + + textbox = gtk.HBox() + textbox.pack_start(textlabel, False, False) + textbox.pack_start(textscroll, True, True) + + self.vbox.pack_start(textbox, False, False, 10) + + # Medal requirements widgets. + medalslabel = gtk.Label() + medalslabel.set_markup("<span size='x-large'><b>" + _('Medal Requirements') + "</b></span>") + medalslabel.set_alignment(0.0, 0.5) + medalslabel.set_padding(10, 0) + + self.vbox.pack_start(gtk.HSeparator(), False, False, 0) + self.vbox.pack_start(medalslabel, False, False, 10) + + self.medalboxes = [] + self.medalboxes.append(self.build_medal(self.lesson['medals'][0], _('Bronze'))) + self.medalboxes.append(self.build_medal(self.lesson['medals'][1], _('Silver'))) + self.medalboxes.append(self.build_medal(self.lesson['medals'][2], _('Gold'))) + + self.vbox.pack_start(self.medalboxes[0], False, False, 10) + self.vbox.pack_start(self.medalboxes[1], False, False, 10) + self.vbox.pack_start(self.medalboxes[2], False, False, 10) + + self.vbox.show_all() + + self.in_build = False + + # Remove any existing controls. + if self.vp.get_child(): + self.vp.remove(self.vp.get_child()) + + self.vp.add(self.vbox) + + def stop_clicked_cb(self, btn): + self.save() + + self.activity.pop_screen() + + def save(self): + self.lesson['name'] = self.nameent.get_text() + + buf = self.desctext.get_buffer() + self.lesson['description'] = buf.get_text(buf.get_start_iter(), buf.get_end_iter()) + + if self.textradio.get_active(): + self.lesson['type'] = 'normal' + + steps = [] + for sb in self.stepboxes: + step = {} + + buf = sb.insttext.get_buffer() + step['instructions'] = buf.get_text(buf.get_start_iter(), buf.get_end_iter()) + + buf = sb.texttext.get_buffer() + step['text'] = buf.get_text(buf.get_start_iter(), buf.get_end_iter()) + + steps.append(step) + + self.lesson['steps'] = steps + + for i in range(0, 3): + self.lesson['medals'][i]['accuracy'] = int(self.medalboxes[i].accent.get_text()) + self.lesson['medals'][i]['wpm'] = int(self.medalboxes[i].wpment.get_text()) + + if self.balloonradio.get_active(): + self.lesson['type'] = 'balloon' + + buf = self.wordstext.get_buffer() + text = buf.get_text(buf.get_start_iter(), buf.get_end_iter()) + self.lesson['words'] = text.split(' ') + + for i in range(0, 3): + self.lesson['medals'][i]['score'] = int(self.medalboxes[i].scoreent.get_text()) + + def add_step_clicked_cb(self, btn, index): + step = { 'instructions': '', 'text': '' } + self.lesson['steps'].insert(index, step) + self.build() + + def del_step_clicked_cb(self, btn, index): + self.lesson['steps'].pop(index) + self.build() + + def move_step_up_clicked_cb(self, btn, index): + if index > 0: + step = self.lesson['steps'].pop(index) + self.lesson['steps'].insert(index-1, step) + self.build() + + def move_step_down_clicked_cb(self, btn, index): + if index < len(self.lesson['steps']) - 1: + step = self.lesson['steps'].pop(index) + self.lesson['steps'].insert(index+1, step) + self.build() + + def type_toggled_cb(self, btn): + # Prevent infinite recursion + if self.in_build: + return + + if self.textradio.get_active(): + self.lesson['type'] = 'normal' + if self.balloonradio.get_active(): + self.lesson['type'] = 'balloon' + self.build() diff --git a/lessonbuilder.py b/lessonbuilder.py index d1e9b4d..48dde12 100755 --- a/lessonbuilder.py +++ b/lessonbuilder.py @@ -377,9 +377,6 @@ def build_key_steps( get_congrats() + _('Time to type real words.'), 'text', make_random_words(good_words, new_keys, all_keys, count))) - text = '$report' - steps.append(make_step(text, 'key', ' ')) - return steps def build_intro_steps(): @@ -408,10 +405,6 @@ def build_intro_steps(): text += _('Press the ENTER key again with your right little finger.') steps.append(make_step(text, 'key', '\n')) - text = '' - text = '$report' - steps.append(make_step(text, 'key', '\n')) - return steps def build_text_step(path): diff --git a/lessonscreen.py b/lessonscreen.py index 884ad3c..1f3a188 100644 --- a/lessonscreen.py +++ b/lessonscreen.py @@ -35,14 +35,6 @@ PARAGRAPH_CODE = u'\xb6' # Maximium width of a text line in text lesson mode. LINE_WIDTH = 70 -# Requirements for earning medals. -# Words per minute goals came from http://en.wikipedia.org/wiki/Words_per_minute. -DEFAULT_MEDALS = [ - { 'name': 'bronze', 'wpm': 15, 'accuracy': 75 }, - { 'name': 'silver', 'wpm': 20, 'accuracy': 85 }, - { 'name': 'gold', 'wpm': 25, 'accuracy': 95 } -] - class LessonScreen(gtk.VBox): def __init__(self, lesson, keyboard_images, activity): gtk.VBox.__init__(self) @@ -204,8 +196,6 @@ class LessonScreen(gtk.VBox): self.start_time = None self.total_time = 0 - self.step = None - self.next_step_idx = 0 self.advance_step() @@ -233,42 +223,49 @@ class LessonScreen(gtk.VBox): self.stop_timer() # Clear step related variables. - self.step = None - - self.text = None self.line = None self.line_marks = None + + # TODO: Play 'step finished' sound here. - # Leave this screen if the lesson is finished. - if self.next_step_idx >= len(self.lesson['steps']): - self.end_lesson() - return + # Set up step if a valid index. + if self.next_step_idx < len(self.lesson['steps']): + step = self.lesson['steps'][self.next_step_idx] + self.next_step_idx = self.next_step_idx + 1 + + self.text = unicode(step['text']) + self.instructions = unicode(step['instructions']) - # Mark the lesson as finished if this is the last step. - if self.next_step_idx == len(self.lesson['steps']) - 1: + # Show report after the last step. + elif self.next_step_idx == len(self.lesson['steps']) and not self.lesson_finished: self.lesson_finished = True + + self.instructions = self.get_lesson_report() + self.text = '\n' - # TODO - Play 'step finished' sound here. + # Leave this screen when the lesson is finished. + else: + self.end_lesson() + return - self.step = self.lesson['steps'][self.next_step_idx] - self.next_step_idx = self.next_step_idx + 1 + # Fix empty steps. + if len(self.text) == 0: + self.text = '\n' # Single character steps are handled differently from multi-character steps. - self.mode = self.step['mode'] + if len(self.text) == 1: + self.mode = 'key' + else: + self.mode = 'text' # Clear the buffer *before* key steps. self.lessonbuffer.set_text('') - # Output the instructions. - self.instructions = self.step['instructions'] - if self.instructions.find('$report') != -1: - self.instructions = self.get_lesson_report() - + # Output the instructions. self.lessonbuffer.insert_with_tags_by_name( self.lessonbuffer.get_end_iter(), '\n\n' + self.instructions + '\n', 'instructions') - self.text = unicode(self.step['text']) - + # Key steps have just one key to press, and show a picture of the key to be pressed beneath the text. if self.mode == 'key': self.lines = [self.text.replace('\n', PARAGRAPH_CODE)] self.line_marks = {} @@ -305,12 +302,13 @@ class LessonScreen(gtk.VBox): # Enable hands for key mode. self.keyboard.set_draw_hands(True) - - else: + + # Text steps require the user to copy out the text that is displayed. + elif self.mode == 'text': # Split text into lines. self.lines = self.text.splitlines(True) - # Substitute paragraph codes. + # Substitute paragraph codes for newlines. self.lines = [l.replace('\n', PARAGRAPH_CODE) for l in self.lines] # Split by line length in addition to by paragraphs. @@ -360,7 +358,7 @@ class LessonScreen(gtk.VBox): if not event.string: return True - print 'key_cb: ' + event.string + #print 'key_cb: ' + event.string # Ignore either press or release events, depending on mode. if self.mode == 'key' and event.type == gtk.gdk.KEY_PRESS: @@ -503,7 +501,7 @@ class LessonScreen(gtk.VBox): # Show the medal screen, if one should be given. got_medal = None - medals = self.lesson.get('medals', DEFAULT_MEDALS) + medals = self.lesson['medals'] for medal in medals: if self.wpm >= medal['wpm'] and self.accuracy >= medal['accuracy']: got_medal = medal['name'] diff --git a/mainscreen.py b/mainscreen.py index 988a8f9..0f2700b 100644 --- a/mainscreen.py +++ b/mainscreen.py @@ -27,7 +27,7 @@ import sugar.activity.activity from sugar.graphics import * # Import activity modules. -import lessonscreen, medalscreen, editlessonscreen +import lessonscreen, medalscreen import balloongame import titlescene import keyboard @@ -97,15 +97,6 @@ class MainScreen(gtk.VBox): self.keyboard_images = keyboard.KeyboardImages(width, height) self.keyboard_images.load_images() - # not yet ready - # - #editbtn = gtk.Button() - #editbtn.add(gtk.Label(_('Edit Lessons'))) - #editbtn.connect('clicked', self.edit_lessons_cb) - # - #toolbar = gtk.HBox() - #toolbar.pack_end(editbtn) - navbox = gtk.HBox() navbox.set_spacing(10) navbox.pack_start(self.prevlessonbtn, True) @@ -117,12 +108,17 @@ class MainScreen(gtk.VBox): lessonbox.pack_start(navbox, False) lessonbox.pack_start(self.lessonbox) - #self.pack_start(toolbar) self.pack_start(self.titlescene, False, True, 10) self.pack_start(lessonbox, True) self.show_next_lesson() + def enter(self): + self.activity.editorbtn.set_sensitive(True) + + def leave(self): + self.activity.editorbtn.set_sensitive(False) + def load_lessons(self, path): # Find all .lesson files in ./lessons/en_US/ for example. self.lessons = [] @@ -221,11 +217,11 @@ class MainScreen(gtk.VBox): # Hilite the button in the direction of the first unmedaled lesson. next_index = self.get_next_lesson() - if next_index > self.lesson_index: + if next_index > self.lesson_index and index < len(self.lessons)-1: self.nextlessonbtn.modify_bg(gtk.STATE_NORMAL, self.get_colormap().alloc_color('#ff8080')) else: self.nextlessonbtn.modify_bg(gtk.STATE_NORMAL, self.get_colormap().alloc_color('#40a040')) - if next_index < self.lesson_index: + if next_index < self.lesson_index and index > 0: self.prevlessonbtn.modify_bg(gtk.STATE_NORMAL, self.get_colormap().alloc_color('#ff8080')) else: self.prevlessonbtn.modify_bg(gtk.STATE_NORMAL, self.get_colormap().alloc_color('#40a040')) @@ -254,6 +250,3 @@ class MainScreen(gtk.VBox): if self.activity.data['medals'].has_key(self.visible_lesson['name']): medal = self.activity.data['medals'][self.visible_lesson['name']] self.activity.push_screen(medalscreen.MedalScreen(medal, self.activity)) - - def edit_lessons_cb(self, widget): - self.activity.push_screen(editlessonscreen.EditLessonScreen(self.activity)) diff --git a/port/chooser.py b/port/chooser.py new file mode 100644 index 0000000..e2df259 --- /dev/null +++ b/port/chooser.py @@ -0,0 +1,66 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Object chooser method""" + +import gtk +import logging + +from sugar import mime +from sugar.graphics.objectchooser import ObjectChooser + +TEXT = hasattr(mime, 'GENERIC_TYPE_TEXT') and mime.GENERIC_TYPE_TEXT or None +IMAGE = hasattr(mime, 'GENERIC_TYPE_IMAGE') and mime.GENERIC_TYPE_IMAGE or None +AUDIO = hasattr(mime, 'GENERIC_TYPE_AUDIO') and mime.GENERIC_TYPE_AUDIO or None +VIDEO = hasattr(mime, 'GENERIC_TYPE_VIDEO') and mime.GENERIC_TYPE_VIDEO or None +LINK = hasattr(mime, 'GENERIC_TYPE_LINK') and mime.GENERIC_TYPE_LINK or None + +def pick(cb=None, default=None, parent=None, what=None): + """ + Opens object chooser. + + Method returns: + + * cb(jobject), if object was choosen and cb is not None + * jobject, if object was choosen and cb is None + * default, otherwise + + NOTE: 'what' makes sense only for sugar >= 0.84 + """ + what = what and {'what_filter': what} or {} + chooser = ObjectChooser(parent=parent, **what) + + jobject = None + out = None + + try: + if chooser.run() == gtk.RESPONSE_ACCEPT: + jobject = chooser.get_selected_object() + logging.debug('ObjectChooser: %r' % jobject) + + if jobject and jobject.file_path: + if cb: + out = cb(jobject) + else: + out = jobject + finally: + if jobject and id(jobject) != id(out): + jobject.destroy() + chooser.destroy() + del chooser + + if out: + return out + else: + return default diff --git a/typingturtle.py b/typingturtle.py index a570eb0..dfe4ccb 100755 --- a/typingturtle.py +++ b/typingturtle.py @@ -34,6 +34,7 @@ import gobject, pygtk, gtk, pango # Import Sugar UI modules. import sugar.activity.activity from sugar.graphics import * +from sugar.graphics import toolbutton from sugar.presence import presenceservice @@ -47,7 +48,7 @@ bundle_path = sugar.activity.activity.get_bundle_path() os.chdir(bundle_path) # Import activity modules. -import mainscreen, lessonscreen, medalscreen +import mainscreen, editlessonlistscreen # This is the main Typing Turtle activity class. # @@ -85,55 +86,89 @@ class TypingTurtle(sugar.activity.activity.Activity): activity_toolbar = self.tbox.get_activity_toolbar() activity_toolbar.share.props.visible = False + self.editorbtn = sugar.graphics.toolbutton.ToolButton('format-justify-left') + self.editorbtn.set_tooltip(_("Edit Lessons")) + self.editorbtn.connect('clicked', self.editor_clicked_cb) + + share_idx = activity_toolbar.get_item_index(activity_toolbar.share) + activity_toolbar.insert(self.editorbtn, share_idx) + self.editorbtn.show_all() + def build_toolbox(self): self.tbox = sugar.activity.activity.ActivityToolbox(self) self.tbox.show_all() self.set_toolbox(self.tbox) + def editor_clicked_cb(self, btn): + self.push_screen(editlessonlistscreen.EditLessonListScreen(self, self.mainscreen.lessons)) + def push_screen(self, screen): if len(self.screens): - self.screenbox.remove(self.screens[-1]) + oldscreen = self.screens[-1] + + try: + oldscreen.leave() + except: + pass + + self.screenbox.remove(oldscreen) self.screenbox.pack_start(screen, True, True) self.screens.append(screen) + + try: + screen.enter() + except: + pass def pop_screen(self): - self.screenbox.remove(self.screens[-1]) - self.screens.pop() + oldscreen = self.screens.pop() + + try: + oldscreen.leave() + except: + pass + + self.screenbox.remove(oldscreen) + if len(self.screens): - self.screenbox.pack_start(self.screens[-1]) - + screen = self.screens[-1] + + try: + screen.enter() + except: + pass + + self.screenbox.pack_start(screen) + def add_history(self, entry): self.data['history'].append(entry) def read_file(self, file_path): - print 'read_file' - if self.metadata['mime_type'] != 'text/plain': return fd = open(file_path, 'r') try: text = fd.read() - print "read %s" % text self.data = json.loads(text) + if self.data.has_key('lessons'): + self.mainscreen.lessons = self.data['lessons'] finally: fd.close() self.mainscreen.show_next_lesson() def write_file(self, file_path): - print 'write_file' - if not self.metadata['mime_type']: self.metadata['mime_type'] = 'text/plain' fd = open(file_path, 'w') try: + self.data['lessons'] = self.mainscreen.lessons text = json.dumps(self.data) fd.write(text) - print "wrote %s" % text finally: fd.close() |