Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/ICanReadActivity.py
diff options
context:
space:
mode:
Diffstat (limited to 'ICanReadActivity.py')
-rw-r--r--ICanReadActivity.py531
1 files changed, 531 insertions, 0 deletions
diff --git a/ICanReadActivity.py b/ICanReadActivity.py
new file mode 100644
index 0000000..1cbaf0f
--- /dev/null
+++ b/ICanReadActivity.py
@@ -0,0 +1,531 @@
+#Copyright (c) 2011 Walter Bender
+
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+
+import gtk
+
+from sugar.activity import activity
+try:
+ from sugar.graphics.toolbarbox import ToolbarBox, ToolbarButton
+ _HAVE_TOOLBOX = True
+except ImportError:
+ _HAVE_TOOLBOX = False
+
+if _HAVE_TOOLBOX:
+ from sugar.activity.widgets import ActivityToolbarButton
+ from sugar.activity.widgets import StopButton
+
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.combobox import ComboBox
+from sugar.graphics.toolcombobox import ToolComboBox
+from sugar.datastore import datastore
+from sugar import profile
+
+from gettext import gettext as _
+import os.path
+
+from page import Page
+from utils.grecord import Grecord
+from utils.play_audio import play_audio_from_file
+
+SERVICE = 'org.sugarlabs.ICanReadActivity'
+IFACE = SERVICE
+PATH = '/org/sugarlabs/ICanReadActivity'
+
+
+def _button_factory(icon_name, tooltip, callback, toolbar, cb_arg=None,
+ accelerator=None):
+ ''' Factory for making toolbar buttons '''
+ my_button = ToolButton(icon_name)
+ my_button.set_tooltip(tooltip)
+ my_button.props.sensitive = True
+ if accelerator is not None:
+ my_button.props.accelerator = accelerator
+ if cb_arg is not None:
+ my_button.connect('clicked', callback, cb_arg)
+ else:
+ my_button.connect('clicked', callback)
+ if hasattr(toolbar, 'insert'): # the main toolbar
+ toolbar.insert(my_button, -1)
+ else: # or a secondary toolbar
+ toolbar.props.page.insert(my_button, -1)
+ my_button.show()
+ return my_button
+
+
+def _label_factory(label, toolbar):
+ ''' Factory for adding a label to a toolbar '''
+ my_label = gtk.Label(label)
+ my_label.set_line_wrap(True)
+ my_label.show()
+ toolitem = gtk.ToolItem()
+ toolitem.add(my_label)
+ toolbar.insert(toolitem, -1)
+ toolitem.show()
+ return my_label
+
+
+def _combo_factory(options, tooltip, toolbar, callback, default=0):
+ ''' Combo box factory '''
+ combo = ComboBox()
+ if hasattr(combo, 'set_tooltip_text'):
+ combo.set_tooltip_text(tooltip)
+ combo.connect('changed', callback)
+ for i, option in enumerate(options):
+ combo.append_item(i, option.replace('-', ' '), None)
+ combo.set_active(default)
+ combo.show()
+ tool = ToolComboBox(combo)
+ tool.show()
+ toolbar.insert(tool, -1)
+ return combo
+
+
+def _separator_factory(toolbar, visible=True, expand=False):
+ ''' Factory for adding a separator to a toolbar '''
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = visible
+ separator.set_expand(expand)
+ toolbar.insert(separator, -1)
+ separator.show()
+
+
+def chooser(parent_window, filter, action):
+ ''' Choose an object from the datastore and take some action '''
+ from sugar.graphics.objectchooser import ObjectChooser
+
+ _chooser = None
+ try:
+ _chooser = ObjectChooser(parent=parent_window, what_filter=filter)
+ except TypeError:
+ _chooser = ObjectChooser(None, parent_window,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
+ if _chooser is not None:
+ try:
+ result = _chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ dsobject = _chooser.get_selected_object()
+ action(dsobject)
+ dsobject.destroy()
+ finally:
+ _chooser.destroy()
+ del _chooser
+
+
+class ICanReadActivity(activity.Activity):
+ ''' ICanRead Reading guide '''
+
+ def __init__(self, handle):
+ ''' Initialize the toolbars and the reading board '''
+ super(ICanReadActivity, self).__init__(handle)
+ self.reading = False
+ self.testing = False
+ self.recording = False
+ self.grecord = None
+ self.datapath = get_path(activity, 'instance')
+
+ if 'LANG' in os.environ:
+ language = os.environ['LANG'][0:2]
+ elif 'LANGUAGE' in os.environ:
+ language = os.environ['LANGUAGE'][0:2]
+ else:
+ language = 'es' # default to Spanish
+ if os.path.exists(os.path.join('~', 'Activities', 'ICanRead.activity')):
+ self._path = os.path.join('~', 'Activities', 'ICanRead.activity',
+ 'lessons', language)
+ else:
+ self._path = os.path.join('.', 'lessons', language)
+
+ self._setup_toolbars()
+
+ # Create a canvas
+ self.scrolled_window = gtk.ScrolledWindow()
+ self.set_canvas(self.scrolled_window)
+ self.scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.scrolled_window.show()
+ canvas = gtk.DrawingArea()
+ width = gtk.gdk.screen_width()
+ height = gtk.gdk.screen_height() * 2
+ canvas.set_size_request(width, height)
+ self.scrolled_window.add_with_viewport(canvas)
+ canvas.show()
+
+ self._level = self._levels_combo.get_active()
+ self._page = Page(canvas, self._path, self._levels[self._level],
+ parent=self)
+
+ # Restore state from Journal or start new session
+ if 'page' in self.metadata:
+ self._restore()
+ else:
+ self._page.new_page()
+
+ # Set up sound combo box
+ self._reload_sound_combo()
+ self._selected_sound = self.sounds_combo.get_active()
+
+
+ def _setup_toolbars(self):
+ ''' Setup the toolbars.. '''
+
+ # no sharing
+ self.max_participants = 1
+
+ if _HAVE_TOOLBOX:
+ toolbox = ToolbarBox()
+
+ # Activity toolbar
+ activity_button = ActivityToolbarButton(self)
+
+ toolbox.toolbar.insert(activity_button, 0)
+ activity_button.show()
+
+ lesson_toolbar = gtk.Toolbar()
+ lesson_toolbar_button = ToolbarButton(label=_('Select a lesson'),
+ page=lesson_toolbar,
+ icon_name='text-x-generic')
+ record_toolbar = gtk.Toolbar()
+ record_toolbar_button = ToolbarButton(label=_('Record a sound'),
+ page=record_toolbar,
+ icon_name='microphone')
+
+ self.set_toolbar_box(toolbox)
+ toolbox.show()
+ lesson_toolbar_button.show()
+ toolbox.toolbar.insert(lesson_toolbar_button, -1)
+ record_toolbar_button.show()
+ toolbox.toolbar.insert(record_toolbar_button, -1)
+ primary_toolbar = toolbox.toolbar
+
+ else:
+ # Use pre-0.86 toolbar design
+ primary_toolbar = gtk.Toolbar()
+ lesson_toolbar = gtk.Toolbar()
+ record_toolbar = gtk.Toolbar()
+ toolbox = activity.ActivityToolbox(self)
+ self.set_toolbox(toolbox)
+ toolbox.add_toolbar(_('Page'), primary_toolbar)
+ toolbox.show()
+ toolbox.add_toolbar(_('Lesson'), lesson_toolbar)
+ toolbox.show()
+ toolbox.add_toolbar(_('Record'), record_toolbar)
+ toolbox.show()
+ toolbox.set_current_toolbar(1)
+
+ # no sharing
+ if hasattr(toolbox, 'share'):
+ toolbox.share.hide()
+ elif hasattr(toolbox, 'props'):
+ toolbox.props.visible = False
+
+ _label_factory(_('Select a lesson') + ':', lesson_toolbar)
+ self._levels = self._get_levels(self._path)
+ self._levels_combo = _combo_factory(self._levels, _('Select a lesson'),
+ lesson_toolbar, self._levels_cb)
+
+ _separator_factory(lesson_toolbar)
+
+ self._lesson_button = _button_factory(
+ 'load-from-journal', _('Load a new lesson from the Journal'),
+ self._lesson_cb, lesson_toolbar)
+
+ _separator_factory(lesson_toolbar)
+
+ self._create_lesson_button = _button_factory(
+ 'view-source-insensitive', _('Create a new lesson'),
+ self._create_lesson_cb, lesson_toolbar)
+
+ self._save_lesson_button = _button_factory(
+ 'save-to-journal-insensitive', _('Nothing to save'),
+ self._save_lesson_cb, lesson_toolbar)
+
+ self._sounds = self._get_sounds()
+ self.sounds_combo = _combo_factory(self._sounds, _('Record a sound'),
+ record_toolbar, self._sounds_cb)
+
+ _separator_factory(record_toolbar)
+
+ _label_factory(_('Record a lesson') + ':', record_toolbar)
+ self._record_lesson_button = _button_factory(
+ 'media-record', _('Start recording'),
+ self._record_lesson_cb, record_toolbar)
+
+ _separator_factory(record_toolbar)
+
+ self._playback_button = _button_factory(
+ 'media-playback-start-insensitive', _('Nothing to play'),
+ self._playback_recording_cb, record_toolbar)
+
+ self._save_recording_button = _button_factory(
+ 'sound-save-insensitive', _('Nothing to save'),
+ self._save_recording_cb, record_toolbar)
+
+ _separator_factory(primary_toolbar)
+
+ self._list_button = _button_factory(
+ 'format-justify-fill', _('Letter list'), self._list_cb,
+ primary_toolbar)
+
+ _separator_factory(primary_toolbar)
+
+ self._prev_page_button = _button_factory(
+ 'previous-letter-insensitive', _('Previous letter'),
+ self._prev_page_cb, primary_toolbar)
+
+ self._next_page_button = _button_factory(
+ 'next-letter', _('Next letter'), self._next_page_cb,
+ primary_toolbar)
+
+ _separator_factory(primary_toolbar)
+
+ self._read_button = _button_factory(
+ 'read', _('Read the sounds one at a time'),
+ self._read_cb, primary_toolbar)
+
+ _separator_factory(primary_toolbar)
+
+ self._test_button = _button_factory('go-right', _('Self test'),
+ self._test_cb, primary_toolbar)
+
+ self.status = _label_factory('', primary_toolbar)
+
+ if _HAVE_TOOLBOX:
+ _separator_factory(toolbox.toolbar, False, True)
+
+ stop_button = StopButton(self)
+ stop_button.props.accelerator = '<Ctrl>q'
+ toolbox.toolbar.insert(stop_button, -1)
+ stop_button.show()
+ lesson_toolbar.show()
+ record_toolbar.show()
+
+ def _levels_cb(self, combobox=None):
+ ''' The combo box has changed. '''
+ if hasattr(self, '_levels_combo'):
+ i = self._levels_combo.get_active()
+ if i != -1 and i != self._level:
+ self._level = i
+ # TODO: levels stored in Journal have a different path
+ try:
+ self._page.load_level(os.path.join(
+ self._path, self._levels[self._level] + '.csv'))
+ except IndexError:
+ print "couldn't restore level %s" % (self.metadata['level'])
+ self._levels_combo.set_active(0)
+ self._page.page = 0
+ self._page.new_page()
+ print 'reloading sound combo box with level sounds'
+ self._reload_sound_combo()
+ self._selected_sound = self.sounds_combo.get_active()
+ return
+
+ def _lesson_cb(self, button=None):
+ ''' Chose a lesson file from the Sugar Journal. '''
+ chooser(self, '', self._load_lesson)
+ return
+
+ def _create_lesson_cb(self, button=None):
+ ''' Chose a lesson file from the Sugar Journal. '''
+ # Do something here:
+ # We need a place to add and edit text
+ # We need a place to select phonemes and assign colors
+ return
+
+ def _save_lesson_cb(self, button=None):
+ ''' Save a lesson file to the Sugar Journal. '''
+ if self._nothing_to_save:
+ return
+ # Do something here
+ return
+
+ def _sounds_cb(self, combobox=None):
+ ''' The combo box has changed. '''
+ if hasattr(self, 'sounds_combo'):
+ self._selected_sound = self.sounds_combo.get_active()
+
+ def _list_cb(self, button=None):
+ ''' Letter list '''
+ self._page.page_list()
+ self.reading = False
+
+ def _prev_page_cb(self, button=None):
+ ''' Start a new letter. '''
+ if self._page.page > 0:
+ self._page.page -= 1
+ if self._page.page == 0:
+ self._prev_page_button.set_icon('previous-letter-insensitive')
+ self._page.new_page()
+ self.reading = False
+ self.testing = False
+ self._read_button.set_icon('read')
+ self._read_button.set_tooltip(_('Show letter'))
+ self._test_button.set_icon('go-right')
+ self._test_button.set_tooltip(_('Self test'))
+
+ def _next_page_cb(self, button=None):
+ ''' Start a new letter. '''
+ self._page.page += 1
+ self._page.new_page()
+ self.reading = False
+ self.testing = False
+ self._read_button.set_icon('read')
+ self._read_button.set_tooltip(_('Show letter'))
+ self._prev_page_button.set_icon('previous-letter')
+ self._test_button.set_icon('go-right')
+ self._test_button.set_tooltip(_('Self test'))
+
+ def _read_cb(self, button=None):
+ ''' Start a new page. '''
+ if not self.reading:
+ self.reading = True
+ self.testing = False
+ self._page.read()
+ self._read_button.set_icon('listen')
+ self._read_button.set_tooltip(_('Show letter'))
+ self._test_button.set_icon('go-right')
+ self._test_button.set_tooltip(_('Self test'))
+ else:
+ self.reading = False
+ self.testing = False
+ self._page.reload()
+ self._read_button.set_icon('read')
+ self._read_button.set_tooltip(_('Read the sounds one at a time'))
+ self._test_button.set_icon('go-right')
+ self._test_button.set_tooltip(_('Self test'))
+
+ def _test_cb(self, button=None):
+ ''' Start a test. '''
+ if not self.testing:
+ self.testing = True
+ self._page.test()
+ self._test_button.set_icon('go-left')
+ self._test_button.set_tooltip(_('Return to reading'))
+ else:
+ self.testing = False
+ self._page.reload()
+ self._test_button.set_icon('go-right')
+ self._test_button.set_tooltip(_('Self test'))
+
+ def write_file(self, file_path):
+ ''' Write status to the Journal '''
+ if not hasattr(self, '_page'):
+ return
+ self.metadata['page'] = str(self._page.page)
+ self.metadata['level'] = str(self._level)
+
+ def _restore(self):
+ ''' Load up cards until we get to the page we stopped on. '''
+ if 'level' in self.metadata:
+ level = int(self.metadata['level'])
+ self._level = level
+ self._levels_combo.set_active(level)
+ try:
+ self._page.load_level(os.path.join(
+ self._path, self._levels[self._level] + '.csv'))
+ except IndexError:
+ print "couldn't restore level %s" % (self.metadata['level'])
+ self._levels_combo.set_active(0)
+ self._page.page = 0
+ self._page.new_page()
+ if 'page' in self.metadata:
+ page = int(self.metadata['page'])
+ for _i in range(page):
+ self._next_page_cb()
+
+ def _get_levels(self, path):
+ ''' Look for level files in lessons directory. '''
+ level_files = []
+ if path is not None:
+ candidates = os.listdir(path)
+ for filename in candidates:
+ if not self._skip_this_file(filename):
+ level_files.append(filename.split('.')[0])
+ level_files.reverse()
+ return level_files
+
+ def _get_sounds(self):
+ ''' Look for sounds list. '''
+ if hasattr(self, '_page'):
+ return self._page.get_phrase_list()
+ else:
+ return([])
+
+ def _reload_sound_combo(self):
+ ''' Rebuild sounds combobox. '''
+ self.sounds_combo.remove_all() # Remove old list.
+ self._sounds = self._get_sounds()
+ for i, sound in enumerate(self._sounds):
+ self.sounds_combo.append_item(i, sound.lower(), None)
+ self.sounds_combo.set_active(self._page.page)
+
+ def _record_lesson_cb(self, button=None):
+ if self.grecord is None:
+ self.grecord = Grecord(self)
+ if self.recording:
+ self.grecord.stop_recording_audio()
+ self.recording = False
+ self._record_lesson_button.set_icon('media-record')
+ self._record_lesson_button.set_tooltip(_('Start recording'))
+ self._playback_button.set_icon('media-playback-start')
+ self._playback_button.set_tooltip(_('Play recording'))
+ self._save_recording_button.set_icon('sound-save')
+ self._save_recording_button.set_tooltip(_('Save recording'))
+ else:
+ self.grecord.record_audio()
+ self.recording = True
+ self._record_lesson_button.set_icon('media-recording')
+ self._record_lesson_button.set_tooltip(_('Stop recording'))
+
+ def _playback_recording_cb(self, button=None):
+ play_audio_from_file(self._page, os.path.join(self.datapath,
+ 'output.ogg'))
+ return
+
+ def _save_recording_cb(self, button=None):
+ savename = self._sounds[self._selected_sound].lower() + '.ogg'
+ if os.path.exists(os.path.join(self.datapath, 'output.ogg')):
+ dsobject = datastore.create()
+ dsobject.metadata['title'] = savename
+ dsobject.metadata['icon-color'] = \
+ profile.get_color().to_string()
+ dsobject.metadata['mime_type'] = 'audio/ogg'
+ dsobject.set_file_path(os.path.join(self.datapath, 'output.ogg'))
+ datastore.write(dsobject)
+ dsobject.destroy()
+ return
+
+ def _skip_this_file(self, filename):
+ ''' Ignore tmp files '''
+ if filename[0] in '#.' or filename[-1] == '~':
+ return True
+ return False
+
+ def _load_lesson(self, dsobject):
+ # TODO: load level and set combo to proper entry
+ # save levels for latter restoring
+ print dsobject.metadata['title']
+ self._levels_combo.append_item(0, dsobject.metadata['title'], None)
+ self._levels_combo.set_active(0)
+ self._page.load_level(dsobject.file_path)
+ self._page.page = 0
+ self._page.new_page()
+ # TO DO: reset sound combo
+
+
+def get_path(activity, subpath):
+ """ Find a Rainbow-approved place for temporary files. """
+ try:
+ return(os.path.join(activity.get_activity_root(), subpath))
+ except:
+ # Early versions of Sugar didn't support get_activity_root()
+ return(os.path.join(
+ os.environ['HOME'], ".sugar/default", SERVICE, subpath))