Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO16
-rw-r--r--editlessonlistscreen.py300
-rw-r--r--editlessonscreen.py406
-rwxr-xr-xlessonbuilder.py7
-rw-r--r--lessonscreen.py68
-rw-r--r--mainscreen.py25
-rw-r--r--port/chooser.py66
-rwxr-xr-xtypingturtle.py59
8 files changed, 817 insertions, 130 deletions
diff --git a/TODO b/TODO
index 0f3f092..5359ddd 100644
--- a/TODO
+++ b/TODO
@@ -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()