Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/page.py
diff options
context:
space:
mode:
Diffstat (limited to 'page.py')
-rw-r--r--page.py364
1 files changed, 364 insertions, 0 deletions
diff --git a/page.py b/page.py
new file mode 100644
index 0000000..b2ac3ef
--- /dev/null
+++ b/page.py
@@ -0,0 +1,364 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2012 Walter Bender
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+import gobject
+import os
+import codecs
+from random import uniform
+
+from gettext import gettext as _
+
+from utils.play_audio import play_audio_from_file
+
+import logging
+_logger = logging.getLogger('iknowmyabcs-activity')
+
+try:
+ from sugar.graphics import style
+ GRID_CELL_SIZE = style.GRID_CELL_SIZE
+except ImportError:
+ GRID_CELL_SIZE = 0
+
+from genpieces import generate_card
+from utils.sprites import Sprites, Sprite
+
+
+XDIM = 3
+YDIM = 3
+GUTTER = 4
+
+
+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._card_data = []
+ self._color_data = []
+ self._image_data = []
+ self._media_data = [] # (image sound, letter sound)
+ self._word_data = []
+
+ # 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._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._width / 240.
+ 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 = [0, 0, 0, 0, 0, 0]
+
+ self.load_level(os.path.join(self._lessons_path, 'alphabet' + '.csv'))
+
+ # Create the cards we'll need
+ self._alpha_cards()
+ self._image_cards()
+
+ self.new_page()
+
+ def new_page(self):
+ ''' Load a page of cards '''
+ if self.timeout is not None:
+ gobject.source_remove(self.timeout)
+ self._hide_cards()
+ 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)):
+ self._pictures[self.answers[i]].move((x, y))
+ self._pictures[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
+ else:
+ self._pictures[self.target].move((x, y))
+ self._pictures[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)):
+ 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 _image_cards(self):
+ 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, 0, 0, pixbuf))
+
+ 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].lower(),
+ 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].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, gtk.gdk.INTERP_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=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(6):
+ self.answers[i] = self.target
+ for i in range(6):
+ while self._bad_answer(i):
+ self.answers[i] = int(uniform(0, len(self._cards)))
+ i = int(uniform(0, 6))
+ 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)
+
+ def _bad_answer(self, i):
+ ''' Make sure answer is unique '''
+ if self.answers[i] == self.target:
+ return True
+ for j in range(6):
+ if i == j:
+ continue
+ if self.answers[i] == self.answers[j]:
+ return True
+ return False
+
+ def _play_target_sound(self):
+ if self._activity.mode == 'letter':
+ play_audio_from_file(self, os.path.join(
+ self._sounds_path,
+ self._media_data[self.target][1]))
+ else:
+ play_audio_from_file(self, 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))
+ self.current_card = -1
+ 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.current_card == -1:
+ return
+
+ if self._activity.mode == 'letter':
+ if spr in self._cards:
+ play_audio_from_file(self, os.path.join(
+ self._sounds_path,
+ self._media_data[self.current_card][1]))
+ return
+ play_audio_from_file(self, os.path.join(
+ self._sounds_path,
+ self._media_data[self.current_card][0]))
+ else:
+ if spr in self._pictures:
+ play_audio_from_file(self, os.path.join(
+ self._sounds_path,
+ self._media_data[self.current_card][0]))
+ return
+ play_audio_from_file(self, os.path.join(
+ self._sounds_path,
+ self._media_data[self.current_card][1]))
+
+ if self.current_card == self.target:
+ self._activity.status.set_text(_('Very good!'))
+ 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_target_sound()
+
+ 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):
+ ''' 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 '''
+ 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 '''
+ self._card_data = []
+ self._color_data = []
+ self._image_data = []
+ self._media_data = [] # (image sound, letter sound)
+ f = codecs.open(path, encoding='utf-8')
+ 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()
+
+def svg_str_to_pixbuf(svg_string):
+ ''' Load pixbuf from SVG string. '''
+ pl = gtk.gdk.PixbufLoader('svg')
+ pl.write(svg_string)
+ pl.close()
+ pixbuf = pl.get_pixbuf()
+ return pixbuf
+
+
+def image_file_to_pixbuf(file_path, w, h):
+ ''' Load pixbuf from file '''
+ return gtk.gdk.pixbuf_new_from_file_at_size(file_path, int(w), int(h))
+