From a0a41dcfa18821a53bb51b029d7dfea9bfd35af1 Mon Sep 17 00:00:00 2001 From: Wade Brainerd Date: Mon, 17 Nov 2008 16:30:10 +0000 Subject: Renamed activity from TypingTurtleApp to TypingTurtle, bundling fixes, improvements to lesson interface. --- diff --git a/.project b/.project deleted file mode 100644 index 216fd33..0000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - typingTurtle - - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - diff --git a/.pydevproject b/.pydevproject deleted file mode 100644 index 9711a53..0000000 --- a/.pydevproject +++ /dev/null @@ -1,9 +0,0 @@ - - - - - -/typingTurtle/src - -python 2.4 - diff --git a/MANIFEST b/MANIFEST index 971b69d..e01a7cd 100644 --- a/MANIFEST +++ b/MANIFEST @@ -12,4 +12,5 @@ images/bronze-medal.jpg images/silver-medal.jpg lessons/en_US/homerow_intro.lesson lessons/en_US/dummy.lesson -po/TypingTurtleApp.pot +lessons/en_US/long.lesson +po/TypingTurtle.pot diff --git a/TODO b/TODO index c9482eb..aa8e894 100644 --- a/TODO +++ b/TODO @@ -1,13 +1,21 @@ Typing Turtle First Release -+ Write to Loser, Wes about developing artwork. +- Write to Loser, Wes about developing artwork. ++ WPM meter updated in 1sec timer. +- Draw incorrect characters in red. +- Support backspace, backspace to previous lines. ++ Handle ends of line in a sane manner. ++ Implement two step types: key learning and text copying. +- Split text into lines for long lessons. +- Try out an insensitive gtk.Entry instead of the gtk.Label. + Write to authors of prior typing activity to inquire about layered artwork. Need to make a .xo file to demo. + Better flow at the end of a level. Report the result on the Lesson screen: Need more work, Medal received, etc. + Status message on the main screen. "You unlocked a new lesson!" for example. Eventually have the turtle 'say' it. + Scroll lessons list to the first non-medaled lesson? + Implement a long text copying lesson and fix bugs in the scrolling and typing. -+ Ability of lessons to list bronze medals in other lessons as prerequisites. ++ Some sort of lesson sorting criteria. ++ Ability of lessons to list medals in other lessons as prerequisites. + Graphical WPM and accuracy meters. - Working medals assignment: "You got a medal!" popup, display next to lesson. - Nice looking keyboard. diff --git a/activity/activity.info b/activity/activity.info index 41a3f3d..aadcd0a 100644 --- a/activity/activity.info +++ b/activity/activity.info @@ -1,7 +1,7 @@ [Activity] -name = TypingTurtleApp +name = Typing Turtle activity_version = 1 host_version = 1 -service_name = org.laptop.community.TypingTurtleApp +service_name = org.laptop.community.TypingTurtle icon = Colors-activity exec = sugar-activity typingturtle.TypingTurtle diff --git a/keyboard.py b/keyboard.py index 5683a30..5dc92c3 100644 --- a/keyboard.py +++ b/keyboard.py @@ -423,9 +423,6 @@ class Keyboard(gtk.EventBox): return True def _key_press_cb(self, widget, event): - # Useful line for determining what scan code matches what key. - #print "HW: %x" % event.hardware_keycode - if self.key_map.has_key(event.hardware_keycode): key = self.key_map[event.hardware_keycode] key.pressed = True diff --git a/po/TypingTurtleApp.pot b/po/TypingTurtle.pot index 07456fd..dc33920 100644 --- a/po/TypingTurtleApp.pot +++ b/po/TypingTurtle.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-11-15 04:36+0000\n" +"POT-Creation-Date: 2008-11-17 04:51+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -16,17 +16,17 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: activity/activity.info:2 -msgid "TypingTurtleApp" +#: activity/activity.info:2 typingturtle.py:429 typingturtle.py:530 +msgid "Typing Turtle" msgstr "" -#: typingturtle.py:52 +#: typingturtle.py:56 msgid "" "Certificate of\n" "Achievement" msgstr "" -#: typingturtle.py:56 +#: typingturtle.py:60 #, python-format msgid "" "This certifies that on %(date)s,\n" @@ -34,34 +34,30 @@ msgid "" "in Typing Turtle lesson %(lesson)s." msgstr "" -#: typingturtle.py:60 +#: typingturtle.py:64 #, python-format msgid "Words Per Minute: %(wpm)d" msgstr "" -#: typingturtle.py:63 typingturtle.py:185 +#: typingturtle.py:67 typingturtle.py:201 #, python-format msgid "Accuracy: %(accuracy)d%%" msgstr "" -#: typingturtle.py:70 typingturtle.py:106 +#: typingturtle.py:74 typingturtle.py:110 msgid "Go Back" msgstr "" -#: typingturtle.py:186 +#: typingturtle.py:202 #, python-format msgid "WPM: %(wpm)d" msgstr "" -#: typingturtle.py:324 typingturtle.py:425 -msgid "Typing Turtle" -msgstr "" - -#: typingturtle.py:327 +#: typingturtle.py:432 msgid "" "Welcome to Typing Turtle! To begin, select a lesson from the list below." msgstr "" -#: typingturtle.py:335 +#: typingturtle.py:440 msgid "Available Lessons" msgstr "" diff --git a/typingturtle.py b/typingturtle.py index b3bec12..0f2156b 100755 --- a/typingturtle.py +++ b/typingturtle.py @@ -37,8 +37,12 @@ log = logging.getLogger('Typing Turtle') log.setLevel(logging.DEBUG) logging.basicConfig() +# Import onscreen keyboard. import keyboard +# Paragraph symbol unicode character. +PARAGRAPH_CODE = u'\xb6' + class MedalScreen(gtk.EventBox): def __init__(self, medal, activity): gtk.EventBox.__init__(self) @@ -123,9 +127,33 @@ class LessonScreen(gtk.VBox): hbox.pack_start(self.accuracylabel, True, False, 10) hbox.pack_end(title, False, False, 10) - self.lessontext = gtk.Label() - #self.lessontext.set_alignment(0, 0) - self.lessontext.set_line_wrap(True) + # Set up font styles. + self.tagtable = gtk.TextTagTable() + instructions_tag = gtk.TextTag('instructions') + instructions_tag.props.size = 10000 + + self.tagtable.add(instructions_tag) + text_tag = gtk.TextTag('text') + text_tag.props.family = 'Monospace' + self.tagtable.add(text_tag) + + correct_copy_tag = gtk.TextTag('correct-copy') + correct_copy_tag.props.family = 'Monospace' + correct_copy_tag.props.foreground = '#0000ff' + self.tagtable.add(correct_copy_tag) + + incorrect_copy_tag = gtk.TextTag('incorrect-copy') + incorrect_copy_tag.props.family = 'Monospace' + incorrect_copy_tag.props.foreground = '#ff0000' + self.tagtable.add(incorrect_copy_tag) + + # Set up the scrolling lesson text. + self.lessonbuffer = gtk.TextBuffer(self.tagtable) + self.lessontext = gtk.TextView(self.lessonbuffer) + self.lessontext.set_editable(False) + #self.lessontext.set_cursor_visible(False) + self.lessontext.set_left_margin(20) + self.lessontext.set_right_margin(20) self.lessonscroll = gtk.ScrolledWindow() self.lessonscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) @@ -150,34 +178,22 @@ class LessonScreen(gtk.VBox): def begin_lesson(self): self.step = None - self.markup = '' + self.line = None + self.line_marks = None self.total_keys = 0 self.correct_keys = 0 self.incorrect_keys = 0 - self.count_words() - self.next_step_idx = 0 self.advance_step() self.start_time = None - def count_words(self): - self.total_words = 0 - for s in self.lesson['steps']: - in_word = False - for c in s['text']: - if not in_word and not c.isspace(): - self.total_words += 1 - in_word = True - elif c.isspace(): - in_word = False - def update_stats(self): self.total_time = time.time() - self.start_time if self.total_time >= 1.0: - self.wpm = 60.0 * self.total_words / self.total_time + self.wpm = 60 * (len(self.step['text']) / 5) / self.total_time else: self.wpm = 1.0 self.accuracy = 100.0 * self.correct_keys / self.total_keys @@ -185,9 +201,9 @@ class LessonScreen(gtk.VBox): self.accuracylabel.set_markup(_('Accuracy: %(accuracy)d%%') % { 'accuracy' : int(self.accuracy) } ) self.wpmlabel.set_markup(_('WPM: %(wpm)d') % { 'wpm': int(self.wpm) } ) - def add_text(self, text): - self.markup += text - self.lessontext.set_markup('' + self.markup + '_' + '') + def wrap_line(self, line): + #return [line[:10], line[10:]] + return [line] def advance_step(self): if self.next_step_idx < len(self.lesson['steps']): @@ -196,20 +212,149 @@ class LessonScreen(gtk.VBox): self.step = self.lesson['steps'][self.next_step_idx] self.next_step_idx = self.next_step_idx + 1 - self.markup = '' + # Clear the text buffer and output the instructions. + self.lessonbuffer.set_text('') + self.lessonbuffer.insert_with_tags_by_name( + self.lessonbuffer.get_end_iter(), self.step['instructions'] + '\n\n', 'instructions') + + # Replace newlines with paragraph marks. + text = unicode(self.step['text']) + + # Split text into lines. + self.lines = text.split('\n') + + # Append paragraph codes. + self.lines = [l + PARAGRAPH_CODE for l in self.lines] + + # TODO: Split by line length in addition to by paragraphs. + for i in range(0, len(self.lines)): + line = self.lines[i] + if len(line) > 30: + self.lines[i:i+1] = self.wrap_line(line) + + # Fill text buffer with text lines, each followed by room for the user to type. + self.line_marks = {} + line_idx = 0 + for l in self.lines: + self.lessonbuffer.insert_with_tags_by_name( + self.lessonbuffer.get_end_iter(), l.encode('utf-8') + '\n', 'text') - self.add_text(self.step['instructions'] + '\n\n') - self.add_text('' + self.step['text'] + '\n') + self.line_marks[line_idx] = self.lessonbuffer.create_mark(None, self.lessonbuffer.get_end_iter(), True) + self.lessonbuffer.insert_at_cursor('\n') - self.char_idx = 0 - self.hilite_next_key() + line_idx += 1 + + self.line_idx = 0 + self.begin_line() else: self.finish_lesson() - def finish_lesson(self): - self.step = None + def begin_line(self): + self.line = self.lines[self.line_idx] + self.line_mark = self.line_marks[self.line_idx] + + self.char_idx = 0 + self.hilite_next_key() + + def key_press_cb(self, widget, event): + # Ignore hotkeys. + if event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.MOD1_MASK): + return + + # Extract information about the key pressed. + key = gtk.gdk.keyval_to_unicode(event.keyval) + if key != 0: key = chr(key) + key_name = gtk.gdk.keyval_name(event.keyval) + + # Convert Return keys to paragraph symbols. + if key_name == 'Return': + key = PARAGRAPH_CODE + + print "key_press_cb: key=%s key_name=%s event.keyval=%d" % (key, key_name, event.keyval) + + # Handle backspace by deleting text and optionally moving up lines. + if key_name == 'BackSpace': + # Move to previous line if at the end of the current one. + if self.char_idx == 0 and self.line_idx > 0: + self.line_idx -= 1 + self.begin_line() + + self.char_idx = len(self.line) + + # Then delete the current character. + if self.char_idx > 0: + self.char_idx -= 1 + + iter = self.lessonbuffer.get_iter_at_mark(self.line_mark) + iter.forward_chars(self.char_idx) + + iter_end = iter.copy() + iter_end.forward_char() + + self.lessonbuffer.delete(iter, iter_end) + + # Process normal key presses. + elif key != 0: + + # Timer starts with first keypress. + if not self.start_time: + self.start_time = time.time() + + # Check to see if they pressed the correct key. + if key == self.line[self.char_idx]: + tag_name = 'correct-copy' + self.correct_keys += 1 + self.total_keys += 1 + + else: + # TODO - Play 'incorrect key' sound here. + + tag_name = 'incorrect-copy' + self.incorrect_keys += 1 + self.total_keys += 1 + + # Insert the key itno the bufffer. + iter = self.lessonbuffer.get_iter_at_mark(self.line_mark) + iter.forward_chars(self.char_idx) + + self.lessonbuffer.insert_with_tags_by_name(iter, key, tag_name) + # Advance to the next character (or else). + self.char_idx += 1 + if self.char_idx >= len(self.line): + if self.line_idx < len(self.lines): + self.line_idx += 1 + self.begin_line() + else: + self.advance_step() + + self.update_stats() + + self.hilite_next_key() + + return False + + def hilite_next_key(self): + # Hilite the next key on the virtual keyboard. + self.keyboard.clear_hilite() + if len(self.line) > 0: + key = self.keyboard.find_key_by_letter(self.line[self.char_idx]) + if key: + key.set_hilite(True) + + # Move the cursor to the insert location. + iter = self.lessonbuffer.get_iter_at_mark(self.line_mark) + iter.forward_chars(self.char_idx) + self.lessonbuffer.place_cursor(iter) + + # Gain focus (this causes the cursor line to draw). + self.lessontext.grab_focus() + + # Scroll the TextView so the cursor is on screen. + self.lessontext.scroll_to_mark(self.lessonbuffer.get_insert(), 0) + + def finish_lesson(self): self.activity.pop_screen() self.update_stats() @@ -270,46 +415,6 @@ class LessonScreen(gtk.VBox): # Show the new medal (regardless of whether it was recorded). self.activity.push_screen(MedalScreen(medal, self.activity)) - def key_press_cb(self, widget, event): - if not self.step: - return False - - # Timer starts with first keypress. - if not self.start_time: - self.start_time = time.time() - - # Check to see if they pressed the correct key. - if event.keyval == ord(self.step['text'][self.char_idx]): - self.correct_keys += 1 - self.total_keys += 1 - - self.add_text('' + chr(event.keyval) + '') - - self.keyboard.clear_hilite() - self.keyboard.queue_draw() - - self.char_idx += 1 - if self.char_idx >= len(self.step['text']): - self.add_text('\n\n') - self.advance_step() - - else: - self.hilite_next_key() - - else: - # TODO - Play 'incorrect key' sound here. - - self.incorrect_keys += 1 - self.total_keys += 1 - - self.update_stats() - - return False - - def hilite_next_key(self): - key = self.keyboard.find_key_by_letter(self.step['text'][self.char_idx]) - key.set_hilite(True) - def stop_cb(self, widget): self.activity.pop_screen() -- cgit v0.9.1