Пример #1
0
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()
Пример #2
0
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()
Пример #3
0
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)
Пример #4
0
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()
Пример #5
0
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()