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 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 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._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()
class Page(): ''' Pages from Infuse Reading method ''' def __init__(self, canvas, lessons_path, images_path, sounds_path, level, 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._card_data = [] self._color_data = [] self._image_data = [] self._media_data = [] # (image sound, letter sound) self._word_data = [] _logger.debug(ALPHABET) # 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.set_flags(gtk.CAN_FOCUS) self._canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK) self._canvas.add_events(gtk.gdk.BUTTON_RELEASE_MASK) self._canvas.connect("expose-event", self._expose_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 = gtk.gdk.screen_width() self._height = gtk.gdk.screen_height() self._scale = self._width / 240. self._sprites = Sprites(self._canvas) self.page = 0 self._cards = [] self._letters = [] self._colored_letters_lower = [] self._colored_letters_upper = [] self._picture = None self._press = None self._release = None # self.gplay = None self.aplay = None self.vplay = None self._final_x = 0 self._lead = int(self._scale * 15) self._margin = int(self._scale * 3) self._left = self._margin # int((self._width - self._scale * 60) / 2.) self._x_pos = self._margin self._y_pos = self._lead self._offset = int(self._scale * 9) # self._width / 30.) self._looking_at_word_list = False self._my_canvas = Sprite( self._sprites, 0, 0, gtk.gdk.Pixmap(self._canvas.window, self._width, int(self._height * 2.75), -1)) self._my_canvas.set_layer(0) self._my_gc = self._my_canvas.images[0].new_gc() self._my_gc.set_foreground( self._my_gc.get_colormap().alloc_color('#FFFFFF')) for c in ALPHABET: self._letters.append( Sprite( self._sprites, 0, 0, svg_str_to_pixbuf( generate_card(string=c, colors=['#000000', '#000000'], font_size=12 * self._scale, background=False)))) self.load_level(os.path.join(self._lessons_path, level + '.csv')) self.new_page() def page_list(self): ''' Index into all the cards in the form of a list of phrases ''' # Already here? Then jump back to current page. if self._looking_at_word_list: self.new_page() return save_page = self.page self._clear_all() rect = gtk.gdk.Rectangle(0, 0, self._width, int(self._height * 2.75)) self._my_canvas.images[0].draw_rectangle(self._my_gc, True, *rect) self.invalt(0, 0, self._width, self._height) self._my_canvas.set_layer(1) self._x_pos, self._y_pos = self._margin, 0 # _logger.debug('%s' % (self.get_phrase_list())) for i, phrase in enumerate(self.get_phrase_list()): if i < len(self._colored_letters_lower): self.page = i else: self.page = -1 self._render_phrase(phrase, self._my_canvas, self._my_gc) self._x_pos = self._margin self._y_pos += self._lead self.page = save_page self._looking_at_word_list = True def get_phrase_list(self): phrase_list = [] # Each list is a collection of phrases, separated by spaces for i, card in enumerate(self._card_data): if card[0] == '': break if card[0] in 'AEIOUY': connector = ' ' + _('like') + ' ' else: connector = ' ' + _('as in') + ' ' phrase_list.append('(' + card[0].lower() + ')' + connector + card[1]) return phrase_list def new_page(self): ''' Load a new page: a card and a message ''' if self.page == len(self._word_data): self.page = 0 if self._sugar: if self.page < len(self._card_data): if hasattr(self._activity, 'sounds_combo'): self._activity.sounds_combo.set_active(self.page) if self.page == len(self._cards) and \ self.page < len(self._card_data): # Two-tone cards add some complexity. if type(self._color_data[self.page][0]) == type([]): stroke = self._test_for_stroke() top = svg_str_to_pixbuf( generate_card( string=self._card_data[self.page][0].lower(), colors=[self._color_data[self.page][0][0], '#FFFFFF'], scale=self._scale, center=True)) bot = svg_str_to_pixbuf( generate_card( string=self._card_data[self.page][0].lower(), colors=[self._color_data[self.page][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, gtk.gdk.INTERP_NEAREST, 255) self._cards.append( Sprite( self._sprites, # self._left, int(self._width - 320 * self._scale / 2.5), GRID_CELL_SIZE, top)) top = svg_str_to_pixbuf( generate_card( string=self._card_data[self.page][0][0].lower(), colors=[self._color_data[self.page][0][0], '#FFFFFF'], font_size=12 * self._scale, background=False, stroke=stroke)) bot = svg_str_to_pixbuf( generate_card( string=self._card_data[self.page][0][0].lower(), colors=[self._color_data[self.page][0][1], '#FFFFFF'], font_size=12 * self._scale, background=False, stroke=stroke)) bot.composite(top, 0, int(h1 * top.get_height()), top.get_width(), int(h2 * top.get_height()), 0, 0, 1, 1, gtk.gdk.INTERP_NEAREST, 255) self._colored_letters_lower.append( Sprite(self._sprites, 0, 0, top)) top = svg_str_to_pixbuf( generate_card( string=self._card_data[self.page][0][0].upper(), colors=[self._color_data[self.page][0][0], '#FFFFFF'], font_size=12 * self._scale, background=False, stroke=stroke)) bot = svg_str_to_pixbuf( generate_card( string=self._card_data[self.page][0][0].upper(), colors=[self._color_data[self.page][0][1], '#FFFFFF'], font_size=12 * self._scale, background=False, stroke=stroke)) bot.composite(top, 0, int(h1 * top.get_height()), top.get_width(), int(h2 * top.get_height()), 0, 0, 1, 1, gtk.gdk.INTERP_NEAREST, 255) self._colored_letters_upper.append( Sprite(self._sprites, 0, 0, top)) else: stroke = self._test_for_stroke() self._cards.append( Sprite( self._sprites, int(self._width - 320 * self._scale / 2.5), GRID_CELL_SIZE, svg_str_to_pixbuf( generate_card( string=self._card_data[self.page][0].lower(), colors=[ self._color_data[self.page][0], '#FFFFFF' ], stroke=stroke, scale=self._scale, center=True)))) self._colored_letters_lower.append( Sprite( self._sprites, 0, 0, svg_str_to_pixbuf( generate_card( string=self._card_data[self.page][0].lower(), colors=[ self._color_data[self.page][0], '#FFFFFF' ], font_size=12 * self._scale, background=False, stroke=stroke)))) self._colored_letters_upper.append( Sprite( self._sprites, 0, 0, svg_str_to_pixbuf( generate_card( string=self._card_data[self.page][0].upper(), colors=[ self._color_data[self.page][0], '#FFFFFF' ], font_size=12 * self._scale, background=False, stroke=stroke)))) self._hide_cards() if self.page >= len(self._card_data): self.read() else: self._load_card() self._looking_at_word_list = False def _test_for_stroke(self): ''' Light colors get a surrounding stroke ''' # TODO: better value test if self._color_data[self.page][0][0:4] == '#FFF': return True else: return False def _load_card(self): ''' a card is a sprite and a message. ''' vadj = self._activity.scrolled_window.get_vadjustment() vadj.set_value(0) self._activity.scrolled_window.set_vadjustment(vadj) self._cards[self.page].set_layer(2) self._x_pos = self._margin self._y_pos = self._cards[self.page].rect.y + \ self._cards[self.page].images[0].get_height() + self._lead rect = gtk.gdk.Rectangle(0, 0, self._width, int(self._height * 2.5)) self._my_canvas.images[0].draw_rectangle(self._my_gc, True, *rect) self.invalt(0, 0, self._width, int(self._height * 2.5)) text = self._card_data[self.page][1] ''' for phrase in text.split('\n'): self._x_pos = self._margin * 2 self._render_phrase(phrase, self._my_canvas, self._my_gc) # self._x_pos = self._margin self._y_pos += self._lead ''' self._x_pos = self._margin * 2 self._render_phrase(text, self._my_canvas, self._my_gc) self._x_pos = self._margin * 2 self._y_pos += self._lead self._render_phrase(text.upper(), self._my_canvas, self._my_gc) # Is there a picture for this page? imagefilename = self._image_data[self.page] if len(imagefilename) > 4 and \ os.path.exists(os.path.join(self._images_path, imagefilename)): pixbuf = image_file_to_pixbuf( os.path.join(self._images_path, imagefilename), self._scale / 4) if self._picture is None: self._picture = Sprite( self._sprites, # int(self._width - 320 * self._scale / 2.5), self._left, GRID_CELL_SIZE, pixbuf) else: self._picture.images[0] = pixbuf self._picture.set_layer(2) elif self._picture is not None: self._picture.set_layer(0) # Hide all the letter sprites. for l in self._letters: l.set_layer(0) for l in self._colored_letters_lower: l.set_layer(0) for l in self._colored_letters_upper: l.set_layer(0) self._my_canvas.set_layer(0) def _strip(self, word, tokens): whole = word for t in tokens: parts = whole.split(t) whole = '' for p in parts: whole += p return whole def reload(self): ''' Switch back and forth between reading and displaying a card. ''' if self.page < len(self._card_data): self._load_card() else: self.read() if self._sugar: self._activity.status.set_label('') def read(self): ''' Read a word list ''' self._clear_all() rect = gtk.gdk.Rectangle(0, 0, self._width, int(self._height * 2.75)) self._my_canvas.images[0].draw_rectangle(self._my_gc, True, *rect) self.invalt(0, 0, self._width, self._height) self._my_canvas.set_layer(1) my_list = self._word_data[self.page].split('/') self._x_pos, self._y_pos = self._margin, self._lead for phrase in my_list: self._render_phrase(phrase, self._my_canvas, self._my_gc) # Put a longer space between each phrase self._x_pos += self._offset if self._x_pos > self._width * 7 / 8.0: self._x_pos, self._y_pos = self._increment_xy(self._y_pos) self._looking_at_word_list = False def test(self): ''' Generate a randomly ordered list of phrases. ''' self._clear_all() rect = gtk.gdk.Rectangle(0, 0, self._width, int(self._height * 2.75)) self._my_canvas.images[0].draw_rectangle(self._my_gc, True, *rect) self.invalt(0, 0, self._width, self._height) self._my_canvas.set_layer(1) phrase_list = self._test_data.split('/') list_length = len(phrase_list) for i in range(list_length): # Randomize the phrase order. j = randrange(list_length - i) tmp = phrase_list[i] phrase_list[i] = phrase_list[list_length - 1 - j] phrase_list[list_length - 1 - j] = tmp self._x_pos, self._y_pos = self._margin, self._lead for phrase in phrase_list: self._render_phrase(phrase, self._my_canvas, self._my_gc) self._x_pos, self._y_pos = self._increment_xy(self._y_pos) if self._y_pos > self._height * 2 - self._lead: break self._looking_at_word_list = False def _render_phrase(self, phrase, canvas, gc): ''' Draw an individual phase onto the canvas. ''' # Either we are rendering complete lines or phrases lines = phrase.split('\\') if len(lines) == 1: # split a phrase into words words = phrase.split() for word in words: # Will line run off the right edge? if self._x_pos + len(word) * self._offset > \ self._width - self._margin: self._x_pos, self._y_pos = self._increment_xy(self._y_pos) self._draw_a_word(word, canvas, gc) else: # render each line as a unit for line in lines: self._draw_a_word(line, canvas, gc) self._x_pos, self._y_pos = self._increment_xy(self._y_pos) def _letter_match(self, word, char, n): ''' Does the current position in the word match the letters on the card for the current page? ''' if not self.page < len(self._card_data): return False if n == 1 and word[char] == self._card_data[self.page][0][0]: return True if len(word) - char < n: return False if word[char:char + n] == self._card_data[self.page][0]: return True return False def _draw_a_word(self, word, canvas, gc): ''' Process each character in the word ''' # some colored text is multiple characters if self.page != -1 and self.page < len(self._card_data): n = len(self._card_data[self.page][0]) else: n = 1 skip_count = 0 # Draw letters enclosed in () in color draw_in_color = False for char in range(len(word)): if skip_count > 0: skip_count -= 1 elif word[char] == '(' and not draw_in_color: draw_in_color = True elif word[char] == ')' and draw_in_color: draw_in_color = False elif draw_in_color and self.page != -1: if word[char].islower(): self._draw_pixbuf( self._colored_letters_lower[self.page].images[0], self._x_pos, self._y_pos, canvas, gc) kern_char = word[char].lower() else: self._draw_pixbuf( self._colored_letters_upper[self.page].images[0], self._x_pos, self._y_pos, canvas, gc) kern_char = word[char].upper() if n > 1: skip_count = n - 1 else: if word[char] in ALPHABET: i = ALPHABET.index(word[char]) self._draw_pixbuf(self._letters[i].images[0], self._x_pos, self._y_pos, canvas, gc) kern_char = word[char] if word[char] not in '()': if kern_char in KERN: self._x_pos += self._offset * KERN[kern_char] else: self._x_pos += self._offset self._final_x = self._x_pos # Put a space after each word if self._x_pos > self._margin: self._x_pos += int(self._offset / 1.6) def _draw_pixbuf(self, pixbuf, x, y, canvas, gc): ''' Draw a pixbuf onto the canvas ''' w = pixbuf.get_width() h = pixbuf.get_height() canvas.images[0].draw_pixbuf(gc, pixbuf, 0, 0, int(x), int(y)) self.invalt(x, y, w, h) def _increment_xy(self, y): ''' Increment the xy postion for drawing the next phrase, with left-justified alignment. ''' return 10, self._lead + y 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()) if self._looking_at_word_list: self._goto_page = int(y * 1.0 / self._lead) else: 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() if self._looking_at_word_list: self._looking_at_word_list = False if self._goto_page > self.page: for _i in range(self._goto_page - self.page): self.page += 1 self.new_page() else: self.page = self._goto_page self.new_page() else: x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) if spr == self._picture: if self.page < len(self._card_data): if len(self._media_data[self.page][0]) > 4 and \ os.path.exists(os.path.join( self._sounds_path, self._media_data[self.page][0])): play_audio_from_file( self, os.path.join(self._sounds_path, self._media_data[self.page][0])) elif spr == self._cards[self.page]: if self.page < len(self._card_data): if os.path.exists( os.path.join(self._sounds_path, self._media_data[self.page][1])): play_audio_from_file( self, os.path.join(self._sounds_path, self._media_data[self.page][1])) ''' elif os.path.exists(os.path.join( os.path.abspath('.'), 'videos', self._media_data[self.page])): play_movie_from_file(self, os.path.join( os.path.abspath('.'), 'videos', self._media_data[self.page]), self._width - 320 * self._scale / 2.5, GRID_CELL_SIZE + 15 * self._scale, 80 * self._scale, 60 * self._scale) ''' 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 _expose_cb(self, win, event): ''' When asked, we need to refresh the screen. ''' self._sprites.redraw_sprites() return True 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 ''' self._canvas.window.invalidate_rect( gtk.gdk.Rectangle(int(x), int(y), int(w), int(h)), False) def load_level(self, path): ''' Load a level (CSV) from path: letter, word, color, image, image sound, letter sound, text ''' self._card_data = [] self._color_data = [] self._image_data = [] self._media_data = [] # (image sound, letter sound) self._word_data = [] f = codecs.open(path, encoding='utf-8') for line in f: if len(line) > 0 and line[0] not in '#\n': words = line.split(', ') if not words[0] in '-+': 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])) if words[0] == '+': self._test_data = words[6] else: self._word_data.append(words[6]) 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() if self._picture is not None: self._picture.set_layer(0) def _hide_cards(self): ''' Hide any cards that might be around. ''' for card in self._cards: card.set_layer(0)