# 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 . #!/usr/bin/env python """Typing Turtle - Interactive typing tutor for the OLPC XO.""" # Import standard Python modules. import logging, os, math, time, copy, json, locale from gettext import gettext as _ # Set up localization. locale.setlocale(locale.LC_ALL, '') # Import PyGTK. import gobject, pygtk, gtk, pango # Import Sugar UI modules. import sugar.activity.activity from sugar.graphics import * # Initialize logging. log = logging.getLogger('Typing Turtle') log.setLevel(logging.DEBUG) logging.basicConfig() import keyboard class LessonScreen(gtk.VBox): def __init__(self, lesson, activity): gtk.VBox.__init__(self) self.lesson = lesson self.activity = activity # Build the user interface. title = gtk.Label() title.set_markup("" + lesson['name'] + "") title.set_alignment(1.0, 0.0) stoplabel = gtk.Label(_('Go Back')) stopbtn = gtk.Button() stopbtn.add(stoplabel) stopbtn.connect('clicked', self.stop_cb) self.wpmlabel = gtk.Label() self.accuracylabel = gtk.Label() hbox = gtk.HBox() hbox.pack_start(stopbtn, False, False, 10) hbox.pack_start(self.wpmlabel, True, False, 10) 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) self.lessonscroll = gtk.ScrolledWindow() self.lessonscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) self.lessonscroll.add_with_viewport(self.lessontext) frame = gtk.Frame() frame.add(self.lessonscroll) self.keyboard = keyboard.Keyboard() self.keyboard.set_layout(keyboard.DEFAULT_LAYOUT) activity.add_events(gtk.gdk.KEY_PRESS_MASK) activity.connect('key-press-event', self.key_press_cb) self.pack_start(hbox, False, False, 10) self.pack_start(frame, True, True) self.pack_start(self.keyboard, True) self.show_all() self.begin_lesson() def begin_lesson(self): self.step = None self.markup = '' 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 self.wpm = 60.0 * self.total_words / self.total_time self.accuracy = 100.0 * self.correct_keys / self.total_keys 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 advance_step(self): if self.next_step_idx < len(self.lesson['steps']): # TODO - Play 'step finished' sound here. self.step = self.lesson['steps'][self.next_step_idx] self.next_step_idx = self.next_step_idx + 1 self.add_text(self.step['instructions'] + '\n\n') self.add_text('' + self.step['text'] + '\n') self.char_idx = 0 else: self.finish_lesson() def finish_lesson(self): self.step = None self.update_stats() self.add_text(_('Congratulations! You finished the lesson in %(time)d seconds.\n\n') % { 'time': int(self.total_time) } ) # Add to the game history. self.activity.add_history({ 'lesson': self.lesson['name'], 'time': self.total_time, 'wpm': self.wpm, 'accuracy': self.accuracy }) 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.char_idx += 1 if self.char_idx >= len(self.step['text']): self.add_text('\n\n') self.advance_step() else: # TODO - Play 'incorrect key' sound here. self.incorrect_keys += 1 self.total_keys += 1 self.update_stats() return False def stop_cb(self, widget): self.activity.pop_screen() class MainScreen(gtk.VBox): def __init__(self, activity): gtk.VBox.__init__(self) self.activity = activity # Build background. title = gtk.Label() title.set_markup("" + _('Typing Turtle') + "") subtitle = gtk.Label() subtitle.set_markup(_('Welcome to Typing Turtle! To begin, select a lesson from the list below.')) spacer = gtk.HBox() # Lessons header. headerbox = gtk.VBox() label = gtk.Label() label.set_alignment(0.0, 0.5) label.set_markup(""+_('Available Lessons')+"") headerbox.pack_start(label, False) headerbox.pack_start(gtk.HSeparator(), False) # Build lessons list. self.lessonbox = gtk.VBox() self.lessonbox.set_spacing(10) code = locale.getlocale(locale.LC_ALL)[0] lessons = [] fd = open(sugar.activity.activity.get_bundle_path() + '/lessons/LESSONS.'+code, 'r') try: lessons = json.read(fd.read()) finally: fd.close() log.debug("Lessons: %r", lessons) for l in lessons: label = gtk.Label() label.set_alignment(0.0, 0.5) label.set_markup("" + l['name'] + "\n" + l['description']) medal = gtk.Image() medal.set_from_file(sugar.activity.activity.get_bundle_path() + '/images/gold-medal.jpg') hbox = gtk.HBox() hbox.pack_start(label, True, True, 10) hbox.pack_end(medal, False, False) btn = gtk.Button() btn.add(hbox) btn.lesson = l btn.connect('clicked', self.button_cb) self.lessonbox.pack_start(btn, False) self.lessonscroll = gtk.ScrolledWindow() self.lessonscroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) self.lessonscroll.add_with_viewport(self.lessonbox) self.pack_start(title, False, True, 10) self.pack_start(subtitle, False) self.pack_start(spacer, False, False, 50) self.pack_start(headerbox, False) self.pack_start(self.lessonscroll, True) def button_cb(self, widget): self.activity.push_screen(LessonScreen(widget.lesson, self.activity)) # This is the main Typing Turtle activity class. # # It owns the main application window, and all the various toolbars and options. # Activity Screens are stored in a stack, with the currently active screen on top. class TypingTurtle(sugar.activity.activity.Activity): def __init__ (self, handle): sugar.activity.activity.Activity.__init__(self, handle) self.set_title(_("Typing Turtle")) self.build_toolbox() self.screens = [] self.screenbox = gtk.VBox() # All data which is saved in the Journal entry is placed in this dictionary. self.data = { 'history': [] } # This has to happen last, because it calls the read_file method when restoring from the Journal. self.set_canvas(self.screenbox) # Start with the main screen. self.push_screen(MainScreen(self)) self.show_all() # Hide the sharing button from the activity toolbar since we don't support sharing. activity_toolbar = self.tbox.get_activity_toolbar() activity_toolbar.share.props.visible = False def build_toolbox(self): self.tbox = sugar.activity.activity.ActivityToolbox(self) self.tbox.show_all() self.set_toolbox(self.tbox) def push_screen(self, screen): if len(self.screens): self.screenbox.remove(self.screens[-1]) self.screenbox.pack_start(screen, True, True) self.screens.append(screen) def pop_screen(self): self.screenbox.remove(self.screens[-1]) self.screens.pop() if len(self.screens): self.screenbox.pack_start(self.screens[-1]) def add_history(self, entry): self.data['history'].append(entry) def read_file(self, file_path): if self.metadata['mime_type'] != 'text/plain': return fd = open(file_path, 'r') try: text = fd.read() print "read %s" % text self.data = json.read(text) finally: fd.close() def write_file(self, file_path): if not self.metadata['mime_type']: self.metadata['mime_type'] = 'text/plain' fd = open(file_path, 'w') try: text = json.write(self.data) fd.write(text) print "wrote %s" % text finally: fd.close()