diff options
author | Walter Bender <walter.bender@gmail.com> | 2011-10-18 18:32:57 (GMT) |
---|---|---|
committer | Walter Bender <walter.bender@gmail.com> | 2011-10-18 18:32:57 (GMT) |
commit | b0aa1047d2fc578bdb555622333dbd977068584b (patch) | |
tree | f7505c0453527705022afd61787b581427c72f0b | |
parent | 03fc20cc788bc100e5b7aaea8f604a6283f3fd8f (diff) |
refactoring to make code easier to read
-rw-r--r-- | FractionBounceActivity.py | 175 | ||||
-rw-r--r-- | animate.py | 711 | ||||
-rw-r--r-- | ball.py | 143 | ||||
-rw-r--r-- | bar.py | 115 | ||||
-rw-r--r-- | bounce.py | 402 | ||||
-rw-r--r-- | svg_utils.py | 126 | ||||
-rw-r--r-- | toolbar_utils.py | 107 | ||||
-rw-r--r-- | utils.py | 54 |
8 files changed, 1364 insertions, 469 deletions
diff --git a/FractionBounceActivity.py b/FractionBounceActivity.py index 819698c..bc0ba92 100644 --- a/FractionBounceActivity.py +++ b/FractionBounceActivity.py @@ -24,8 +24,6 @@ if HAS_TOOLBARBOX: from sugar.graphics.toolbarbox import ToolbarButton from sugar.activity.widgets import ActivityToolbarButton from sugar.activity.widgets import StopButton -from sugar.graphics.radiotoolbutton import RadioToolButton -from sugar.graphics.toolbutton import ToolButton import telepathy from dbus.service import signal @@ -38,22 +36,12 @@ from gettext import gettext as _ import logging _logger = logging.getLogger('fractionbounce-activity') -from bounce import Bounce, svg_str_to_pixbuf, generate_xo_svg +from toolbar_utils import image_factory, separator_factory, \ + label_factory, radio_factory, button_factory, entry_factory +from utils import json_load, json_dump +from svg_utils import svg_str_to_pixbuf, generate_xo_svg -from StringIO import StringIO -try: - USING_JSON_READWRITE = False - import json - json.dumps - from json import load as jload - from json import dump as jdump -except (ImportError, AttributeError): - try: - import simplejson as json - from simplejson import load as jload - from simplejson import dump as jdump - except (ImportError, AttributeError): - USING_JSON_READWRITE = True +from bounce import Bounce SERVICE = 'org.sugarlabs.FractionBounceActivity' @@ -61,124 +49,6 @@ IFACE = SERVICE PATH = '/org/augarlabs/FractionBounceActivity' -def json_load(text): - """ Load JSON data using what ever resources are available. """ - if USING_JSON_READWRITE is True: - listdata = json.read(text) - else: - # strip out leading and trailing whitespace, nulls, and newlines - io = StringIO(text) - try: - listdata = jload(io) - except ValueError: - # assume that text is ascii list - listdata = text.split() - for i, value in enumerate(listdata): - listdata[i] = int(value) - return listdata - - -def json_dump(data): - """ Save data using available JSON tools. """ - if USING_JSON_READWRITE is True: - return json.write(data) - else: - _io = StringIO() - jdump(data, _io) - return _io.getvalue() - - -def _entry_factory(default_string, toolbar, tooltip='', max=3): - """ Factory for adding a text box to a toolbar """ - entry = gtk.Entry() - entry.set_text(default_string) - if hasattr(entry, 'set_tooltip_text'): - entry.set_tooltip_text(tooltip) - entry.set_width_chars(max) - entry.show() - toolitem = gtk.ToolItem() - toolitem.add(entry) - toolbar.insert(toolitem, -1) - toolitem.show() - return entry - - -def _button_factory(icon_name, toolbar, callback, cb_arg=None, tooltip=None, - accelerator=None): - """Factory for making toolbar buttons""" - button = ToolButton(icon_name) - button.set_tooltip(tooltip) - button.props.sensitive = True - if accelerator is not None: - button.props.accelerator = accelerator - if cb_arg is not None: - button.connect('clicked', callback, cb_arg) - else: - button.connect('clicked', callback) - if hasattr(toolbar, 'insert'): # the main toolbar - toolbar.insert(button, -1) - else: # or a secondary toolbar - toolbar.props.page.insert(button, -1) - button.show() - return button - - -def _radio_factory(button_name, toolbar, callback, cb_arg=None, tooltip=None, - group=None): - ''' Add a radio button to a toolbar ''' - button = RadioToolButton(group=group) - button.set_named_icon(button_name) - if callback is not None: - if cb_arg is None: - button.connect('clicked', callback) - else: - button.connect('clicked', callback, cb_arg) - if hasattr(toolbar, 'insert'): # Add button to the main toolbar... - toolbar.insert(button, -1) - else: # ...or a secondary toolbar. - toolbar.props.page.insert(button, -1) - button.show() - if tooltip is not None: - button.set_tooltip(tooltip) - return button - - -def _label_factory(toolbar, label_text, width=None): - ''' Factory for adding a label to a toolbar ''' - label = gtk.Label(label_text) - label.set_line_wrap(True) - if width is not None: - label.set_size_request(width, -1) # doesn't work on XOs - label.show() - toolitem = gtk.ToolItem() - toolitem.add(label) - toolbar.insert(toolitem, -1) - toolitem.show() - return label - - -def _separator_factory(toolbar, expand=False, visible=True): - ''' add a separator to a toolbar ''' - separator = gtk.SeparatorToolItem() - separator.props.draw = visible - separator.set_expand(expand) - toolbar.insert(separator, -1) - separator.show() - - -def _image_factory(image, toolbar, tooltip=None): - ''' Add an image to the toolbar ''' - img = gtk.Image() - img.set_from_pixbuf(image) - img_tool = gtk.ToolItem() - img_tool.add(img) - if tooltip is not None: - img.set_tooltip_text(tooltip) - toolbar.insert(img_tool, -1) - img_tool.show() - return img - - class FractionBounceActivity(activity.Activity): def __init__(self, handle): @@ -236,7 +106,7 @@ class FractionBounceActivity(activity.Activity): custom_toolbar_button.show() self._load_standard_buttons(self.toolbar) - _separator_factory(self.toolbar, expand=True, visible=False) + separator_factory(self.toolbar, expand=True, visible=False) stop_button = StopButton(self) stop_button.props.accelerator = _('<Ctrl>Q') @@ -257,29 +127,29 @@ class FractionBounceActivity(activity.Activity): def _load_standard_buttons(self, toolbar): ''' Load buttons onto whichever toolbar we are using ''' - self.fraction_button = _radio_factory('fraction', toolbar, - self._fraction_cb, - tooltip=_('fractions'), - group=None) - self.percent_button = _radio_factory('percent', toolbar, - self._percent_cb, - tooltip=_('percents'), - group=self.fraction_button) - self.player = _image_factory( + self.fraction_button = radio_factory('fraction', toolbar, + self._fraction_cb, + tooltip=_('fractions'), + group=None) + self.percent_button = radio_factory('percent', toolbar, + self._percent_cb, + tooltip=_('percents'), + group=self.fraction_button) + self.player = image_factory( svg_str_to_pixbuf(generate_xo_svg(scale=0.8, colors=['#282828', '#000000'])), toolbar, tooltip=self.nick) - _separator_factory(toolbar, expand=False, visible=True) - self.challenge = _label_factory(toolbar, _("Click the ball to start.")) + separator_factory(toolbar, expand=False, visible=True) + self.challenge = label_factory(toolbar, _("Click the ball to start.")) def _load_custom_buttons(self, toolbar): ''' Entry fields and buttons for adding custom fractions ''' - self.numerator = _entry_factory('', toolbar, tooltip=_('numerator')) - _label_factory(toolbar, ' / ') - self.denominator = _entry_factory('', toolbar, + self.numerator = entry_factory('', toolbar, tooltip=_('numerator')) + label_factory(toolbar, ' / ') + self.denominator = entry_factory('', toolbar, tooltip=_('denominator')) - _separator_factory(toolbar, expand=False, visible=False) - self.enter_button = _button_factory('list-add', toolbar, + separator_factory(toolbar, expand=False, visible=False) + self.enter_button = button_factory('list-add', toolbar, self._add_fraction_cb, tooltip=_('add new fraction'), accelerator='Return') @@ -348,6 +218,7 @@ class FractionBounceActivity(activity.Activity): _logger.debug('unpause it') self.challenge.set_label(_('Click the ball to continue')) ''' + # Collaboration-related methods def _setup_presence_service(self): diff --git a/animate.py b/animate.py new file mode 100644 index 0000000..6648821 --- /dev/null +++ b/animate.py @@ -0,0 +1,711 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2011, Walter Bender, Paulina Clares, Chris Rowe +# +# 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. + +# The challenges are arrays: +# [a fraction to display on the ball, +# the number of segments in the bar, +# the number of times this challenge has been played] + +EASY = [['1/2', 2, 0], ['1/3', 3, 0], ['1/4', 4, 0], + ['2/4', 4, 0], ['2/3', 3, 0], ['3/4', 4, 0]] +MEDIUM = [['1/6', 6, 0], ['2/6', 6, 0], ['3/6', 6, 0], + ['4/6', 6, 0], ['5/6', 6, 0], + ['1/8', 8, 0], ['2/8', 8, 0], ['3/8', 8, 0], + ['4/8', 8, 0], ['5/8', 8, 0], ['6/8', 8, 0], + ['7/8', 8, 0]] +HARD = [['1/12', 12, 0], ['2/12', 12, 0], ['3/12', 12, 0], + ['4/12', 12, 0], ['3/12', 12, 0], ['6/12', 12, 0], + ['7/12', 12, 0], ['8/12', 12, 0], ['9/12', 12, 0], + ['10/12', 12, 0], ['11/12', 12, 0], + ['1/5', 10, 0], ['2/5', 10, 0], ['3/5', 10, 0], + ['4/5', 10, 0], + ['1/10', 10, 0], ['2/10', 10, 0], ['3/10', 10, 0], + ['4/10', 10, 0], ['5/10', 10, 0], ['6/10', 10, 0], + ['7/10', 10, 0], ['8/10', 10, 0], ['9/10', 10, 0], + ['1/16', 4, 0], ['2/16', 4, 0], ['3/16', 4, 0], + ['4/16', 4, 0], ['5/16', 4, 0], ['6/16', 4, 0], + ['7/16', 4, 0], ['8/16', 4, 0], ['9/16', 4, 0], + ['10/16', 4, 0], ['11/16', 4, 0], ['12/16', 4, 0], + ['13/16', 4, 0], ['14/16', 4, 0], ['15/16', 4, 0]] +EXPERT = 100 # after some number of correct answers, don't segment the bar +BAR_HEIGHT = 25 +STEPS = 100. # number of time steps per bounce rise and fall +STEP_PAUSE = 50 # milliseconds between steps +BOUNCE_PAUSE = 3000 # milliseconds between bounces +DX = 10 # starting step size for horizontal movement +DDX = 1.25 # acceleration during keypress +ANIMATION = {10: (0, 1), 15: (1, 2), 20: (2, 1), 25: (1, 2), 30: (2, 1), + 35: (1, 2), 40: (2, 3), 45: (3, 4), 50: (4, 3), 55: (3, 4), + 60: (4, 3), 65: (3, 4), 70: (4, 5), 75: (5, 6), 80: (6, 5), + 85: (5, 6), 90: (6, 7)} +ACCELEROMETER_DEVICE = '/sys/devices/platform/lis3lv02d/position' +CRASH = 'crash.ogg' # wrong answer sound +LAUGH = 'bottle.ogg' # correct answer sound +BUBBLES = 'bubbles.ogg' # Easter Egg sound +# Easter Egg animation graphics +TRANSFORMS = ['<g>', + '<g transform="matrix(0.83251323,0.17764297,-0.48065174, \ +1.0074555,27.969568,-8.7531294)">', + '<g transform="matrix(-0.83251323,0.17764297,0.48065174, \ +1.0074555,57.030432,-8.7531294)">', + '<g transform="matrix(0.57147881,-0.357582,-0.32994345, \ +0.96842187,32.525583,15.686767)">', + '<g transform="matrix(-0.57147881,-0.357582,0.32994345, \ +0.96842187,52.474417,15.686767)">', + '<g transform="matrix(0.39557109,-0.57943591,-0.22838308, \ +0.86196565,35.595823,29.733447)">', + '<g transform="matrix(-0.39557109,-0.57943591,0.22838308, \ +0.86196565,49.404177,29.733447)">', + '<g transform="matrix(1,0,0,0.08410415,0,73.873449)">'] +PUNCTURE = \ +' <g \ + transform="translate(2.5316175, -8)">\ + <path \ + d="m 33.19688,68.961518 c 3.900378,7.602149 10.970659,7.634416 \ +13.708164,7.432138"\ + style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;\ +stroke-miterlimit:4" />\ + <path \ + d="m 33.031721,77.05429 c 8.199837,0.123635 12.819227,-7.570626 \ +12.882372,-8.423089" \ + style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;\ +stroke-miterlimit:4" />\ + </g>' +AIR = \ +' <g \ + transform="matrix(0.63786322,0,0,0.64837179,17.379518,68.534252)"> \ + <path \ + d="M 39.054054,1.75 C 37.741313,16.51834 25.926641,23.082047 \ +25.926641,23.082047 l 0,0" \ + style="fill:none;stroke:#0ac9fb;stroke-width:6.0;stroke-linecap:round;\ +stroke-miterlimit:4;" />\ + <path \ + d="m 39.710425,1.75 c 1.312741,14.76834 13.127413,21.332047 \ +13.127413,21.332047 l 0,0" \ + style="fill:none;stroke:#0ac9fb;stroke-width:6.0;stroke-linecap:round;\ +stroke-miterlimit:4" />\ + <path \ + d="m 39.054054,1.75 c 1.969112,3.281854 -0.656371,20.347491 \ +-0.656371,20.347491 l 0,0" \ + style="fill:none;stroke:#0ac9fb;stroke-width:6.0;stroke-linecap:round;\ +stroke-miterlimit:4" />\ + </g>' + +import gtk +from random import uniform +import os +import gobject + +from play_audio import play_audio_from_file + +from gettext import gettext as _ + +import logging +_logger = logging.getLogger('fractionbounce-activity') + +try: + from sugar.graphics import style + GRID_CELL_SIZE = style.GRID_CELL_SIZE +except ImportError: + GRID_CELL_SIZE = 0 + +from sprites import Sprites, Sprite + + +def generate_xo_svg(scale=1.0, colors=["#C0C0C0", "#282828"]): + ''' Returns an SVG string representing an XO image ''' + return _svg_header(55, 55, scale) + \ + _svg_xo(colors[0], colors[1]) + \ + _svg_footer() + + +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 _svg_rect(w, h, rx, ry, x, y, fill, stroke): + ''' Returns an SVG rectangle ''' + svg_string = ' <rect\n' + svg_string += ' width="%f"\n' % (w) + svg_string += ' height="%f"\n' % (h) + svg_string += ' rx="%f"\n' % (rx) + svg_string += ' ry="%f"\n' % (ry) + svg_string += ' x="%f"\n' % (x) + svg_string += ' y="%f"\n' % (y) + svg_string += _svg_style('fill:%s;stroke:%s;' % (fill, stroke)) + return svg_string + + +def _svg_xo(fill, stroke, width=3.5): + ''' Returns XO icon graphic ''' + svg_string = '<path d="M33.233,35.1l10.102,10.1c0.752,\ +0.75,1.217,1.783,1.217,2.932\ + c0,2.287-1.855,4.143-4.146,4.143c-1.145,0-2.178-0.463-2.932-1.211L27.372,\ +40.961l-10.1,10.1c-0.75,0.75-1.787,1.211-2.934,1.211\ + c-2.284,0-4.143-1.854-4.143-4.141c0-1.146,0.465-2.184,\ +1.212-2.934l10.104-10.102L11.409,24.995\ + c-0.747-0.748-1.212-1.785-1.212-2.93c0-2.289,1.854-4.146,4.146-4.146c1.143,\ +0,2.18,0.465,2.93,1.214l10.099,10.102l10.102-10.103\ + c0.754-0.749,1.787-1.214,2.934-1.214c2.289,0,4.146,1.856,4.146,4.145c0,\ +1.146-0.467,2.18-1.217,2.932L33.233,35.1z" ' + svg_string += _svg_style('fill:%s;stroke:%s;stroke_width:%f' % (fill, + stroke, + width)) + svg_string += '\n<circle cx="27.371" cy="10.849" r="8.122" ' + svg_string += _svg_style('fill:%s;stroke:%s;stroke_width:%f' % (fill, + stroke, + width)) + return svg_string + + +def _svg_header(w, h, scale, hscale=1.0): + ''' Returns SVG header; some beads are elongated (hscale) ''' + svg_string = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n' + svg_string += '<!-- Created with Python -->\n' + svg_string += '<svg\n' + svg_string += ' xmlns:svg="http://www.w3.org/2000/svg"\n' + svg_string += ' xmlns="http://www.w3.org/2000/svg"\n' + svg_string += ' version="1.0"\n' + svg_string += ' width="%f"\n' % (w * scale) + svg_string += ' height="%f">\n' % (h * scale * hscale) + svg_string += '<g\n transform="matrix(%f,0,0,%f,0,0)">\n' % ( + scale, scale) + return svg_string + + +def _svg_footer(): + ''' Returns SVG footer ''' + svg_string = '</g>\n' + svg_string += '</svg>\n' + return svg_string + + +def _svg_style(extras=''): + ''' Returns SVG style for shape rendering ''' + return 'style="%s"/>\n' % (extras) + + +def svg_from_file(pathname): + ''' Read SVG string from a file ''' + f = file(pathname, 'r') + svg = f.read() + f.close() + return(svg) + + +def _extract_svg_payload(fd): + """Returns everything between <svg ...> and </svg>""" + payload = '' + looking_for_start_svg_token = True + looking_for_close_token = True + looking_for_end_svg_token = True + for line in fd: + if looking_for_start_svg_token: + if line.find('<svg') < 0: + continue + looking_for_start_svg_token = False + line = line.split('<svg', 1)[1] + if looking_for_close_token: + if line.find('>') < 0: + continue + looking_for_close_token = False + line = line.split('>', 1)[1] + if looking_for_end_svg_token: + if line.find('</svg>') < 0: + payload += line + continue + payload += line.split('</svg>')[0] + break + return payload + + +class Bounce(): + ''' The Bounce class is used to define the ball and the user + interaction. ''' + + def __init__(self, canvas, path, parent=None): + ''' Initialize the canvas and set up the callbacks. ''' + self.activity = parent + + if parent is None: # Starting from command line + self.sugar = False + self.canvas = canvas + else: # Starting from Sugar + self.sugar = True + self.canvas = canvas + parent.show_all() + + self.canvas.grab_focus() + + if os.path.exists(ACCELEROMETER_DEVICE): + self.accelerometer = True + else: + self.accelerometer = False + + 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.add_events(gtk.gdk.POINTER_MOTION_MASK) + self.canvas.add_events(gtk.gdk.KEY_PRESS_MASK) + self.canvas.add_events(gtk.gdk.KEY_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.canvas.connect('key_release_event', self._keyrelease_cb) + self.width = gtk.gdk.screen_width() + self.height = gtk.gdk.screen_height() - GRID_CELL_SIZE + self.sprites = Sprites(self.canvas) + self.scale = gtk.gdk.screen_height() / 900.0 + self.timeout = None + + self.buddies = [] # used for sharing + self.my_turn = False + self.select_a_fraction = False + + self.easter_egg = int(uniform(1, 100)) + + # Find paths to sound files + self.path_to_success = os.path.join(path, LAUGH) + self.path_to_failure = os.path.join(path, CRASH) + self.path_to_bubbles = os.path.join(path, BUBBLES) + + self._create_sprites(path) + + self.challenges = [] + for challenge in EASY: + self.challenges.append(challenge) + self.fraction = 0.5 # the target of the current challenge + self.label = '1/2' # the label + self.count = 0 # number of bounces played + self.correct = 0 # number of correct answers + self.press = None # sprite under mouse click + self.mode = 'fractions' + self.new_bounce = False + self.n = 0 + + self.dx = 0. # ball horizontal trajectory + # acceleration (with dampening) + self.ddy = (6.67 * self.height) / (STEPS * STEPS) + self.dy = self.ddy * (1 - STEPS) / 2. # initial step size + + def _create_sprites(self, path): + ''' Create all of the sprites we'll need ''' + self.smiley_graphic = svg_str_to_pixbuf(svg_from_file( + os.path.join(path, 'smiley.svg'))) + + self.frown_graphic = svg_str_to_pixbuf(svg_from_file( + os.path.join(path, 'frown.svg'))) + + self.egg_graphic = svg_str_to_pixbuf(svg_from_file( + os.path.join(path, 'Easter_egg.svg'))) + + self.blank_graphic = svg_str_to_pixbuf( + _svg_header(BAR_HEIGHT, BAR_HEIGHT, 1.0) + \ + _svg_rect(BAR_HEIGHT, BAR_HEIGHT, 5, 5, 0, 0, + '#C0C0C0', '#282828') + \ + _svg_footer()) + + self.ball = Sprite(self.sprites, 0, 0, svg_str_to_pixbuf( + svg_from_file(os.path.join(path, 'basketball.svg')))) + self.ball.set_layer(1) + self.ball.set_label_attributes(24) + + ball = _extract_svg_payload( + file(os.path.join(path, 'basketball.svg'), 'r')) + self.cells = [] # Easter Egg animation + for i in range(8): + self.cells.append(Sprite( + self.sprites, 0, 0, svg_str_to_pixbuf( + _svg_header(85, 85, 1.0) + TRANSFORMS[i] + \ + ball + PUNCTURE + AIR + '</g>' + _svg_footer()))) + + for spr in self.cells: + spr.set_layer(1) + spr.move((0, self.height)) # move animation cells off screen + self.frame = 0 + + mark = _svg_header(self.ball.rect[2] / 2., + BAR_HEIGHT * self.scale + 4, 1.0) + \ + _svg_rect(self.ball.rect[2] / 2., + BAR_HEIGHT * self.scale + 4, 0, 0, 0, 0, + '#FF0000', '#FF0000') + \ + _svg_rect(1, BAR_HEIGHT * self.scale + 4, 0, 0, + self.ball.rect[2] / 4., 0, '#000000', '#000000') + \ + _svg_footer() + self.mark = Sprite(self.sprites, 0, + self.height, # hide off bottom of screen + svg_str_to_pixbuf(mark)) + self.mark.set_layer(2) + + self.bars = {} + self.bars[2] = Sprite(self.sprites, 0, 0, + svg_str_to_pixbuf(self._gen_bar(2))) + self.bars[2].move((int(self.ball.rect[2] / 2), + self.height - int((self.ball.rect[3] + \ + self.bars[2].rect[3]) / 2))) + self.current_bar = self.bars[2] + + num = _svg_header(BAR_HEIGHT * self.scale, BAR_HEIGHT * self.scale, + 1.0) + \ + _svg_rect(BAR_HEIGHT * self.scale, + BAR_HEIGHT * self.scale, 0, 0, 0, 0, + 'none', 'none') + \ + _svg_footer() + self.left = Sprite(self.sprites, int(self.ball.rect[2] / 4), + self.bars[2].rect[1], svg_str_to_pixbuf(num)) + self.left.set_label('0') + self.right = Sprite(self.sprites, + self.width - int(self.ball.rect[2] / 2), + self.bars[2].rect[1], svg_str_to_pixbuf(num)) + self.right.set_label('1') + + self.ball_y_max = self.bars[2].rect[1] - self.ball.rect[3] + self.ball.move((int((self.width - self.ball.rect[2]) / 2), + self.ball_y_max)) + + def _gen_bar(self, nsegments): + ''' Return a bar with n segments ''' + svg = _svg_header(self.width - self.ball.rect[2], BAR_HEIGHT, 1.0) + dx = (self.width - self.ball.rect[2]) / float(nsegments) + for i in range(int(nsegments) / 2): + svg += _svg_rect(dx, BAR_HEIGHT * self.scale, 0, 0, + i * 2 * dx, 0, '#FFFFFF', '#FFFFFF') + svg += _svg_rect(dx, BAR_HEIGHT * self.scale, 0, 0, + (i * 2 + 1) * dx, 0, '#AAAAAA', '#AAAAAA') + if int(nsegments) % 2 == 1: # odd + svg += _svg_rect(dx, BAR_HEIGHT * self.scale, 0, 0, + (i * 2 + 2) * dx, 0, '#FFFFFF', '#FFFFFF') + svg += _svg_footer() + return svg + + def pause(self): + ''' Pause play when visibility changes ''' + if self.timeout is not None: + gobject.source_remove(self.timeout) + self.timeout = None + + def we_are_sharing(self): + ''' If there is more than one buddy, we are sharing. ''' + if len(self.buddies) > 1: + return True + + def its_my_turn(self): + ''' When sharing, it is your turn... ''' + gobject.timeout_add(1000, self._take_a_turn) + + def _take_a_turn(self): + ''' On your turn, choose a fraction. ''' + self.my_turn = True + self.select_a_fraction = True + self.activity.set_player_on_toolbar(self.activity.nick) + self.activity.challenge.set_label( + _("Click on the bar to choose a fraction.")) + + def its_their_turn(self, nick): + ''' When sharing, it is nick's turn... ''' + gobject.timeout_add(1000, self._wait_your_turn, nick) + + def _wait_your_turn(self, nick): + ''' Wait for nick to choose a fraction. ''' + self.my_turn = False + self.activity.set_player_on_toolbar(nick) + self.activity.challenge.set_label( + _('Waiting for %(buddy)s') % {'buddy': nick}) + + def play_a_fraction(self, fraction): + ''' Play this fraction ''' + fraction_is_new = True + for i, c in enumerate(self.challenges): + if c[0] == fraction: + fraction_is_new = False + self.n = i + break + if fraction_is_new: + self.add_fraction(fraction) + self.n = len(self.challenges) + self._choose_a_fraction() + self._move_ball() + + def _button_press_cb(self, win, event): + ''' Callback to handle the button presses ''' + win.grab_focus() + x, y = map(int, event.get_coords()) + self.press = self.sprites.find_sprite((x, y)) + return True + + def _button_release_cb(self, win, event): + ''' Callback to handle the button releases ''' + win.grab_focus() + x, y = map(int, event.get_coords()) + if self.press is not None: + if self.we_are_sharing(): + if self.select_a_fraction and self.press == self.current_bar: + # Find the fraction closest to the click + fraction = self._search_challenges( + (x - self.current_bar.rect[0]) / \ + float(self.current_bar.rect[2])) + self.select_a_fraction = False + self.activity.send_a_fraction(fraction) + self.play_a_fraction(fraction) + else: + if self.timeout is None and self.press == self.ball: + self._choose_a_fraction() + self._move_ball() + return True + + def _search_challenges(self, f): + ''' Find the fraction which is closest to f in the list. ''' + dist = 1. + closest = '1/2' + for c in self.challenges: + numden = c[0].split('/') + delta = abs((float(numden[0]) / float(numden[1])) - f) + if delta <= dist: + dist = delta + closest = c[0] + return closest + + def _move_ball(self): + ''' Move the ball and test boundary conditions ''' + if self.new_bounce: + self.mark.move((0, self.height)) # hide the mark + if not self.we_are_sharing(): + self._choose_a_fraction() + self.new_bounce = False + self.dy = self.ddy * (1 - STEPS) / 2 # initial step size + + if self.accelerometer: + fh = open(ACCELEROMETER_DEVICE) + string = fh.read() + xyz = string[1:-2].split(',') + self.dx = float(xyz[0]) / 18. + fh.close() + + if self.ball.get_xy()[0] + self.dx > 0 and \ + self.ball.get_xy()[0] + self.dx < self.width - self.ball.rect[2]: + self.ball.move_relative((int(self.dx), int(self.dy))) + else: + self.ball.move_relative((0, int(self.dy))) + + # speed up ball in x while key is pressed + self.dx *= DDX + + # accelerate in y + self.dy += self.ddy + + if self.ball.get_xy()[1] >= self.ball_y_max: + # hit the bottom + self.ball.move((self.ball.get_xy()[0], self.ball_y_max)) + self._test() + self.new_bounce = True + + if self.we_are_sharing(): + if self.my_turn: + # Let the next player know it is their turn. + i = (self.buddies.index(self.activity.nick) + 1) % \ + len(self.buddies) + self.its_their_turn(self.buddies[i]) + self.activity.send_event('t|%s' % (self.buddies[i])) + else: + if self._easter_egg_test(): + self._animate() + else: + self.timeout = gobject.timeout_add( + max(STEP_PAUSE, + BOUNCE_PAUSE - self.count * STEP_PAUSE), + self._move_ball) + else: + self.timeout = gobject.timeout_add(STEP_PAUSE, self._move_ball) + + def _animate(self): + ''' A little Easter Egg just for fun. ''' + if self.new_bounce: + self.dy = self.ddy * (1 - STEPS) / 2 # initial step size + self.new_bounce = False + self.frame = 0 + self.frame_counter = 0 + self.cells[self.frame].move(self.ball.get_xy()) + self.ball.move((self.ball.get_xy()[0], self.height)) + gobject.idle_add(play_audio_from_file, self, self.path_to_bubbles) + + if self.accelerometer: + fh = open(ACCELEROMETER_DEVICE) + string = fh.read() + xyz = string[1:-2].split(',') + self.dx = float(xyz[0]) / 18. + fh.close() + else: + self.dx = uniform(-int(DX * self.scale), int(DX * self.scale)) + self.cells[self.frame].move_relative((int(self.dx), int(self.dy))) + self.dy += self.ddy + + self.frame_counter += 1 + if self.frame_counter in ANIMATION: + self._switch_cells(ANIMATION[self.frame_counter]) + + if self.cells[self.frame].get_xy()[1] >= self.ball_y_max: + # hit the bottom + self.ball.move((self.ball.get_xy()[0], self.ball_y_max)) + for spr in self.cells: + spr.move((0, self.height)) # hide the animation frames + self._test(easter_egg=True) + self.new_bounce = True + self.timeout = gobject.timeout_add(BOUNCE_PAUSE, self._move_ball) + else: + gobject.timeout_add(STEP_PAUSE, self._animate) + + def _switch_cells(self, cells): + ''' Switch between cells in the animation ''' + self.cells[cells[1]].move(self.cells[cells[0]].get_xy()) + self.cells[cells[0]].move((0, self.height)) + self.frame = cells[1] + + def add_fraction(self, string): + ''' Add a new challenge; set bar to 2x demominator ''' + numden = string.split('/', 2) + self.challenges.append([string, int(numden[1]), 0]) + + def _choose_a_fraction(self): + ''' Select a new fraction challenge from the table ''' + if not self.we_are_sharing(): + self.n = int(uniform(0, len(self.challenges))) + fstr = self.challenges[self.n][0] + saw_a_fraction = False + if '/' in fstr: # fraction + numden = fstr.split('/', 2) + self.fraction = float(numden[0].strip()) / float(numden[1].strip()) + saw_a_fraction = True + elif '%' in fstr: # percentage + self.fraction = float(fstr.strip().strip('%').strip()) / 100. + else: # To do: add support for decimals (using locale) + _logger.debug('Could not parse challenge (%s)', fstr) + fstr = '1/2' + self.fraction = 0.5 + saw_a_fraction = True + + if self.mode == 'fractions': + if saw_a_fraction: + self.label = fstr + else: + self.label = fstr.strip().strip('%').strip() + '/100' + else: # percentage + if not saw_a_fraction: + self.label = fstr + else: + self.label = str(int(self.fraction * 100 + 0.5)) + '%' + self.activity.reset_label(self.label) + self.ball.set_label(self.label) + + for bar in self.bars: + self.bars[bar].set_layer(-1) + if self.correct > EXPERT: # Show two-segment bar in expert mode + self.bars[2].set_layer(0) + self.current_bar = self.bars[2] + else: + if self.mode == 'fractions': + nseg = self.challenges[self.n][1] + else: + nseg = 10 # percentages + # generate new bar on demand + if not nseg in self.bars: + self.bars[nseg] = Sprite(self.sprites, 0, 0, + svg_str_to_pixbuf(self._gen_bar(nseg))) + self.bars[nseg].move((self.bars[2].rect[0], + self.bars[2].rect[1])) + self.bars[nseg].set_layer(0) + self.current_bar = self.bars[nseg] + + def _easter_egg_test(self): + ''' Test to see if we show the Easter Egg ''' + delta = self.ball.rect[2] / 8 + x = self.ball.get_xy()[0] + self.ball.rect[2] / 2 + f = self.bars[2].rect[2] * self.easter_egg / 100. + if x > f - delta and x < f + delta: + return True + else: + return False + + def _test(self, easter_egg=False): + ''' Test to see if we estimated correctly ''' + self.timeout = None + delta = self.ball.rect[2] / 4 + x = self.ball.get_xy()[0] + self.ball.rect[2] / 2 + f = self.ball.rect[2] / 2 + int(self.fraction * self.bars[2].rect[2]) + self.mark.move((int(f - self.mark.rect[2] / 2), + self.bars[2].rect[1] - 2)) + if self.challenges[self.n][2] == 0: # label the column + spr = Sprite(self.sprites, 0, 0, self.blank_graphic) + spr.set_label(self.label) + spr.move((int(self.n * 25), 0)) + spr.set_layer(-1) + self.challenges[self.n][2] += 1 + if x > f - delta and x < f + delta: + if not easter_egg: + spr = Sprite(self.sprites, 0, 0, self.smiley_graphic) + self.correct += 1 + gobject.idle_add(play_audio_from_file, self, self.path_to_success) + else: + if not easter_egg: + spr = Sprite(self.sprites, 0, 0, self.frown_graphic) + gobject.idle_add(play_audio_from_file, self, self.path_to_failure) + + if easter_egg: + spr = Sprite(self.sprites, 0, 0, self.egg_graphic) + + spr.move((int(self.n * 25), int(self.challenges[self.n][2] * 25))) + spr.set_layer(-1) + + # after enough correct answers, up the difficulty + if self.correct == len(EASY) * 2: + for challenge in MEDIUM: + self.challenges.append(challenge) + elif self.correct == len(EASY) * 4: + for challenge in HARD: + self.challenges.append(challenge) + + self.count += 1 + self.dx = 0. # stop horizontal movement between bounces + + def _keypress_cb(self, area, event): + ''' Keypress: moving the slides with the arrow keys ''' + k = gtk.gdk.keyval_name(event.keyval) + if k in ['h', 'Left', 'KP_Left']: + self.dx = -DX * self.scale + elif k in ['l', 'Right', 'KP_Right']: + self.dx = DX * self.scale + elif k in ['KP_Page_Up', 'Return']: + self._choose_a_fraction() + self._move_ball() + else: + self.dx = 0. + return True + + def _keyrelease_cb(self, area, event): + ''' Keyrelease: stop horizontal movement ''' + self.dx = 0. + return True + + def _expose_cb(self, win, event): + ''' Callback to handle window expose events ''' + self.sprites.redraw_sprites(event.area) + return True + + def _destroy_cb(self, win, event): + ''' Callback to handle quit ''' + gtk.main_quit() @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2011, Walter Bender, Paulina Clares, Chris Rowe +# +# 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. + +from sprites import Sprite +from svg_utils import svg_header, svg_footer, svg_str_to_pixbuf, \ + extract_svg_payload, svg_from_file + + +SIZE = 85 +ANIMATION = {10: (0, 1), 15: (1, 2), 20: (2, 1), 25: (1, 2), 30: (2, 1), + 35: (1, 2), 40: (2, 3), 45: (3, 4), 50: (4, 3), 55: (3, 4), + 60: (4, 3), 65: (3, 4), 70: (4, 5), 75: (5, 6), 80: (6, 5), + 85: (5, 6), 90: (6, 7)} +# Easter Egg animation graphics +TRANSFORMS = ['<g>', + '<g transform="matrix(0.83251323,0.17764297,-0.48065174, \ +1.0074555,27.969568,-8.7531294)">', + '<g transform="matrix(-0.83251323,0.17764297,0.48065174, \ +1.0074555,57.030432,-8.7531294)">', + '<g transform="matrix(0.57147881,-0.357582,-0.32994345, \ +0.96842187,32.525583,15.686767)">', + '<g transform="matrix(-0.57147881,-0.357582,0.32994345, \ +0.96842187,52.474417,15.686767)">', + '<g transform="matrix(0.39557109,-0.57943591,-0.22838308, \ +0.86196565,35.595823,29.733447)">', + '<g transform="matrix(-0.39557109,-0.57943591,0.22838308, \ +0.86196565,49.404177,29.733447)">', + '<g transform="matrix(1,0,0,0.08410415,0,73.873449)">'] +PUNCTURE = \ +' <g \ + transform="translate(2.5316175, -8)">\ + <path \ + d="m 33.19688,68.961518 c 3.900378,7.602149 10.970659,7.634416 \ +13.708164,7.432138"\ + style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;\ +stroke-miterlimit:4" />\ + <path \ + d="m 33.031721,77.05429 c 8.199837,0.123635 12.819227,-7.570626 \ +12.882372,-8.423089" \ + style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;\ +stroke-miterlimit:4" />\ + </g>' +AIR = \ +' <g \ + transform="matrix(0.63786322,0,0,0.64837179,17.379518,68.534252)"> \ + <path \ + d="M 39.054054,1.75 C 37.741313,16.51834 25.926641,23.082047 \ +25.926641,23.082047 l 0,0" \ + style="fill:none;stroke:#0ac9fb;stroke-width:6.0;stroke-linecap:round;\ +stroke-miterlimit:4;" />\ + <path \ + d="m 39.710425,1.75 c 1.312741,14.76834 13.127413,21.332047 \ +13.127413,21.332047 l 0,0" \ + style="fill:none;stroke:#0ac9fb;stroke-width:6.0;stroke-linecap:round;\ +stroke-miterlimit:4" />\ + <path \ + d="m 39.054054,1.75 c 1.969112,3.281854 -0.656371,20.347491 \ +-0.656371,20.347491 l 0,0" \ + style="fill:none;stroke:#0ac9fb;stroke-width:6.0;stroke-linecap:round;\ +stroke-miterlimit:4" />\ + </g>' + + +class Ball(): + ''' The Bounce class is used to define the ball and the user + interaction. ''' + + def __init__(self, sprites, filename): + ''' Create a ball object and Easter Egg animation from an SVG file. ''' + self.ball = Sprite(sprites, 0, 0, svg_str_to_pixbuf( + svg_from_file(filename))) + self.current_frame = 0 + self.frames = [] # Easter Egg animation + + self.ball.set_layer(1) + self.ball.set_label_attributes(24) + + ball = extract_svg_payload(file(filename, 'r')) + for i in range(8): + self.frames.append(Sprite( + sprites, 0, 0, svg_str_to_pixbuf( + svg_header(SIZE, SIZE, 1.0) + TRANSFORMS[i] + \ + ball + PUNCTURE + AIR + '</g>' + svg_footer()))) + + for spr in self.frames: + spr.set_layer(1) + spr.move((0, -SIZE)) # move animation frames off screen + + def ball_x(self): + return self.ball.get_xy()[0] + + def ball_y(self): + return self.ball.get_xy()[1] + + def frame_x(self, i): + return self.frames[i].get_xy()[0] + + def frame_y(self, i): + return self.frames[i].get_xy()[1] + + def width(self): + return self.ball.rect[2] + + def height(self): + return self.ball.rect[3] + + def move_ball(self, pos): + self.ball.move(pos) + + def move_ball_relative(self, pos): + self.ball.move_relative(pos) + + def move_frame(self, i, pos): + self.frames[i].move(pos) + + def move_frame_relative(self, i, pos): + self.frames[i].move_relative(pos) + + def hide_frames(self): + for frame in self.frames: + frame.move((0, -SIZE)) # hide the animation frames + + def next_frame(self, frame_counter): + if frame_counter in ANIMATION: + self._switch_frames(ANIMATION[frame_counter]) + return self.current_frame + + def _switch_frames(self, frames): + ''' Switch between frames in the animation ''' + self.move_frame(frames[1], (self.frame_x(frames[0]), + self.frame_y(frames[0]))) + self.move_frame(frames[0], ((0, -SIZE))) # hide the frame + self.current_frame = frames[1] @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2011, Walter Bender, Paulina Clares, Chris Rowe +# +# 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. + +from sprites import Sprite +from svg_utils import svg_header, svg_footer, svg_rect, svg_str_to_pixbuf + +from gettext import gettext as _ + + +BAR_HEIGHT = 25 + + +class Bar(): + ''' The Bar class is used to define the bars at the bottom of the + screen ''' + + def __init__(self, sprites, width, height, scale, size): + ''' Initialize the 2-segment bar, labels, and mark ''' + self.sprites = sprites + self.bars = {} + self.screen_width = width + self.screen_height = height + self.scale = scale + self.ball_size = size + + self.make_bar(2) + self.make_mark() + self.make_labels() + + def make_mark(self): + ''' Make a mark to show the fraction position on the bar. ''' + mark = svg_header(self.ball_size / 2., + BAR_HEIGHT * self.scale + 4, 1.0) + \ + svg_rect(self.ball_size / 2., + BAR_HEIGHT * self.scale + 4, 0, 0, 0, 0, + '#FF0000', '#FF0000') + \ + svg_rect(1, BAR_HEIGHT * self.scale + 4, 0, 0, + self.ball_size / 4., 0, '#000000', '#000000') + \ + svg_footer() + self.mark = Sprite(self.sprites, 0, + self.screen_height, # hide off bottom of screen + svg_str_to_pixbuf(mark)) + self.mark.set_layer(2) + + def mark_width(self): + return self.mark.rect[2] + + def bar_x(self): + return self.bars[2].get_xy()[0] + + def bar_y(self): + return self.bars[2].get_xy()[1] + + def width(self): + return self.bars[2].rect[2] + + def height(self): + return self.bars[2].rect[3] + + def hide_bars(self): + ''' Hide all of the bars ''' + for bar in self.bars: + self.bars[bar].set_layer(-1) + + def make_labels(self): + ''' Label the bar ''' + num = svg_header(BAR_HEIGHT * self.scale, BAR_HEIGHT * self.scale, + 1.0) + \ + svg_rect(BAR_HEIGHT * self.scale, BAR_HEIGHT * self.scale, + 0, 0, 0, 0, 'none', 'none') + \ + svg_footer() + self.left = Sprite(self.sprites, int(self.ball_size / 4), + self.bar_y(), svg_str_to_pixbuf(num)) + self.left.set_label(_('0')) + self.right = Sprite(self.sprites, + self.screen_width - int(self.ball_size / 2), + self.bar_y(), svg_str_to_pixbuf(num)) + self.right.set_label(_('1')) + + def get_bar(self, nsegments): + ''' Return a bar with n segments ''' + if nsegments not in self.bars: + self.make_bar(nsegments) + return self.bars[nsegments] + + def make_bar(self, nsegments): + ''' Create a bar with n segments ''' + svg = svg_header(self.screen_width - self.ball_size, BAR_HEIGHT, 1.0) + dx = (self.screen_width - self.ball_size) / float(nsegments) + for i in range(int(nsegments) / 2): + svg += svg_rect(dx, BAR_HEIGHT * self.scale, 0, 0, + i * 2 * dx, 0, '#FFFFFF', '#FFFFFF') + svg += svg_rect(dx, BAR_HEIGHT * self.scale, 0, 0, + (i * 2 + 1) * dx, 0, '#AAAAAA', '#AAAAAA') + if int(nsegments) % 2 == 1: # odd + svg += svg_rect(dx, BAR_HEIGHT * self.scale, 0, 0, + (i * 2 + 2) * dx, 0, '#FFFFFF', '#FFFFFF') + svg += svg_footer() + + self.bars[nsegments] = Sprite(self.sprites, 0, 0, + svg_str_to_pixbuf(svg)) + self.bars[nsegments].move( + (int(self.ball_size / 2), self.screen_height - \ + int((self.ball_size + self.height()) / 2))) + @@ -16,98 +16,50 @@ # the number of segments in the bar, # the number of times this challenge has been played] -EASY = [['1/2', 2, 0], ['1/3', 3, 0], ['1/4', 4, 0], - ['2/4', 4, 0], ['2/3', 3, 0], ['3/4', 4, 0]] -MEDIUM = [['1/6', 6, 0], ['2/6', 6, 0], ['3/6', 6, 0], - ['4/6', 6, 0], ['5/6', 6, 0], - ['1/8', 8, 0], ['2/8', 8, 0], ['3/8', 8, 0], - ['4/8', 8, 0], ['5/8', 8, 0], ['6/8', 8, 0], - ['7/8', 8, 0]] -HARD = [['1/12', 12, 0], ['2/12', 12, 0], ['3/12', 12, 0], - ['4/12', 12, 0], ['3/12', 12, 0], ['6/12', 12, 0], - ['7/12', 12, 0], ['8/12', 12, 0], ['9/12', 12, 0], - ['10/12', 12, 0], ['11/12', 12, 0], - ['1/5', 10, 0], ['2/5', 10, 0], ['3/5', 10, 0], - ['4/5', 10, 0], - ['1/10', 10, 0], ['2/10', 10, 0], ['3/10', 10, 0], - ['4/10', 10, 0], ['5/10', 10, 0], ['6/10', 10, 0], - ['7/10', 10, 0], ['8/10', 10, 0], ['9/10', 10, 0], - ['1/16', 4, 0], ['2/16', 4, 0], ['3/16', 4, 0], - ['4/16', 4, 0], ['5/16', 4, 0], ['6/16', 4, 0], - ['7/16', 4, 0], ['8/16', 4, 0], ['9/16', 4, 0], - ['10/16', 4, 0], ['11/16', 4, 0], ['12/16', 4, 0], - ['13/16', 4, 0], ['14/16', 4, 0], ['15/16', 4, 0]] -EXPERT = 100 # after some number of correct answers, don't segment the bar -BAR_HEIGHT = 25 +CHALLENGES = [[['1/2', 2, 0], ['1/3', 3, 0], ['1/4', 4, 0], + ['2/4', 4, 0], ['2/3', 3, 0], ['3/4', 4, 0]], + [['1/8', 8, 0], ['2/8', 8, 0], ['3/8', 8, 0], + ['4/8', 8, 0], ['5/8', 8, 0], ['6/8', 8, 0], + ['7/8', 8, 0]], + [['1/6', 6, 0], ['2/6', 6, 0], ['3/6', 6, 0], + ['4/6', 6, 0], ['5/6', 6, 0]], + [['1/5', 10, 0], ['2/5', 10, 0], ['3/5', 10, 0], + ['4/5', 10, 0]], + [['1/10', 10, 0], ['2/10', 10, 0], ['3/10', 10, 0], + ['4/10', 10, 0], ['5/10', 10, 0], ['6/10', 10, 0], + ['7/10', 10, 0], ['8/10', 10, 0], ['9/10', 10, 0]], + [['1/12', 12, 0], ['2/12', 12, 0], ['3/12', 12, 0], + ['4/12', 12, 0], ['3/12', 12, 0], ['6/12', 12, 0], + ['7/12', 12, 0], ['8/12', 12, 0], ['9/12', 12, 0], + ['10/12', 12, 0], ['11/12', 12, 0]], + [['1/16', 4, 0], ['2/16', 4, 0], ['3/16', 4, 0], + ['4/16', 4, 0], ['5/16', 4, 0], ['6/16', 4, 0], + ['7/16', 4, 0], ['8/16', 4, 0], ['9/16', 4, 0], + ['10/16', 4, 0], ['11/16', 4, 0], ['12/16', 4, 0], + ['13/16', 4, 0], ['14/16', 4, 0], ['15/16', 4, 0]]] +REWARD_HEIGHT = 25 STEPS = 100. # number of time steps per bounce rise and fall STEP_PAUSE = 50 # milliseconds between steps BOUNCE_PAUSE = 3000 # milliseconds between bounces DX = 10 # starting step size for horizontal movement DDX = 1.25 # acceleration during keypress -ANIMATION = {10: (0, 1), 15: (1, 2), 20: (2, 1), 25: (1, 2), 30: (2, 1), - 35: (1, 2), 40: (2, 3), 45: (3, 4), 50: (4, 3), 55: (3, 4), - 60: (4, 3), 65: (3, 4), 70: (4, 5), 75: (5, 6), 80: (6, 5), - 85: (5, 6), 90: (6, 7)} ACCELEROMETER_DEVICE = '/sys/devices/platform/lis3lv02d/position' CRASH = 'crash.ogg' # wrong answer sound LAUGH = 'bottle.ogg' # correct answer sound BUBBLES = 'bubbles.ogg' # Easter Egg sound -# Easter Egg animation graphics -TRANSFORMS = ['<g>', - '<g transform="matrix(0.83251323,0.17764297,-0.48065174, \ -1.0074555,27.969568,-8.7531294)">', - '<g transform="matrix(-0.83251323,0.17764297,0.48065174, \ -1.0074555,57.030432,-8.7531294)">', - '<g transform="matrix(0.57147881,-0.357582,-0.32994345, \ -0.96842187,32.525583,15.686767)">', - '<g transform="matrix(-0.57147881,-0.357582,0.32994345, \ -0.96842187,52.474417,15.686767)">', - '<g transform="matrix(0.39557109,-0.57943591,-0.22838308, \ -0.86196565,35.595823,29.733447)">', - '<g transform="matrix(-0.39557109,-0.57943591,0.22838308, \ -0.86196565,49.404177,29.733447)">', - '<g transform="matrix(1,0,0,0.08410415,0,73.873449)">'] -PUNCTURE = \ -' <g \ - transform="translate(2.5316175, -8)">\ - <path \ - d="m 33.19688,68.961518 c 3.900378,7.602149 10.970659,7.634416 \ -13.708164,7.432138"\ - style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;\ -stroke-miterlimit:4" />\ - <path \ - d="m 33.031721,77.05429 c 8.199837,0.123635 12.819227,-7.570626 \ -12.882372,-8.423089" \ - style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;\ -stroke-miterlimit:4" />\ - </g>' -AIR = \ -' <g \ - transform="matrix(0.63786322,0,0,0.64837179,17.379518,68.534252)"> \ - <path \ - d="M 39.054054,1.75 C 37.741313,16.51834 25.926641,23.082047 \ -25.926641,23.082047 l 0,0" \ - style="fill:none;stroke:#0ac9fb;stroke-width:6.0;stroke-linecap:round;\ -stroke-miterlimit:4;" />\ - <path \ - d="m 39.710425,1.75 c 1.312741,14.76834 13.127413,21.332047 \ -13.127413,21.332047 l 0,0" \ - style="fill:none;stroke:#0ac9fb;stroke-width:6.0;stroke-linecap:round;\ -stroke-miterlimit:4" />\ - <path \ - d="m 39.054054,1.75 c 1.969112,3.281854 -0.656371,20.347491 \ --0.656371,20.347491 l 0,0" \ - style="fill:none;stroke:#0ac9fb;stroke-width:6.0;stroke-linecap:round;\ -stroke-miterlimit:4" />\ - </g>' import gtk from random import uniform import os import gobject +from svg_utils import svg_header, svg_footer, svg_rect, svg_str_to_pixbuf, \ + svg_from_file from play_audio import play_audio_from_file +from ball import Ball +from bar import Bar + from gettext import gettext as _ import logging @@ -122,118 +74,6 @@ except ImportError: from sprites import Sprites, Sprite -def generate_xo_svg(scale=1.0, colors=["#C0C0C0", "#282828"]): - ''' Returns an SVG string representing an XO image ''' - return _svg_header(55, 55, scale) + \ - _svg_xo(colors[0], colors[1]) + \ - _svg_footer() - - -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 _svg_rect(w, h, rx, ry, x, y, fill, stroke): - ''' Returns an SVG rectangle ''' - svg_string = ' <rect\n' - svg_string += ' width="%f"\n' % (w) - svg_string += ' height="%f"\n' % (h) - svg_string += ' rx="%f"\n' % (rx) - svg_string += ' ry="%f"\n' % (ry) - svg_string += ' x="%f"\n' % (x) - svg_string += ' y="%f"\n' % (y) - svg_string += _svg_style('fill:%s;stroke:%s;' % (fill, stroke)) - return svg_string - - -def _svg_xo(fill, stroke, width=3.5): - ''' Returns XO icon graphic ''' - svg_string = '<path d="M33.233,35.1l10.102,10.1c0.752,\ -0.75,1.217,1.783,1.217,2.932\ - c0,2.287-1.855,4.143-4.146,4.143c-1.145,0-2.178-0.463-2.932-1.211L27.372,\ -40.961l-10.1,10.1c-0.75,0.75-1.787,1.211-2.934,1.211\ - c-2.284,0-4.143-1.854-4.143-4.141c0-1.146,0.465-2.184,\ -1.212-2.934l10.104-10.102L11.409,24.995\ - c-0.747-0.748-1.212-1.785-1.212-2.93c0-2.289,1.854-4.146,4.146-4.146c1.143,\ -0,2.18,0.465,2.93,1.214l10.099,10.102l10.102-10.103\ - c0.754-0.749,1.787-1.214,2.934-1.214c2.289,0,4.146,1.856,4.146,4.145c0,\ -1.146-0.467,2.18-1.217,2.932L33.233,35.1z" ' - svg_string += _svg_style('fill:%s;stroke:%s;stroke_width:%f' % (fill, - stroke, - width)) - svg_string += '\n<circle cx="27.371" cy="10.849" r="8.122" ' - svg_string += _svg_style('fill:%s;stroke:%s;stroke_width:%f' % (fill, - stroke, - width)) - return svg_string - - -def _svg_header(w, h, scale, hscale=1.0): - ''' Returns SVG header; some beads are elongated (hscale) ''' - svg_string = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n' - svg_string += '<!-- Created with Python -->\n' - svg_string += '<svg\n' - svg_string += ' xmlns:svg="http://www.w3.org/2000/svg"\n' - svg_string += ' xmlns="http://www.w3.org/2000/svg"\n' - svg_string += ' version="1.0"\n' - svg_string += ' width="%f"\n' % (w * scale) - svg_string += ' height="%f">\n' % (h * scale * hscale) - svg_string += '<g\n transform="matrix(%f,0,0,%f,0,0)">\n' % ( - scale, scale) - return svg_string - - -def _svg_footer(): - ''' Returns SVG footer ''' - svg_string = '</g>\n' - svg_string += '</svg>\n' - return svg_string - - -def _svg_style(extras=''): - ''' Returns SVG style for shape rendering ''' - return 'style="%s"/>\n' % (extras) - - -def svg_from_file(pathname): - ''' Read SVG string from a file ''' - f = file(pathname, 'r') - svg = f.read() - f.close() - return(svg) - - -def _extract_svg_payload(fd): - """Returns everything between <svg ...> and </svg>""" - payload = '' - looking_for_start_svg_token = True - looking_for_close_token = True - looking_for_end_svg_token = True - for line in fd: - if looking_for_start_svg_token: - if line.find('<svg') < 0: - continue - looking_for_start_svg_token = False - line = line.split('<svg', 1)[1] - if looking_for_close_token: - if line.find('>') < 0: - continue - looking_for_close_token = False - line = line.split('>', 1)[1] - if looking_for_end_svg_token: - if line.find('</svg>') < 0: - payload += line - continue - payload += line.split('</svg>')[0] - break - return payload - - class Bounce(): ''' The Bounce class is used to define the ball and the user interaction. ''' @@ -287,8 +127,10 @@ class Bounce(): self._create_sprites(path) + self.challenge = 0 + self.expert = False self.challenges = [] - for challenge in EASY: + for challenge in CHALLENGES[self.challenge]: self.challenges.append(challenge) self.fraction = 0.5 # the target of the current challenge self.label = '1/2' # the label @@ -316,83 +158,21 @@ class Bounce(): os.path.join(path, 'Easter_egg.svg'))) self.blank_graphic = svg_str_to_pixbuf( - _svg_header(BAR_HEIGHT, BAR_HEIGHT, 1.0) + \ - _svg_rect(BAR_HEIGHT, BAR_HEIGHT, 5, 5, 0, 0, - '#C0C0C0', '#282828') + \ - _svg_footer()) - - self.ball = Sprite(self.sprites, 0, 0, svg_str_to_pixbuf( - svg_from_file(os.path.join(path, 'basketball.svg')))) - self.ball.set_layer(1) - self.ball.set_label_attributes(24) - - ball = _extract_svg_payload( - file(os.path.join(path, 'basketball.svg'), 'r')) - self.cells = [] # Easter Egg animation - for i in range(8): - self.cells.append(Sprite( - self.sprites, 0, 0, svg_str_to_pixbuf( - _svg_header(85, 85, 1.0) + TRANSFORMS[i] + \ - ball + PUNCTURE + AIR + '</g>' + _svg_footer()))) - - for spr in self.cells: - spr.set_layer(1) - spr.move((0, self.height)) # move animation cells off screen - self.frame = 0 - - mark = _svg_header(self.ball.rect[2] / 2., - BAR_HEIGHT * self.scale + 4, 1.0) + \ - _svg_rect(self.ball.rect[2] / 2., - BAR_HEIGHT * self.scale + 4, 0, 0, 0, 0, - '#FF0000', '#FF0000') + \ - _svg_rect(1, BAR_HEIGHT * self.scale + 4, 0, 0, - self.ball.rect[2] / 4., 0, '#000000', '#000000') + \ - _svg_footer() - self.mark = Sprite(self.sprites, 0, - self.height, # hide off bottom of screen - svg_str_to_pixbuf(mark)) - self.mark.set_layer(2) - - self.bars = {} - self.bars[2] = Sprite(self.sprites, 0, 0, - svg_str_to_pixbuf(self._gen_bar(2))) - self.bars[2].move((int(self.ball.rect[2] / 2), - self.height - int((self.ball.rect[3] + \ - self.bars[2].rect[3]) / 2))) - self.current_bar = self.bars[2] - - num = _svg_header(BAR_HEIGHT * self.scale, BAR_HEIGHT * self.scale, - 1.0) + \ - _svg_rect(BAR_HEIGHT * self.scale, - BAR_HEIGHT * self.scale, 0, 0, 0, 0, - 'none', 'none') + \ - _svg_footer() - self.left = Sprite(self.sprites, int(self.ball.rect[2] / 4), - self.bars[2].rect[1], svg_str_to_pixbuf(num)) - self.left.set_label('0') - self.right = Sprite(self.sprites, - self.width - int(self.ball.rect[2] / 2), - self.bars[2].rect[1], svg_str_to_pixbuf(num)) - self.right.set_label('1') - - self.ball_y_max = self.bars[2].rect[1] - self.ball.rect[3] - self.ball.move((int((self.width - self.ball.rect[2]) / 2), - self.ball_y_max)) + svg_header(REWARD_HEIGHT, REWARD_HEIGHT, 1.0) + \ + svg_rect(REWARD_HEIGHT, REWARD_HEIGHT, 5, 5, 0, 0, + '#C0C0C0', '#282828') + \ + svg_footer()) + + self.ball = Ball(self.sprites, os.path.join(path, 'basketball.svg')) + self.current_frame = 0 - def _gen_bar(self, nsegments): - ''' Return a bar with n segments ''' - svg = _svg_header(self.width - self.ball.rect[2], BAR_HEIGHT, 1.0) - dx = (self.width - self.ball.rect[2]) / float(nsegments) - for i in range(int(nsegments) / 2): - svg += _svg_rect(dx, BAR_HEIGHT * self.scale, 0, 0, - i * 2 * dx, 0, '#FFFFFF', '#FFFFFF') - svg += _svg_rect(dx, BAR_HEIGHT * self.scale, 0, 0, - (i * 2 + 1) * dx, 0, '#AAAAAA', '#AAAAAA') - if int(nsegments) % 2 == 1: # odd - svg += _svg_rect(dx, BAR_HEIGHT * self.scale, 0, 0, - (i * 2 + 2) * dx, 0, '#FFFFFF', '#FFFFFF') - svg += _svg_footer() - return svg + self.bar = Bar(self.sprites, self.width, self.height, self.scale, + self.ball.width()) + self.current_bar = self.bar.get_bar(2) + + self.ball_y_max = self.bar.bar_y() - self.ball.height() + self.ball.move_ball((int((self.width - self.ball.width()) / 2), + self.ball_y_max)) def pause(self): ''' Pause play when visibility changes ''' @@ -458,13 +238,12 @@ class Bounce(): if self.select_a_fraction and self.press == self.current_bar: # Find the fraction closest to the click fraction = self._search_challenges( - (x - self.current_bar.rect[0]) / \ - float(self.current_bar.rect[2])) + (x - self.bar.bar_x()) / float(self.bar.width())) self.select_a_fraction = False self.activity.send_a_fraction(fraction) self.play_a_fraction(fraction) else: - if self.timeout is None and self.press == self.ball: + if self.timeout is None and self.press == self.ball.ball: self._choose_a_fraction() self._move_ball() return True @@ -484,7 +263,7 @@ class Bounce(): def _move_ball(self): ''' Move the ball and test boundary conditions ''' if self.new_bounce: - self.mark.move((0, self.height)) # hide the mark + self.bar.mark.move((0, self.height)) # hide the mark if not self.we_are_sharing(): self._choose_a_fraction() self.new_bounce = False @@ -497,11 +276,11 @@ class Bounce(): self.dx = float(xyz[0]) / 18. fh.close() - if self.ball.get_xy()[0] + self.dx > 0 and \ - self.ball.get_xy()[0] + self.dx < self.width - self.ball.rect[2]: - self.ball.move_relative((int(self.dx), int(self.dy))) + if self.ball.ball_x() + self.dx > 0 and \ + self.ball.ball_x() + self.dx < self.width - self.ball.width(): + self.ball.move_ball_relative((int(self.dx), int(self.dy))) else: - self.ball.move_relative((0, int(self.dy))) + self.ball.move_ball_relative((0, int(self.dy))) # speed up ball in x while key is pressed self.dx *= DDX @@ -509,9 +288,9 @@ class Bounce(): # accelerate in y self.dy += self.ddy - if self.ball.get_xy()[1] >= self.ball_y_max: + if self.ball.ball_y() >= self.ball_y_max: # hit the bottom - self.ball.move((self.ball.get_xy()[0], self.ball_y_max)) + self.ball.move_ball((self.ball.ball_x(), self.ball_y_max)) self._test() self.new_bounce = True @@ -538,10 +317,11 @@ class Bounce(): if self.new_bounce: self.dy = self.ddy * (1 - STEPS) / 2 # initial step size self.new_bounce = False - self.frame = 0 + self.current_frame = 0 self.frame_counter = 0 - self.cells[self.frame].move(self.ball.get_xy()) - self.ball.move((self.ball.get_xy()[0], self.height)) + self.ball.move_frame(self.current_frame, + (self.ball.ball_x(), self.ball.ball_y())) + self.ball.move_ball((self.ball.ball_x(), self.height)) gobject.idle_add(play_audio_from_file, self, self.path_to_bubbles) if self.accelerometer: @@ -552,30 +332,23 @@ class Bounce(): fh.close() else: self.dx = uniform(-int(DX * self.scale), int(DX * self.scale)) - self.cells[self.frame].move_relative((int(self.dx), int(self.dy))) + self.ball.move_frame_relative(self.current_frame, (int(self.dx), + int(self.dy))) self.dy += self.ddy self.frame_counter += 1 - if self.frame_counter in ANIMATION: - self._switch_cells(ANIMATION[self.frame_counter]) + self.current_frame = self.ball.next_frame(self.frame_counter) - if self.cells[self.frame].get_xy()[1] >= self.ball_y_max: + if self.ball.frame_y(self.current_frame) >= self.ball_y_max: # hit the bottom - self.ball.move((self.ball.get_xy()[0], self.ball_y_max)) - for spr in self.cells: - spr.move((0, self.height)) # hide the animation frames + self.ball.move_ball((self.ball.ball_x(), self.ball_y_max)) + self.ball.hide_frames() self._test(easter_egg=True) self.new_bounce = True self.timeout = gobject.timeout_add(BOUNCE_PAUSE, self._move_ball) else: gobject.timeout_add(STEP_PAUSE, self._animate) - def _switch_cells(self, cells): - ''' Switch between cells in the animation ''' - self.cells[cells[1]].move(self.cells[cells[0]].get_xy()) - self.cells[cells[0]].move((0, self.height)) - self.frame = cells[1] - def add_fraction(self, string): ''' Add a new challenge; set bar to 2x demominator ''' numden = string.split('/', 2) @@ -610,32 +383,26 @@ class Bounce(): else: self.label = str(int(self.fraction * 100 + 0.5)) + '%' self.activity.reset_label(self.label) - self.ball.set_label(self.label) + self.ball.ball.set_label(self.label) - for bar in self.bars: - self.bars[bar].set_layer(-1) - if self.correct > EXPERT: # Show two-segment bar in expert mode - self.bars[2].set_layer(0) - self.current_bar = self.bars[2] + self.bar.hide_bars() + if self.expert: # Show two-segment bar in expert mode + self.current_bar = self.bar.get_bar(2) else: if self.mode == 'fractions': nseg = self.challenges[self.n][1] else: nseg = 10 # percentages # generate new bar on demand - if not nseg in self.bars: - self.bars[nseg] = Sprite(self.sprites, 0, 0, - svg_str_to_pixbuf(self._gen_bar(nseg))) - self.bars[nseg].move((self.bars[2].rect[0], - self.bars[2].rect[1])) - self.bars[nseg].set_layer(0) - self.current_bar = self.bars[nseg] + self.current_bar = self.bar.get_bar(nseg) + self.current_bar.move((self.bar.bar_x(), self.bar.bar_y())) + self.current_bar.set_layer(0) def _easter_egg_test(self): ''' Test to see if we show the Easter Egg ''' - delta = self.ball.rect[2] / 8 - x = self.ball.get_xy()[0] + self.ball.rect[2] / 2 - f = self.bars[2].rect[2] * self.easter_egg / 100. + delta = self.ball.width() / 8 + x = self.ball.ball_x() + self.ball.width() / 2 + f = self.bar.width() * self.easter_egg / 100. if x > f - delta and x < f + delta: return True else: @@ -644,11 +411,11 @@ class Bounce(): def _test(self, easter_egg=False): ''' Test to see if we estimated correctly ''' self.timeout = None - delta = self.ball.rect[2] / 4 - x = self.ball.get_xy()[0] + self.ball.rect[2] / 2 - f = self.ball.rect[2] / 2 + int(self.fraction * self.bars[2].rect[2]) - self.mark.move((int(f - self.mark.rect[2] / 2), - self.bars[2].rect[1] - 2)) + delta = self.ball.width() / 4 + x = self.ball.ball_x() + self.ball.width() / 2 + f = self.ball.width() / 2 + int(self.fraction * self.bar.width()) + self.bar.mark.move((int(f - self.bar.mark_width() / 2), + self.bar.bar_y() - 2)) if self.challenges[self.n][2] == 0: # label the column spr = Sprite(self.sprites, 0, 0, self.blank_graphic) spr.set_label(self.label) @@ -672,12 +439,13 @@ class Bounce(): spr.set_layer(-1) # after enough correct answers, up the difficulty - if self.correct == len(EASY) * 2: - for challenge in MEDIUM: - self.challenges.append(challenge) - elif self.correct == len(EASY) * 4: - for challenge in HARD: - self.challenges.append(challenge) + if self.correct == len(self.challenges) * 2: + self.challenge += 1 + if self.challenge < len(CHALLENGES): + for challenge in CHALLENGES[self.challenge]: + self.challenges.append(challenge) + else: + self.expert = True self.count += 1 self.dx = 0. # stop horizontal movement between bounces diff --git a/svg_utils.py b/svg_utils.py new file mode 100644 index 0000000..0d828c6 --- /dev/null +++ b/svg_utils.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2011, 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 + + +def generate_xo_svg(scale=1.0, colors=["#C0C0C0", "#282828"]): + ''' Returns an SVG string representing an XO image ''' + return svg_header(55, 55, scale) + \ + _svg_xo(colors[0], colors[1]) + \ + svg_footer() + + +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 svg_rect(w, h, rx, ry, x, y, fill, stroke): + ''' Returns an SVG rectangle ''' + svg_string = ' <rect\n' + svg_string += ' width="%f"\n' % (w) + svg_string += ' height="%f"\n' % (h) + svg_string += ' rx="%f"\n' % (rx) + svg_string += ' ry="%f"\n' % (ry) + svg_string += ' x="%f"\n' % (x) + svg_string += ' y="%f"\n' % (y) + svg_string += _svg_style('fill:%s;stroke:%s;' % (fill, stroke)) + return svg_string + + +def _svg_xo(fill, stroke, width=3.5): + ''' Returns XO icon graphic ''' + svg_string = '<path d="M33.233,35.1l10.102,10.1c0.752,\ +0.75,1.217,1.783,1.217,2.932\ + c0,2.287-1.855,4.143-4.146,4.143c-1.145,0-2.178-0.463-2.932-1.211L27.372,\ +40.961l-10.1,10.1c-0.75,0.75-1.787,1.211-2.934,1.211\ + c-2.284,0-4.143-1.854-4.143-4.141c0-1.146,0.465-2.184,\ +1.212-2.934l10.104-10.102L11.409,24.995\ + c-0.747-0.748-1.212-1.785-1.212-2.93c0-2.289,1.854-4.146,4.146-4.146c1.143,\ +0,2.18,0.465,2.93,1.214l10.099,10.102l10.102-10.103\ + c0.754-0.749,1.787-1.214,2.934-1.214c2.289,0,4.146,1.856,4.146,4.145c0,\ +1.146-0.467,2.18-1.217,2.932L33.233,35.1z" ' + svg_string += _svg_style('fill:%s;stroke:%s;stroke_width:%f' % (fill, + stroke, + width)) + svg_string += '\n<circle cx="27.371" cy="10.849" r="8.122" ' + svg_string += _svg_style('fill:%s;stroke:%s;stroke_width:%f' % (fill, + stroke, + width)) + return svg_string + + +def svg_header(w, h, scale, hscale=1.0): + ''' Returns SVG header; some beads are elongated (hscale) ''' + svg_string = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n' + svg_string += '<!-- Created with Python -->\n' + svg_string += '<svg\n' + svg_string += ' xmlns:svg="http://www.w3.org/2000/svg"\n' + svg_string += ' xmlns="http://www.w3.org/2000/svg"\n' + svg_string += ' version="1.0"\n' + svg_string += ' width="%f"\n' % (w * scale) + svg_string += ' height="%f">\n' % (h * scale * hscale) + svg_string += '<g\n transform="matrix(%f,0,0,%f,0,0)">\n' % ( + scale, scale) + return svg_string + + +def svg_footer(): + ''' Returns SVG footer ''' + svg_string = '</g>\n' + svg_string += '</svg>\n' + return svg_string + + +def _svg_style(extras=''): + ''' Returns SVG style for shape rendering ''' + return 'style="%s"/>\n' % (extras) + + +def svg_from_file(pathname): + ''' Read SVG string from a file ''' + f = file(pathname, 'r') + svg = f.read() + f.close() + return(svg) + + +def extract_svg_payload(fd): + """Returns everything between <svg ...> and </svg>""" + payload = '' + looking_for_start_svg_token = True + looking_for_close_token = True + looking_for_end_svg_token = True + for line in fd: + if looking_for_start_svg_token: + if line.find('<svg') < 0: + continue + looking_for_start_svg_token = False + line = line.split('<svg', 1)[1] + if looking_for_close_token: + if line.find('>') < 0: + continue + looking_for_close_token = False + line = line.split('>', 1)[1] + if looking_for_end_svg_token: + if line.find('</svg>') < 0: + payload += line + continue + payload += line.split('</svg>')[0] + break + return payload diff --git a/toolbar_utils.py b/toolbar_utils.py new file mode 100644 index 0000000..9dea658 --- /dev/null +++ b/toolbar_utils.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2011, 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 + +from sugar.graphics.radiotoolbutton import RadioToolButton +from sugar.graphics.toolbutton import ToolButton + +def entry_factory(default_string, toolbar, tooltip='', max=3): + """ Factory for adding a text box to a toolbar """ + entry = gtk.Entry() + entry.set_text(default_string) + if hasattr(entry, 'set_tooltip_text'): + entry.set_tooltip_text(tooltip) + entry.set_width_chars(max) + entry.show() + toolitem = gtk.ToolItem() + toolitem.add(entry) + toolbar.insert(toolitem, -1) + toolitem.show() + return entry + + +def button_factory(icon_name, toolbar, callback, cb_arg=None, tooltip=None, + accelerator=None): + """Factory for making toolbar buttons""" + button = ToolButton(icon_name) + button.set_tooltip(tooltip) + button.props.sensitive = True + if accelerator is not None: + button.props.accelerator = accelerator + if cb_arg is not None: + button.connect('clicked', callback, cb_arg) + else: + button.connect('clicked', callback) + if hasattr(toolbar, 'insert'): # the main toolbar + toolbar.insert(button, -1) + else: # or a secondary toolbar + toolbar.props.page.insert(button, -1) + button.show() + return button + + +def radio_factory(button_name, toolbar, callback, cb_arg=None, tooltip=None, + group=None): + ''' Add a radio button to a toolbar ''' + button = RadioToolButton(group=group) + button.set_named_icon(button_name) + if callback is not None: + if cb_arg is None: + button.connect('clicked', callback) + else: + button.connect('clicked', callback, cb_arg) + if hasattr(toolbar, 'insert'): # Add button to the main toolbar... + toolbar.insert(button, -1) + else: # ...or a secondary toolbar. + toolbar.props.page.insert(button, -1) + button.show() + if tooltip is not None: + button.set_tooltip(tooltip) + return button + + +def label_factory(toolbar, label_text, width=None): + ''' Factory for adding a label to a toolbar ''' + label = gtk.Label(label_text) + label.set_line_wrap(True) + if width is not None: + label.set_size_request(width, -1) # doesn't work on XOs + label.show() + toolitem = gtk.ToolItem() + toolitem.add(label) + toolbar.insert(toolitem, -1) + toolitem.show() + return label + + +def separator_factory(toolbar, expand=False, visible=True): + ''' add a separator to a toolbar ''' + separator = gtk.SeparatorToolItem() + separator.props.draw = visible + separator.set_expand(expand) + toolbar.insert(separator, -1) + separator.show() + + +def image_factory(image, toolbar, tooltip=None): + ''' Add an image to the toolbar ''' + img = gtk.Image() + img.set_from_pixbuf(image) + img_tool = gtk.ToolItem() + img_tool.add(img) + if tooltip is not None: + img.set_tooltip_text(tooltip) + toolbar.insert(img_tool, -1) + img_tool.show() + return img diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..3ffac3d --- /dev/null +++ b/utils.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2011, 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. + +from StringIO import StringIO +try: + USING_JSON_READWRITE = False + import json + json.dumps + from json import load as jload + from json import dump as jdump +except (ImportError, AttributeError): + try: + import simplejson as json + from simplejson import load as jload + from simplejson import dump as jdump + except (ImportError, AttributeError): + USING_JSON_READWRITE = True + + +def json_load(text): + """ Load JSON data using what ever resources are available. """ + if USING_JSON_READWRITE is True: + listdata = json.read(text) + else: + # strip out leading and trailing whitespace, nulls, and newlines + io = StringIO(text) + try: + listdata = jload(io) + except ValueError: + # assume that text is ascii list + listdata = text.split() + for i, value in enumerate(listdata): + listdata[i] = int(value) + return listdata + + +def json_dump(data): + """ Save data using available JSON tools. """ + if USING_JSON_READWRITE is True: + return json.write(data) + else: + _io = StringIO() + jdump(data, _io) + return _io.getvalue() |