예제 #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 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()
예제 #4
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)
예제 #5
0
파일: page.py 프로젝트: leonardcj/AEIOU
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()
예제 #6
0
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)