# -*- 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 import gobject import os import sugar from sugar.activity import activity from sugar import profile try: from sugar.graphics.toolbarbox import ToolbarBox _have_toolbox = True except ImportError: _have_toolbox = False if _have_toolbox: from sugar.bundle.activitybundle import ActivityBundle from sugar.activity.widgets import ActivityToolbarButton from sugar.activity.widgets import StopButton from sugar.graphics.toolbarbox import ToolbarButton from toolbar_utils import button_factory, separator_factory, label_factory from sprites import Sprites, Sprite from gettext import gettext as _ try: from sugar.graphics import style GRID_CELL_SIZE = style.GRID_CELL_SIZE except ImportError: GRID_CELL_SIZE = 0 SERVICE = 'org.sugarlabs.NapierActivity' IFACE = SERVICE PATH = '/org/augarlabs/NapierActivity' BONE_WIDTH = 101 BONE_HEIGHT = 901 def _svg_header(scale=1.0): ''' Return standard header for SVG bones ''' return '\ \ ' % (101 * scale, 901 * scale) def _svg_footer(): ''' Return standard footer for SVG bones ''' return '\ ' def _svg_single_box(a, scale=1.0): ''' Return standard top-of-column box for SVG bones ''' return ' \ \ \ \ %d\ \ \ ' % (99 * scale, 99 * scale, scale, scale, 2 * scale, 4 * scale, 40 * scale, 55 * scale, 65 * scale, a) def _svg_double_box(a, b, y, scale=1.0): ''' Return standard double-digit box for SVG bones ''' return ' \ \ \ \ \ \ %d\ \ \ \ \ %d\ \ \ ' % (99 * scale, 99 * scale, scale, y * scale, 2 * scale, 4 * scale, scale, (y + 99) * scale, 99 * scale, (y + 1) * scale, 2 * scale, 4 * scale, scale, (y + 1) * scale, 99 * scale, (y + 1) * scale, 2 * scale, 4 * scale, 40 * scale, 12 * scale, (y + 51) * scale, a, 40 * scale, 54 * scale, (y + 85) * scale, b) def _bone_factory(value, scale=1.0): svg = _svg_header(scale=scale) svg += _svg_single_box(value, scale=scale) for i in range(9): if i > 0: j = (i + 1) * value svg += _svg_double_box(int(j / 10), j % 10, i * 100, scale=scale) return svg + _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 _load_svg_from_file(file_path, width, height): '''Create a pixbuf from SVG in a file. ''' return gtk.gdk.pixbuf_new_from_file_at_size(file_path, width, height) class NapierActivity(activity.Activity): ''' Napier's bones: Napier's bones were invented by John Napier (1550-1617), a Scottish mathematician and scientist. They help you to do multiplication. ''' # TODO: Define your own bone. def __init__(self, handle): ''' Initialize the toolbars and the work surface ''' super(NapierActivity, self).__init__(handle) if os.path.exists(os.path.join('~', 'Activities', 'Napier.activity')): self._bone_path = os.path.join('~', 'Activities', 'Napier.activity', 'bones') else: self._bone_path = os.path.join('.', 'bones') self._bones = [] self._bone_images = [None, None, None, None, None, None, None, None, None, None] self._blank_image = None self._number = 0 self._number_of_bones = 0 self._setup_toolbars(_have_toolbox) self._setup_canvas() self._circles = [None, None] self._ovals = [] self._setup_workspace() self._restore() def _setup_canvas(self): ''' Create a canvas ''' self._canvas = gtk.DrawingArea() self._canvas.set_size_request(gtk.gdk.screen_width(), gtk.gdk.screen_height()) self.set_canvas(self._canvas) self._canvas.show() self.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.add_events(gtk.gdk.POINTER_MOTION_MASK) self._canvas.connect("expose-event", self._expose_cb) self._canvas.connect("motion-notify-event", self._mouse_move_cb) # self._canvas.connect("key_press_event", self._key_press_cb) def _setup_workspace(self): ''' Add the bones. ''' self._width = gtk.gdk.screen_width() self._height = int(gtk.gdk.screen_height() - (GRID_CELL_SIZE * 2)) self._scale = self._height * 1.0 / BONE_HEIGHT self._bone_width = int(BONE_WIDTH * self._scale) self._bone_height = int(BONE_HEIGHT * self._scale) # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._bone_index = Sprite(self._sprites, 0, 0, _load_svg_from_file( os.path.join(self._bone_path, 'bones-index.svg'), self._bone_width, self._bone_height)) self._max_bones = int(self._width / self._bone_width) - 1 self._blank_image = _load_svg_from_file( os.path.join(self._bone_path, 'blank-bone.svg'), self._bone_width, self._bone_height) for bones in range(self._max_bones): self._bones.append(Sprite(self._sprites, bones * self._bone_width, 0, self._blank_image)) circle_image = _load_svg_from_file( os.path.join(self._bone_path, 'circle.svg'), int(self._scale * 45), int(self._scale * 45)) self._circles[0] = Sprite(self._sprites, 0, -100, circle_image) self._circles[1] = Sprite(self._sprites, 0, -100, circle_image) oval_image = _load_svg_from_file( os.path.join(self._bone_path, 'oval.svg'), int(self._scale * 129), int(self._scale * 92)) for bones in range(self._max_bones - 1): self._ovals.append(Sprite(self._sprites, 0, -100, oval_image)) def _setup_toolbars(self, have_toolbox): ''' Setup the toolbars. ''' self.max_participants = 1 # no sharing if have_toolbox: toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() self._bones_toolbar = gtk.Toolbar() self._bones_toolbar_button = ToolbarButton(label=_('Select a bone'), page=self._bones_toolbar, icon_name='bones') self._bones_toolbar_button.show() toolbox.toolbar.insert(self._bones_toolbar_button, -1) self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar else: # Use pre-0.86 toolbar design self._bones_toolbar = gtk.Toolbar() self.toolbar = gtk.Toolbar() toolbox = activity.ActivityToolbox(self) self.set_toolbox(toolbox) toolbox.add_toolbar(_('Bones'), self._bones_toolbar) toolbox.add_toolbar(_('Results'), self.toolbar) toolbox.show() toolbox.set_current_toolbar(1) separator_factory(self.toolbar) self._new_calc_button = button_factory( 'erase', self.toolbar, self._new_calc_cb, tooltip=_('Clear')) self._status = label_factory(self.toolbar, '') button_factory('number-0', self._bones_toolbar, self._number_cb, cb_arg=0, tooltip=_('zero')) button_factory('number-1', self._bones_toolbar, self._number_cb, cb_arg=1, tooltip=_('one')) button_factory('number-2', self._bones_toolbar, self._number_cb, cb_arg=2, tooltip=_('two')) button_factory('number-3', self._bones_toolbar, self._number_cb, cb_arg=3, tooltip=_('three')) button_factory('number-4', self._bones_toolbar, self._number_cb, cb_arg=4, tooltip=_('four')) button_factory('number-5', self._bones_toolbar, self._number_cb, cb_arg=5, tooltip=_('five')) button_factory('number-6', self._bones_toolbar, self._number_cb, cb_arg=6, tooltip=_('six')) button_factory('number-7', self._bones_toolbar, self._number_cb, cb_arg=7, tooltip=_('seven')) button_factory('number-8', self._bones_toolbar, self._number_cb, cb_arg=8, tooltip=_('eight')) button_factory('number-9', self._bones_toolbar, self._number_cb, cb_arg=9, tooltip=_('nine')) if _have_toolbox: separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = 'q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() self._bones_toolbar_button.set_expanded(True) def _new_calc_cb(self, button=None): ''' Start a new calculation. ''' for bone in range(self._max_bones): self._bones[bone].set_shape(self._blank_image) self._bones[bone].inval() self._number_of_bones = 0 self._number = 0 self._status.set_label('') return def _number_cb(self, button=None, value=0): ''' Try to add a digit. ''' if self._number_of_bones == self._max_bones: return self._number_of_bones += 1 if self._bone_images[value] is None: self._bone_images[value] = _svg_str_to_pixbuf( _bone_factory(value, scale=self._scale)) self._bones[self._number_of_bones].set_shape(self._bone_images[value]) self._bones[self._number_of_bones].inval() self._number = self._number * 10 + value def _mouse_move_cb(self, win, event): ''' Determine which row we are in and then calculate the product. ''' win.grab_focus() x, y = map(int, event.get_coords()) factor = int(y / self._bone_width) # The row determines a factor if self._number == 0 or factor == 0: self._status.set_label('') self._circles[0].move((0, -100)) self._circles[1].move((0, -100)) for number in range(self._max_bones - 1): self._ovals[number].move((0, -100)) else: c0dx = int(4 * self._scale) c0dy = int(12 * self._scale) c1dx = int(44 * self._scale) c1dy = int(47 * self._scale) odx = int(42 * self._scale) ody = int(2 * self._scale) self._circles[0].move((self._bone_width + c0dx, factor * self._bone_width + c0dy)) self._circles[1].move(( self._number_of_bones * self._bone_width + c1dx, factor * self._bone_width + c1dy)) for number in range(self._number_of_bones - 1): self._ovals[number].move(((number + 1) * self._bone_width + odx, factor * self._bone_width + ody)) self._status.set_label('%d × %d = %d' % ( factor + 1, self._number, (factor + 1) * self._number)) return True def _key_press_cb(self, win, event): ''' TODO: Add bones by typing numbers ''' return True def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() 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): gtk.main_quit() def _restore(self): ''' Try to restore previous state. ''' if 'number' in self.metadata and self.metadata['number'] != '0': for digit in range(len(self.metadata['number'])): self._number_cb(button=None, value=int(self.metadata['number'][digit])) def write_file(self, file_path): ''' Write the status to the Journal. ''' if not hasattr(self, '_number'): return self.metadata['number'] = str(self._number)