class Page(): ''' Pages from Infuse Reading method ''' def __init__(self, canvas, lessons_path, images_path, sounds_path, parent=None): ''' The general stuff we need to track ''' self._activity = parent self._lessons_path = lessons_path self._images_path = images_path self._sounds_path = sounds_path self._colors = profile.get_color().to_string().split(',') self._card_data = [] self._color_data = [] self._image_data = [] self._word_data = [] self.chosen_image = None # Starting from command line if self._activity is None: self._sugar = False self._canvas = canvas else: self._sugar = True self._canvas = canvas self._activity.show_all() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.connect("draw", self.__draw_cb) self.button_release_event_id = \ self._canvas.connect("button-release-event", self._button_release_cb) self.button_press_event_id = \ self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("key_press_event", self._keypress_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() self._card_height = int((self._height - GRID_CELL_SIZE) / YDIM) \ - GUTTER * 2 self._card_width = int(self._card_height * 4 / 3.) self._grid_x_offset = int( (self._width - XDIM * (self._card_width + GUTTER * 2)) / 2) self._grid_y_offset = 0 self._scale = self._card_width / 80. self._sprites = Sprites(self._canvas) self.current_card = 0 self._cards = [] self._pictures = [] self._press = None self._release = None self.timeout = None self.target = 0 self.answers = [] self._my_canvas = Sprite( self._sprites, 0, 0, svg_str_to_pixbuf( genblank(self._width, self._height, (self._colors[0], self._colors[0])))) self._my_canvas.type = 'background' self._smile = Sprite( self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._activity.activity_path, 'images', 'correct.png'), int(self._width / 2), int(self._height / 2))) self._frown = Sprite( self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._activity.activity_path, 'images', 'wrong.png'), int(self._width / 2), int(self._height / 2))) self.load_level(os.path.join(self._lessons_path, 'alphabet' + '.csv')) # Create the cards we'll need self._alpha_cards() self.load_from_journal(self._activity.data_from_journal) self.new_page() def _hide_feedback(self): if hasattr(self, '_smile'): self._smile.hide() self._frown.hide() def new_page(self): ''' Load a page of cards ''' if self.timeout is not None: GObject.source_remove(self.timeout) self.answers = [] for i in range(min(6, len(self._cards))): self.answers.append(0) self._hide_cards() self._hide_feedback() self.new_target() x = self._grid_x_offset + self._card_width + GUTTER * 3 y = self._grid_y_offset + GUTTER if self._activity.mode == 'letter': self._cards[self.target].move((x, y)) self._cards[self.target].set_layer(100) x = self._grid_x_offset + GUTTER y = self._grid_y_offset + self._card_height + GUTTER * 3 for i in range(len(self.answers)): alphabet = self._card_data[self.answers[i]][0] # select the sprite randomly s = choice(self._image_data[alphabet])[0] s.move((x, y)) s.set_layer(100) x += self._card_width + GUTTER * 2 if x > self._width - (self._card_width / 2): x = self._grid_x_offset + GUTTER y += self._card_height + GUTTER * 2 else: alphabet = self._card_data[self.target][0] self.chosen_image = choice(self._image_data[alphabet]) s = self.chosen_image[0] s.move((x, y)) s.set_layer(100) x = self._grid_x_offset + GUTTER y = self._grid_y_offset + self._card_height + GUTTER * 3 for i in range(len(self.answers)): self._cards[self.answers[i]].move((x, y)) self._cards[self.answers[i]].set_layer(100) x += self._card_width + GUTTER * 2 if x > self._width - (self._card_width / 2): x = self._grid_x_offset + GUTTER y += self._card_height + GUTTER * 2 def _hide_cards(self): if len(self._cards) > 0: for card in self._cards: card.hide() if len(self._pictures) > 0: for card in self._pictures: card.hide() def _alpha_cards(self): for card in self._card_data: self.current_card = self._card_data.index(card) # Two-tone cards add some complexity. if type(self._color_data[self.current_card][0]) == type([]): stroke = self._test_for_stroke() top = svg_str_to_pixbuf( generate_card( string=card[0], colors=[ self._color_data[self.current_card][0][0], '#FFFFFF' ], scale=self._scale, center=True)) bot = svg_str_to_pixbuf( generate_card( string=card[0], colors=[ self._color_data[self.current_card][0][1], '#FFFFFF' ], scale=self._scale, center=True)) # Where to draw the line h1 = 9 / 16. h2 = 1.0 - h1 bot.composite(top, 0, int(h1 * top.get_height()), top.get_width(), int(h2 * top.get_height()), 0, 0, 1, 1, GdkPixbuf.InterpType.NEAREST, 255) self._cards.append(Sprite(self._sprites, 0, 0, top)) else: stroke = self._test_for_stroke() self._cards.append( Sprite( self._sprites, 0, 0, svg_str_to_pixbuf( generate_card( string='%s%s' % (card[0].upper(), card[0].lower()), colors=[ self._color_data[self.current_card][0], '#FFFFFF' ], stroke=stroke, scale=self._scale, center=True)))) def _test_for_stroke(self): ''' Light colors get a surrounding stroke ''' if self._color_data[self.current_card][0][0:4] == '#FFF': return True else: return False def new_target(self): ''' Generate a new target and answer list ''' self._activity.status.set_text( _('Click on the card that corresponds to the sound.')) self.target = int(uniform(0, len(self._cards))) for i in range(min(6, len(self._cards))): self.answers[i] = int(uniform(0, len(self._cards))) for i in range(min(6, len(self._cards))): while self._bad_answer(i): self.answers[i] += 1 self.answers[i] %= len(self._cards) # Choose a random card and assign it to the target i = int(uniform(0, min(6, len(self._cards)))) self.answers[i] = self.target if self.timeout is not None: GObject.source_remove(self.timeout) self.timeout = GObject.timeout_add(1000, self._play_target_sound, False) def _bad_answer(self, i): ''' Make sure answer is unique ''' if self.answers[i] == self.target: return True for j in range(min(6, len(self._cards))): if i == j: continue if self.answers[i] == self.answers[j]: return True return False def _play_target_sound(self, queue=True): if self._activity.mode == 'letter': play_audio_from_file(self._card_data[self.target][-1], queue) else: play_audio_from_file(self.chosen_image[-1], queue) self.timeout = None def _button_press_cb(self, win, event): ''' Either a card or list entry was pressed. ''' win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) self._press = spr self._release = None return True def _button_release_cb(self, win, event): ''' Play a sound or video or jump to a card as indexed in the list. ''' win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) self.current_card = -1 if self._activity.mode == 'letter': if spr in self._cards: self.current_card = self._cards.index(spr) play_audio_from_file(self._card_data[self.current_card][-1]) return for a in self._image_data: for b in self._image_data[a]: if spr == b[0]: play_audio_from_file(b[1]) for c in range(len(self._card_data)): if self._card_data[c][0] == a: self.current_card = c break break else: for a in self._image_data: for b in self._image_data[a]: if spr == b[0]: play_audio_from_file(b[1]) return if spr in self._cards: self.current_card = self._cards.index(spr) play_audio_from_file(self._card_data[self.current_card][-1]) if self.current_card == self.target: self._activity.status.set_text(_('Very good!')) self._play(True) if self.timeout is not None: GObject.source_remove(self.timeout) self.timeout = GObject.timeout_add(1000, self.new_page) else: self._activity.status.set_text(_('Please try again.')) self._play(False) self._play_target_sound() self.timeout = GObject.timeout_add(1000, self._hide_feedback) def _play(self, great): if great: self._smile.set_layer(1000) # play_audio_from_file(os.getcwd() + '/sounds/great.ogg') else: self._frown.set_layer(1000) # play_audio_from_file(os.getcwd() + '/sounds/bad.ogg') def _keypress_cb(self, area, event): ''' No keyboard shortcuts at the moment. Perhaps jump to the page associated with the key pressed? ''' return True def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True # Handle the expose-event by drawing def do_expose_event(self, event): # Create the cairo context cr = self._canvas.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): ''' Make a clean exit. ''' Gtk.main_quit() def invalt(self, x, y, w, h): ''' Mark a region for refresh ''' rectangle = Gdk.Rectangle() rectangle.x = x rectangle.y = y rectangle.width = w rectangle.height = h self._canvas.window.invalidate_rect(rectangle) def load_level(self, path): ''' Load a level (CSV) from path: letter, word, color, image, image sound, letter sound ''' self._card_data = [] # (letter, word, letter_sound_path) self._color_data = [] self._image_data = {} # {letter: [(Sprite, image_sound_path)...]} self._pictures = [] f = open(path) for line in f: if len(line) > 0 and line[0] not in '#\n': words = line.split(', ') self._card_data.append((words[0], words[1].replace('-', ', '), os.path.join(self._sounds_path, words[5]))) if words[2].count('#') > 1: self._color_data.append([words[2].split('/')]) else: self._color_data.append([words[2]]) imagefilename = words[3] imagepath = os.path.join(self._images_path, imagefilename) pixbuf = image_file_to_pixbuf(imagepath, self._card_width, self._card_height) s = Sprite(self._sprites, 0, 0, pixbuf) self._image_data[words[0]] = \ [(s, os.path.join(self._sounds_path, words[4]))] self._pictures.append(s) f.close() self._clear_all() self._cards = [] self._colored_letters_lower = [] self._colored_letters_upper = [] def load_from_journal(self, journal_data): for card in self._card_data: alphabet = card[0] if alphabet in journal_data: for images in journal_data[alphabet]: imagedataobject = datastore.get(images[0]) audiodataobject = datastore.get(images[1]) if imagedataobject and audiodataobject: imagepath = imagedataobject.get_file_path() pixbuf = image_file_to_pixbuf(imagepath, self._card_width, self._card_height) audiopath = audiodataobject.get_file_path() s = Sprite(self._sprites, 0, 0, pixbuf) self._image_data[alphabet].append((s, audiopath)) self._pictures.append(s) def _clear_all(self): ''' Hide everything so we can begin a new page. ''' self._hide_cards()
class Page(): ''' Pages from Infuse Reading method ''' def __init__(self, canvas, lessons_path, images_path, sounds_path, parent=None): ''' The general stuff we need to track ''' self._activity = parent self._lessons_path = lessons_path self._images_path = images_path self._sounds_path = sounds_path self._colors = profile.get_color().to_string().split(',') self._card_data = [] self._color_data = [] self._image_data = [] self._media_data = [] # (image sound, letter sound) self._word_data = [] self._deja_vu = [] # Starting from command line if self._activity is None: self._sugar = False self._canvas = canvas else: self._sugar = True self._canvas = canvas self._activity.show_all() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("button-release-event", self._button_release_cb) self._canvas.connect("key_press_event", self._keypress_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() self._card_width = int((self._width / XDIM)) - GUTTER * 2 self._card_height = int((self._height - GRID_CELL_SIZE) / YDIM) \ - GUTTER * 2 self._grid_x_offset = int( (self._width - XDIM * (self._card_width + GUTTER * 2)) / 2) self._grid_y_offset = 0 self._scale = self._card_width / 80. self._sprites = Sprites(self._canvas) self.current_card = 0 self._cards = [] self._pictures = [] self._press = None self._release = None self.timeout = None self._my_canvas = Sprite( self._sprites, 0, 0, svg_str_to_pixbuf( genblank(self._width, self._height, (self._colors[0], self._colors[0])))) self._my_canvas.type = 'background' self._smile = Sprite( self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._activity.activity_path, 'images', 'correct.png'), int(self._width / 2), int(self._height / 2))) self._frown = Sprite( self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._activity.activity_path, 'images', 'wrong.png'), int(self._width / 2), int(self._height / 2))) self.load_level(os.path.join(self._lessons_path, 'alphabet' + '.csv')) self.new_page() def _hide_feedback(self): if hasattr(self, '_smile'): self._smile.hide() self._frown.hide() def new_page(self, cardtype='alpha'): ''' Load a page of cards ''' if self.timeout is not None: GObject.source_remove(self.timeout) self._hide_cards() if cardtype == 'alpha': self._alpha_cards() else: self._image_cards() def _hide_cards(self): if len(self._cards) > 0: for card in self._cards: card.hide() if len(self._pictures) > 0: for card in self._pictures: card.hide() self._hide_feedback() def _image_cards(self): x = self._grid_x_offset + GUTTER y = self._grid_y_offset + GUTTER if len(self._pictures) > 0: for card in self._pictures: card.set_layer(100) return for card in self._card_data: self.current_card = self._card_data.index(card) imagefilename = self._image_data[self.current_card] imagepath = os.path.join(self._images_path, imagefilename) pixbuf = image_file_to_pixbuf(imagepath, self._card_width, self._card_height) self._pictures.append(Sprite(self._sprites, x, y, pixbuf)) x += self._card_width + GUTTER * 2 if x > self._width - (self._card_width / 2): x = self._grid_x_offset + GUTTER y += self._card_height + GUTTER * 2 def _alpha_cards(self): x = self._grid_x_offset + GUTTER y = self._grid_y_offset + GUTTER if len(self._cards) > 0: for card in self._cards: card.set_layer(100) return for card in self._card_data: self.current_card = self._card_data.index(card) # Two-tone cards add some complexity. if type(self._color_data[self.current_card][0]) == type([]): stroke = self._test_for_stroke() top = svg_str_to_pixbuf( generate_card( string='%s%s' % (card[0].upper(), card[0].lower()), colors=[ self._color_data[self.current_card][0][0], '#FFFFFF' ], scale=self._scale, center=True)) bot = svg_str_to_pixbuf( generate_card( string='%s%s' % (card[0].upper(), card[0].lower()), colors=[ self._color_data[self.current_card][0][1], '#FFFFFF' ], scale=self._scale, center=True)) # Where to draw the line h1 = 9 / 16. h2 = 1.0 - h1 bot.composite(top, 0, int(h1 * top.get_height()), top.get_width(), int(h2 * top.get_height()), 0, 0, 1, 1, GdkPixbuf.InterpType.NEAREST, 255) self._cards.append(Sprite(self._sprites, x, y, top)) else: stroke = self._test_for_stroke() self._cards.append( Sprite( self._sprites, x, y, svg_str_to_pixbuf( generate_card( string='%s%s' % (card[0].upper(), card[0].lower()), colors=[ self._color_data[self.current_card][0], '#FFFFFF' ], stroke=stroke, scale=self._scale, center=True)))) x += self._card_width + GUTTER * 2 if x > self._width - (self._card_width / 2): x = self._grid_x_offset + GUTTER * 2 y += self._card_height + GUTTER * 2 def _test_for_stroke(self): ''' Light colors get a surrounding stroke ''' if self._color_data[self.current_card][0][0:4] == '#FFF': return True else: return False def new_target(self): self._activity.status.set_text( _('Click on the card that corresponds to the sound.')) self.target = int(uniform(0, len(self._cards))) # Don't repeat while self.target in self._deja_vu: self.target += 1 self.target %= len(self._cards) self._deja_vu.append(self.target) if len(self._deja_vu) == len(self._cards): self._deja_vu = [] if self.timeout is not None: GObject.source_remove(self.timeout) self.timeout = GObject.timeout_add(1000, self._play_target_sound) def _play_target_sound(self): _logger.debug(self._activity.mode) if self._activity.mode in ['letter', 'find by letter']: aplay.play( os.path.join(self._sounds_path, self._media_data[self.target][1])) elif self._activity.mode == 'picture': aplay.play( os.path.join(self._sounds_path, self._media_data[self.target][1])) aplay.play( os.path.join(self._sounds_path, self._media_data[self.target][0])) else: aplay.play( os.path.join(self._sounds_path, self._media_data[self.target][0])) self.timeout = None def _button_press_cb(self, win, event): ''' Either a card or list entry was pressed. ''' win.grab_focus() x, y = list(map(int, event.get_coords())) spr = self._sprites.find_sprite((x, y)) self._press = spr self._release = None return True def _button_release_cb(self, win, event): ''' Play a sound or video or jump to a card as indexed in the list. ''' win.grab_focus() x, y = list(map(int, event.get_coords())) spr = self._sprites.find_sprite((x, y)) if spr is None: return if spr.type == 'background': return if spr in self._cards: self.current_card = self._cards.index(spr) elif spr in self._pictures: self.current_card = self._pictures.index(spr) if self._activity.mode in ['letter', 'picture']: self.target = self.current_card self._play_target_sound() elif self._activity.mode in ['find by letter', 'find by word']: if self.current_card == self.target: self._activity.status.set_text(_('Very good!')) self._play(True) if self.timeout is not None: GObject.source_remove(self.timeout) self.timeout = GObject.timeout_add(1000, self._correct_feedback) else: self._activity.status.set_text(_('Please try again.')) self._play(False) if self.timeout is not None: GObject.source_remove(self.timeout) self.timeout = GObject.timeout_add(1000, self._wrong_feedback) def _correct_feedback(self): self._hide_feedback() self.new_target() def _wrong_feedback(self): self._hide_feedback() self._play_target_sound() def _play(self, great): if great: self._smile.set_layer(1000) # aplay(os.getcwd() + '/sounds/great.ogg') else: self._frown.set_layer(1000) # aplay(os.getcwd() + '/sounds/bad.ogg') def _keypress_cb(self, area, event): ''' No keyboard shortcuts at the moment. Perhaps jump to the page associated with the key pressed? ''' return True def __draw_cb(self, canvas, cr): ''' Draw - Expose event ''' self._sprites.redraw_sprites(cr=cr) def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True # Handle the expose-event by drawing def do_expose_event(self, event): # Create the cairo context cr = self._canvas.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): ''' Make a clean exit. ''' Gtk.main_quit() def invalt(self, x, y, w, h): ''' Mark a region for refresh ''' rectangle = Gdk.Rectangle() rectangle.x = x rectangle.y = y rectangle.width = w rectangle.height = h self._canvas.window.invalidate_rect(rectangle) def load_level(self, path): ''' Load a level (CSV) from path: letter, word, color, image, image sound, letter sound ''' self._card_data = [] self._color_data = [] self._image_data = [] self._media_data = [] # (image sound, letter sound) f = open(path) for line in f: if len(line) > 0 and line[0] not in '#\n': words = line.split(', ') self._card_data.append([words[0], words[1].replace('-', ', ')]) if words[2].count('#') > 1: self._color_data.append([words[2].split('/')]) else: self._color_data.append([words[2]]) self._image_data.append(words[3]) self._media_data.append((words[4], words[5].strip())) f.close() self._clear_all() self._cards = [] self._colored_letters_lower = [] self._colored_letters_upper = [] def _clear_all(self): ''' Hide everything so we can begin a new page. ''' self._hide_cards()
class LetterMatch(activity.Activity): ''' Learning the alphabet. Level1: A letter card and six picture cards appear; the user listens to the name of letter and then selects the matching picture. Level2: A picture card and six letter cards appear; the user listens to the name of the picture and then selects the matching letter. Customization toolbar allows loading of new images and sounds. ''' def __init__(self, handle): ''' Initialize the toolbars and the reading board ''' super(LetterMatch, self).__init__(handle) self.datapath = get_path(activity, 'instance') self.image_id = None self.audio_id = None 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 # FIXME: find some reasonable default situation language = 'es' self.letter = None self.activity_path = activity.get_bundle_path() self._lessons_path = os.path.join(self.activity_path, 'lessons', language) self._images_path = os.path.join(self.activity_path, 'images', language) self._sounds_path = os.path.join(self.activity_path, 'sounds', language) self.data_from_journal = {} if 'data_from_journal' in self.metadata: self.data_from_journal = json.loads( str(self.metadata['data_from_journal'])) self._setup_toolbars() self.canvas = Gtk.DrawingArea() self.canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.canvas.modify_bg(Gtk.StateType.NORMAL, Gdk.color_parse("#000000")) self.canvas.show() self.set_canvas(self.canvas) self.mode = 'letter' self._page = Page(self.canvas, self._lessons_path, self._images_path, self._sounds_path, parent=self) def _setup_toolbars(self): self.max_participants = 1 # no sharing toolbox = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() separator = Gtk.SeparatorToolItem() toolbox.toolbar.insert(separator, -1) self.set_toolbar_box(toolbox) toolbox.show() primary_toolbar = toolbox.toolbar custom_toolbar = ToolbarBox() self.custom_toolbar_button = ToolbarButton(icon_name='view-source', page=custom_toolbar) self.custom_toolbar_button.connect('clicked', self._customization_toolbar_cb) toolbox.toolbar.insert(self.custom_toolbar_button, -1) button = radio_factory('letter', primary_toolbar, self._letter_cb, tooltip=_('listen to the letter names')) radio_factory('picture', primary_toolbar, self._picture_cb, tooltip=_('listen to the letter names'), group=button) self.status = label_factory(primary_toolbar, '', width=300) self.letter_entry = None self.image_button = button_factory('load_image_from_journal', custom_toolbar.toolbar, self._choose_image_from_journal_cb, tooltip=_("Import Image")) self.sound_button = button_factory('load_audio_from_journal', custom_toolbar.toolbar, self._choose_audio_from_journal_cb, tooltip=_("Import Audio")) container = Gtk.ToolItem() self.letter_entry = Gtk.Entry() self.letter_entry.set_max_length(1) self.letter_entry.set_width_chars(3) # because 1 char looks funny self.letter_entry.connect('changed', self._set_letter) self.letter_entry.set_sensitive(False) self.letter_entry.show() container.add(self.letter_entry) container.show_all() custom_toolbar.toolbar.insert(container, -1) self.add_button = button_factory('list-add', custom_toolbar.toolbar, self._copy_to_journal, tooltip=_("Add")) self.add_button.set_sensitive(False) separator_factory(primary_toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _set_letter(self, event): ''' Process letter in text entry ''' text = self.letter_entry.get_text().strip() if text and len(text) > 0: if len(text) != 1: text = text[0].upper() text = text.upper() self.letter_entry.set_text(text) self.letter = text if self.letter in self.data_from_journal: self.data_from_journal[self.letter].append( (self.image_id, self.audio_id)) else: self.data_from_journal[self.letter] = \ [(self.image_id, self.audio_id)] self.add_button.set_sensitive(True) else: self.letter = None self.add_button.set_sensitive(False) def _copy_to_journal(self, event): ''' Callback from add button on customization toolbar ''' # Save data to journal and load it into the card database self.metadata['data_from_journal'] = json.dumps(self.data_from_journal) self._page.load_from_journal(self.data_from_journal) # Reinit the preview, et al. after add self.preview_image.hide() self._init_preview() self.image_id = None self.object_id = None self.letter_entry.set_text('') self.letter_entry.set_sensitive(False) self.add_button.set_sensitive(False) def _init_preview(self): ''' Set up customization toolbar, preview image ''' w = int(self._page._card_width) h = int(self._page._card_height) x = int(self._page._grid_x_offset + w + 12) y = int(self._page._grid_y_offset + 40) pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._images_path, '../drawing.png'), w, h) self.status.set_text( _('Please choose image and audio objects from the Journal.')) self._page._hide_cards() if not hasattr(self, 'preview_image'): self.preview_image = Sprite(self._page._sprites, 0, 0, pixbuf) else: self.preview_image.set_image(pixbuf) self.preview_image.move((x, y)) self.preview_image.set_layer(100) self._page._canvas.disconnect(self._page.button_press_event_id) self._page._canvas.disconnect(self._page.button_release_event_id) self._page.button_press_event_id = \ self._page._canvas.connect('button-press-event', self._preview_press_cb) self._page.button_release_event_id = \ self._page._canvas.connect('button-release-event', self._dummy_cb) def _customization_toolbar_cb(self, event): ''' Override toolbar button behavior ''' if self.custom_toolbar_button.is_expanded(): self._init_preview() else: if self.mode == 'letter': self._letter_cb() else: self._picture_cb() def _preview_press_cb(self, win, event): ''' Preview image was clicked ''' self._choose_image_from_journal_cb(None) def _dummy_cb(self, win, event): '''Does nothing''' return True def _choose_audio_from_journal_cb(self, event): ''' Create a chooser for audio objects ''' self.add_button.set_sensitive(False) self.letter_entry.set_sensitive(False) self.image_button.set_sensitive(False) self.sound_button.set_sensitive(False) self.audio_id = None chooser = ObjectChooser(what_filter=mime.GENERIC_TYPE_AUDIO) result = chooser.run() if result == Gtk.ResponseType.ACCEPT: jobject = chooser.get_selected_object() self.audio_id = str(jobject._object_id) self.image_button.set_sensitive(True) self.sound_button.set_sensitive(True) if self.image_id and self.audio_id: self.letter_entry.set_sensitive(True) self._page._canvas.disconnect(self._page.button_press_event_id) self._page.button_press_event_id = \ self._page._canvas.connect('button-press-event', self._play_audio_cb) def _play_audio_cb(self, win, event): ''' Preview audio ''' if self.audio_id: play_audio_from_file(datastore.get(self.audio_id).get_file_path()) def _choose_image_from_journal_cb(self, event): ''' Create a chooser for image objects ''' self.add_button.set_sensitive(False) self.letter_entry.set_sensitive(False) self.image_button.set_sensitive(False) self.sound_button.set_sensitive(False) self.image_id = None chooser = ObjectChooser(what_filter=mime.GENERIC_TYPE_IMAGE) result = chooser.run() if result == Gtk.ResponseType.ACCEPT: jobject = chooser.get_selected_object() self.image_id = str(jobject._object_id) x = self._page._grid_x_offset + self._page._card_width + 12 y = self._page._grid_y_offset + 40 w = self._page._card_width h = self._page._card_height pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( jobject.get_file_path(), w, h) self.preview_image.set_image(pixbuf) self.preview_image.move((x, y)) self.preview_image.set_layer(100) self.image_button.set_sensitive(True) self.sound_button.set_sensitive(True) if self.image_id and self.audio_id: self.letter_entry.set_sensitive(True) self._page._canvas.disconnect(self._page.button_press_event_id) self._page.button_press_event_id = \ self._page._canvas.connect('button-press-event', self._play_audio_cb) def _cleanup_preview(self): ''' No longer previewing, so hide image and clean up callbacks ''' if hasattr(self, 'preview_image'): self.preview_image.hide() self._page._canvas.disconnect(self._page.button_press_event_id) self._page._canvas.disconnect(self._page.button_release_event_id) self._page.button_press_event_id = \ self._canvas.connect("button-press-event", self._page._button_press_cb) self._page.button_release_event_id = \ self._canvas.connect("button-release-event", self._page._button_release_cb) def _letter_cb(self, event=None): ''' Click on card to hear the letter name ''' if self.custom_toolbar_button.is_expanded(): self.custom_toolbar_button.set_expanded(False) self._cleanup_preview() self.mode = 'letter' self.status.set_text( _('Click on the picture that matches the letter.')) if hasattr(self, '_page'): self._page.new_page() return def _picture_cb(self, event=None): ''' Click on card to hear the letter name ''' if self.custom_toolbar_button.is_expanded(): self.custom_toolbar_button.set_expanded(False) self._cleanup_preview() self.mode = 'picture' self.status.set_text( _('Click on the letter that matches the picture.')) if hasattr(self, '_page'): self._page.new_page() return def write_file(self, file_path): ''' Write status to the Journal ''' if not hasattr(self, '_page'): return self.metadata['page'] = str(self._page.current_card)
class Page(): ''' Pages from Infuse Reading method ''' def __init__(self, canvas, lessons_path, images_path, sounds_path, parent=None): ''' The general stuff we need to track ''' self._activity = parent self._lessons_path = lessons_path self._images_path = images_path self._sounds_path = sounds_path self._colors = profile.get_color().to_string().split(',') self._card_data = [] self._color_data = [] self._image_data = [] self._word_data = [] self.chosen_image = None # Starting from command line if self._activity is None: self._sugar = False self._canvas = canvas else: self._sugar = True self._canvas = canvas self._activity.show_all() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.connect("draw", self.__draw_cb) self.button_release_event_id = \ self._canvas.connect("button-release-event", self._button_release_cb) self.button_press_event_id = \ self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("key_press_event", self._keypress_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() self._card_height = int((self._height - GRID_CELL_SIZE) / YDIM) \ - GUTTER * 2 self._card_width = int(self._card_height * 4 / 3.) self._grid_x_offset = int( (self._width - XDIM * (self._card_width + GUTTER * 2)) / 2) self._grid_y_offset = 0 self._scale = self._card_width / 80. self._sprites = Sprites(self._canvas) self.current_card = 0 self._cards = [] self._pictures = [] self._press = None self._release = None self.timeout = None self.target = 0 self.answers = [] self._my_canvas = Sprite( self._sprites, 0, 0, svg_str_to_pixbuf(genblank( self._width, self._height, (self._colors[0], self._colors[0])))) self._my_canvas.type = 'background' self._smile = Sprite(self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._activity.activity_path, 'images', 'correct.png'), int(self._width / 2), int(self._height / 2))) self._frown = Sprite(self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._activity.activity_path, 'images', 'wrong.png'), int(self._width / 2), int(self._height / 2))) self.load_level(os.path.join(self._lessons_path, 'alphabet' + '.csv')) # Create the cards we'll need self._alpha_cards() self.load_from_journal(self._activity.data_from_journal) self.new_page() def _hide_feedback(self): if hasattr(self, '_smile'): self._smile.hide() self._frown.hide() def new_page(self): ''' Load a page of cards ''' if self.timeout is not None: GObject.source_remove(self.timeout) self.answers = [] for i in range(min(6, len(self._cards))): self.answers.append(0) self._hide_cards() self._hide_feedback() self.new_target() x = self._grid_x_offset + self._card_width + GUTTER * 3 y = self._grid_y_offset + GUTTER if self._activity.mode == 'letter': self._cards[self.target].move((x, y)) self._cards[self.target].set_layer(100) x = self._grid_x_offset + GUTTER y = self._grid_y_offset + self._card_height + GUTTER * 3 for i in range(len(self.answers)): alphabet = self._card_data[self.answers[i]][0] # select the sprite randomly s = choice(self._image_data[alphabet])[0] s.move((x, y)) s.set_layer(100) x += self._card_width + GUTTER * 2 if x > self._width - (self._card_width / 2): x = self._grid_x_offset + GUTTER y += self._card_height + GUTTER * 2 else: alphabet = self._card_data[self.target][0] self.chosen_image = choice(self._image_data[alphabet]) s = self.chosen_image[0] s.move((x, y)) s.set_layer(100) x = self._grid_x_offset + GUTTER y = self._grid_y_offset + self._card_height + GUTTER * 3 for i in range(len(self.answers)): self._cards[self.answers[i]].move((x, y)) self._cards[self.answers[i]].set_layer(100) x += self._card_width + GUTTER * 2 if x > self._width - (self._card_width / 2): x = self._grid_x_offset + GUTTER y += self._card_height + GUTTER * 2 def _hide_cards(self): if len(self._cards) > 0: for card in self._cards: card.hide() if len(self._pictures) > 0: for card in self._pictures: card.hide() def _alpha_cards(self): for card in self._card_data: self.current_card = self._card_data.index(card) # Two-tone cards add some complexity. if type(self._color_data[self.current_card][0]) == type([]): stroke = self._test_for_stroke() top = svg_str_to_pixbuf(generate_card( string=card[0], colors=[self._color_data[self.current_card][0][0], '#FFFFFF'], scale=self._scale, center=True)) bot = svg_str_to_pixbuf(generate_card( string=card[0], colors=[self._color_data[self.current_card][0][1], '#FFFFFF'], scale=self._scale, center=True)) # Where to draw the line h1 = 9 / 16. h2 = 1.0 - h1 bot.composite(top, 0, int(h1 * top.get_height()), top.get_width(), int(h2 * top.get_height()), 0, 0, 1, 1, GdkPixbuf.InterpType.NEAREST, 255) self._cards.append(Sprite(self._sprites, 0, 0, top)) else: stroke = self._test_for_stroke() if card[0] == 'ch': self._cards.append(Sprite(self._sprites, 0, 0, svg_str_to_pixbuf(generate_card( string='%s' % ( card[0].lower()), colors=[self._color_data[self.current_card][0], '#FFFFFF'], stroke=stroke, scale=self._scale, center=True)))) else: self._cards.append(Sprite(self._sprites, 0, 0, svg_str_to_pixbuf(generate_card( string='%s%s' % ( card[0].upper(), card[0].lower()), colors=[self._color_data[self.current_card][0], '#FFFFFF'], stroke=stroke, scale=self._scale, center=True)))) def _test_for_stroke(self): ''' Light colors get a surrounding stroke ''' if self._color_data[self.current_card][0][0:4] == '#FFF': return True else: return False def new_target(self): ''' Generate a new target and answer list ''' self._activity.status.set_text( _('Click on the card that corresponds to the sound.')) self.target = int(uniform(0, len(self._cards))) for i in range(min(6, len(self._cards))): self.answers[i] = int(uniform(0, len(self._cards))) for i in range(min(6, len(self._cards))): while self._bad_answer(i): self.answers[i] += 1 self.answers[i] %= len(self._cards) # Choose a random card and assign it to the target i = int(uniform(0, min(6, len(self._cards)))) self.answers[i] = self.target if self.timeout is not None: GObject.source_remove(self.timeout) self.timeout = GObject.timeout_add( 1000, self._play_target_sound, False) def _bad_answer(self, i): ''' Make sure answer is unique ''' if self.answers[i] == self.target: return True for j in range(min(6, len(self._cards))): if i == j: continue if self.answers[i] == self.answers[j]: return True return False def _play_target_sound(self, queue=True): if self._activity.mode == 'letter': play_audio_from_file(self._card_data[self.target][-1], queue) else: play_audio_from_file(self.chosen_image[-1], queue) self.timeout = None def _button_press_cb(self, win, event): ''' Either a card or list entry was pressed. ''' win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) self._press = spr self._release = None return True def _button_release_cb(self, win, event): ''' Play a sound or video or jump to a card as indexed in the list. ''' win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) self.current_card = -1 if self._activity.mode == 'letter': if spr in self._cards: self.current_card = self._cards.index(spr) play_audio_from_file(self._card_data[self.current_card][-1]) return for a in self._image_data: for b in self._image_data[a]: if spr == b[0]: play_audio_from_file(b[1]) for c in range(len(self._card_data)): if self._card_data[c][0] == a: self.current_card = c break break else: for a in self._image_data: for b in self._image_data[a]: if spr == b[0]: play_audio_from_file(b[1]) return if spr in self._cards: self.current_card = self._cards.index(spr) play_audio_from_file(self._card_data[self.current_card][-1]) if self.current_card == self.target: self._activity.status.set_text(_('Very good!')) self._play(True) if self.timeout is not None: GObject.source_remove(self.timeout) self.timeout = GObject.timeout_add(1000, self.new_page) else: self._activity.status.set_text(_('Please try again.')) self._play(False) self._play_target_sound() self.timeout = GObject.timeout_add(1000, self._hide_feedback) def _play(self, great): if great: self._smile.set_layer(1000) # play_audio_from_file(os.getcwd() + '/sounds/great.ogg') else: self._frown.set_layer(1000) # play_audio_from_file(os.getcwd() + '/sounds/bad.ogg') def _keypress_cb(self, area, event): ''' No keyboard shortcuts at the moment. Perhaps jump to the page associated with the key pressed? ''' return True def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True # Handle the expose-event by drawing def do_expose_event(self, event): # Create the cairo context cr = self._canvas.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): ''' Make a clean exit. ''' Gtk.main_quit() def invalt(self, x, y, w, h): ''' Mark a region for refresh ''' rectangle = Gdk.Rectangle() rectangle.x = x rectangle.y = y rectangle.width = w rectangle.height = h self._canvas.window.invalidate_rect(rectangle) def load_level(self, path): ''' Load a level (CSV) from path: letter, word, color, image, image sound, letter sound ''' self._card_data = [] # (letter, word, letter_sound_path) self._color_data = [] self._image_data = {} # {letter: [(Sprite, image_sound_path)...]} self._pictures = [] f = open(path) for line in f: if len(line) > 0 and line[0] not in '#\n': words = line.split(', ') self._card_data.append((words[0], words[1].replace('-', ', '), os.path.join(self._sounds_path, words[5]))) if words[2].count('#') > 1: self._color_data.append( [words[2].split('/')]) else: self._color_data.append( [words[2]]) imagefilename = words[3] imagepath = os.path.join(self._images_path, imagefilename) pixbuf = image_file_to_pixbuf(imagepath, self._card_width, self._card_height) s = Sprite(self._sprites, 0, 0, pixbuf) self._image_data[words[0]] = \ [(s, os.path.join(self._sounds_path, words[4]))] self._pictures.append(s) f.close() self._clear_all() self._cards = [] self._colored_letters_lower = [] self._colored_letters_upper = [] def load_from_journal(self, journal_data): for card in self._card_data: alphabet = card[0] if alphabet in journal_data: for images in journal_data[alphabet]: imagedataobject = datastore.get(images[0]) audiodataobject = datastore.get(images[1]) if imagedataobject and audiodataobject: imagepath = imagedataobject.get_file_path() pixbuf = image_file_to_pixbuf(imagepath, self._card_width, self._card_height) audiopath = audiodataobject.get_file_path() s = Sprite(self._sprites, 0, 0, pixbuf) self._image_data[alphabet].append((s, audiopath)) self._pictures.append(s) def _clear_all(self): ''' Hide everything so we can begin a new page. ''' self._hide_cards()
class Page(): ''' Pages from Infuse Reading method ''' def __init__(self, canvas, lessons_path, images_path, sounds_path, parent=None): ''' The general stuff we need to track ''' self._activity = parent self._lessons_path = lessons_path self._images_path = images_path self._sounds_path = sounds_path self._colors = profile.get_color().to_string().split(',') self._card_data = [] self._color_data = [] self._image_data = [] self._media_data = [] # (image sound, letter sound) self._word_data = [] self._deja_vu = [] # Starting from command line if self._activity is None: self._sugar = False self._canvas = canvas else: self._sugar = True self._canvas = canvas self._activity.show_all() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("button-release-event", self._button_release_cb) self._canvas.connect("key_press_event", self._keypress_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() self._card_width = int((self._width / XDIM)) - GUTTER * 2 self._card_height = int((self._height - GRID_CELL_SIZE) / YDIM) \ - GUTTER * 2 self._grid_x_offset = int( (self._width - XDIM * (self._card_width + GUTTER * 2)) / 2) self._grid_y_offset = 0 self._scale = self._card_width / 80. self._sprites = Sprites(self._canvas) self.current_card = 0 self._cards = [] self._pictures = [] self._press = None self._release = None self.timeout = None self._my_canvas = Sprite( self._sprites, 0, 0, svg_str_to_pixbuf(genblank( self._width, self._height, (self._colors[0], self._colors[0])))) self._my_canvas.type = 'background' self._smile = Sprite(self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._activity.activity_path, 'images', 'correct.png'), int(self._width / 2), int(self._height / 2))) self._frown = Sprite(self._sprites, int(self._width / 4), int(self._height / 4), GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._activity.activity_path, 'images', 'wrong.png'), int(self._width / 2), int(self._height / 2))) self.load_level(os.path.join(self._lessons_path, 'alphabet' + '.csv')) self.new_page() def _hide_feedback(self): if hasattr(self, '_smile'): self._smile.hide() self._frown.hide() def new_page(self, cardtype='alpha'): ''' Load a page of cards ''' if self.timeout is not None: GObject.source_remove(self.timeout) self._hide_cards() if cardtype == 'alpha': self._alpha_cards() else: self._image_cards() def _hide_cards(self): if len(self._cards) > 0: for card in self._cards: card.hide() if len(self._pictures) > 0: for card in self._pictures: card.hide() self._hide_feedback() def _image_cards(self): x = self._grid_x_offset + GUTTER y = self._grid_y_offset + GUTTER if len(self._pictures) > 0: for card in self._pictures: card.set_layer(100) return for card in self._card_data: self.current_card = self._card_data.index(card) imagefilename = self._image_data[self.current_card] imagepath = os.path.join(self._images_path, imagefilename) pixbuf = image_file_to_pixbuf(imagepath, self._card_width, self._card_height) self._pictures.append(Sprite(self._sprites, x, y, pixbuf)) x += self._card_width + GUTTER * 2 if x > self._width - (self._card_width / 2): x = self._grid_x_offset + GUTTER y += self._card_height + GUTTER * 2 def _alpha_cards(self): x = self._grid_x_offset + GUTTER y = self._grid_y_offset + GUTTER if len(self._cards) > 0: for card in self._cards: card.set_layer(100) return for card in self._card_data: self.current_card = self._card_data.index(card) # Two-tone cards add some complexity. if type(self._color_data[self.current_card][0]) == type([]): stroke = self._test_for_stroke() top = svg_str_to_pixbuf(generate_card( string='%s%s' % ( card[0].upper(), card[0].lower()), colors=[self._color_data[self.current_card][0][0], '#FFFFFF'], scale=self._scale, center=True)) bot = svg_str_to_pixbuf(generate_card( string='%s%s' % ( card[0].upper(), card[0].lower()), colors=[self._color_data[self.current_card][0][1], '#FFFFFF'], scale=self._scale, center=True)) # Where to draw the line h1 = 9 / 16. h2 = 1.0 - h1 bot.composite(top, 0, int(h1 * top.get_height()), top.get_width(), int(h2 * top.get_height()), 0, 0, 1, 1, GdkPixbuf.InterpType.NEAREST, 255) self._cards.append(Sprite(self._sprites, x, y, top)) else: stroke = self._test_for_stroke() self._cards.append(Sprite(self._sprites, x, y, svg_str_to_pixbuf(generate_card( string='%s%s' % ( card[0].upper(), card[0].lower()), colors=[self._color_data[self.current_card][0], '#FFFFFF'], stroke=stroke, scale=self._scale, center=True)))) x += self._card_width + GUTTER * 2 if x > self._width - (self._card_width / 2): x = self._grid_x_offset + GUTTER * 2 y += self._card_height + GUTTER * 2 def _test_for_stroke(self): ''' Light colors get a surrounding stroke ''' if self._color_data[self.current_card][0][0:4] == '#FFF': return True else: return False def new_target(self): self._activity.status.set_text( _('Click on the card that corresponds to the sound.')) self.target = int(uniform(0, len(self._cards))) # Don't repeat while self.target in self._deja_vu: self.target += 1 self.target %= len(self._cards) self._deja_vu.append(self.target) if len(self._deja_vu) == len(self._cards): self._deja_vu = [] if self.timeout is not None: GObject.source_remove(self.timeout) self.timeout = GObject.timeout_add(1000, self._play_target_sound) def _play_target_sound(self): _logger.debug(self._activity.mode) if self._activity.mode in ['letter', 'find by letter']: play_audio_from_file(os.path.join( self._sounds_path, self._media_data[self.target][1])) elif self._activity.mode == 'picture': play_audio_from_file(os.path.join( self._sounds_path, self._media_data[self.target][1])) GObject.timeout_add(1000, play_audio_from_file, os.path.join( self._sounds_path, self._media_data[self.target][0])) else: play_audio_from_file(os.path.join( self._sounds_path, self._media_data[self.target][0])) self.timeout = None def _button_press_cb(self, win, event): ''' Either a card or list entry was pressed. ''' win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) self._press = spr self._release = None return True def _button_release_cb(self, win, event): ''' Play a sound or video or jump to a card as indexed in the list. ''' win.grab_focus() x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) if spr is None: return if spr.type == 'background': return if spr in self._cards: self.current_card = self._cards.index(spr) elif spr in self._pictures: self.current_card = self._pictures.index(spr) if self._activity.mode in ['letter', 'picture']: self.target = self.current_card self._play_target_sound() elif self._activity.mode in ['find by letter', 'find by word']: if self.current_card == self.target: self._activity.status.set_text(_('Very good!')) self._play(True) if self.timeout is not None: GObject.source_remove(self.timeout) self.timeout = GObject.timeout_add(1000, self._correct_feedback) else: self._activity.status.set_text(_('Please try again.')) self._play(False) if self.timeout is not None: GObject.source_remove(self.timeout) self.timeout = GObject.timeout_add(1000, self._wrong_feedback) def _correct_feedback(self): self._hide_feedback() self.new_target() def _wrong_feedback(self): self._hide_feedback() self._play_target_sound() def _play(self, great): if great: self._smile.set_layer(1000) # play_audio_from_file(os.getcwd() + '/sounds/great.ogg') else: self._frown.set_layer(1000) # play_audio_from_file(os.getcwd() + '/sounds/bad.ogg') def _keypress_cb(self, area, event): ''' No keyboard shortcuts at the moment. Perhaps jump to the page associated with the key pressed? ''' return True def __draw_cb(self, canvas, cr): ''' Draw - Expose event ''' self._sprites.redraw_sprites(cr=cr) def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True # Handle the expose-event by drawing def do_expose_event(self, event): # Create the cairo context cr = self._canvas.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): ''' Make a clean exit. ''' Gtk.main_quit() def invalt(self, x, y, w, h): ''' Mark a region for refresh ''' rectangle = Gdk.Rectangle() rectangle.x = x rectangle.y = y rectangle.width = w rectangle.height = h self._canvas.window.invalidate_rect(rectangle) def load_level(self, path): ''' Load a level (CSV) from path: letter, word, color, image, image sound, letter sound ''' self._card_data = [] self._color_data = [] self._image_data = [] self._media_data = [] # (image sound, letter sound) f = open(path) for line in f: if len(line) > 0 and line[0] not in '#\n': words = line.split(', ') self._card_data.append([words[0], words[1].replace('-', ', ')]) if words[2].count('#') > 1: self._color_data.append( [words[2].split('/')]) else: self._color_data.append( [words[2]]) self._image_data.append(words[3]) self._media_data.append((words[4], words[5])) f.close() self._clear_all() self._cards = [] self._colored_letters_lower = [] self._colored_letters_upper = [] def _clear_all(self): ''' Hide everything so we can begin a new page. ''' self._hide_cards()