Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWalter Bender <walter@sugarlabs.org>2010-04-02 16:27:20 (GMT)
committer Walter Bender <walter@sugarlabs.org>2010-04-02 16:27:20 (GMT)
commit8162f477074c485f417ffc7e5b2eb0f1022d3068 (patch)
treeaf0ee443b1537019e5743cf40aa70c93ca80b0ae
New project
-rw-r--r--.abacusrc4
-rw-r--r--AbacusActivity.py235
-rw-r--r--COPYING12
-rw-r--r--NEWS6
-rwxr-xr-xabacus.py161
-rw-r--r--abacus_window.py507
-rw-r--r--activity/activity-abacus.svg187
-rw-r--r--activity/activity.info8
-rw-r--r--constants.py26
-rw-r--r--icons/Coff.svg184
-rw-r--r--icons/Con.svg184
-rw-r--r--icons/Joff.svg192
-rw-r--r--icons/Jon.svg192
-rw-r--r--icons/Roff.svg198
-rw-r--r--icons/Ron.svg198
-rw-r--r--images/black.svg22
-rw-r--r--images/chinese_rod.svg15
-rw-r--r--images/divider_bar.svg15
-rw-r--r--images/japanese_rod.svg15
-rw-r--r--images/russian_rod.svg15
-rw-r--r--images/white.svg22
-rwxr-xr-xsetup.py5
-rw-r--r--sprites.py367
23 files changed, 2770 insertions, 0 deletions
diff --git a/.abacusrc b/.abacusrc
new file mode 100644
index 0000000..9373b51
--- /dev/null
+++ b/.abacusrc
@@ -0,0 +1,4 @@
+50
+50
+800
+550
diff --git a/AbacusActivity.py b/AbacusActivity.py
new file mode 100644
index 0000000..fd5f7e2
--- /dev/null
+++ b/AbacusActivity.py
@@ -0,0 +1,235 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2010, 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 pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+
+import sugar
+from sugar.activity import activity
+try: # 0.86+ toolbar widgets
+ from sugar.bundle.activitybundle import ActivityBundle
+ from sugar.activity.widgets import ActivityToolbarButton
+ from sugar.activity.widgets import StopButton
+ from sugar.graphics.toolbarbox import ToolbarBox
+ from sugar.graphics.toolbarbox import ToolbarButton
+except ImportError:
+ pass
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.icon import Icon
+from sugar.datastore import datastore
+
+from gettext import gettext as _
+import locale
+import os.path
+
+import logging
+_logger = logging.getLogger("abacus-activity")
+
+from abacus_window import Abacus
+
+#
+# Sugar activity
+#
+class AbacusActivity(activity.Activity):
+
+ def __init__(self, handle):
+ super(AbacusActivity,self).__init__(handle)
+
+ try:
+ # Use 0.86 toolbar design
+ toolbar_box = ToolbarBox()
+
+ # Buttons added to the Activity toolbar
+ activity_button = ActivityToolbarButton(self)
+ toolbar_box.toolbar.insert(activity_button, 0)
+ activity_button.show()
+
+ # Suanpan (Chinese abacus) 2:5
+ self.chinese = ToolButton( "Con" )
+ self.chinese.set_tooltip(_('suanpan'))
+ self.chinese.props.sensitive = True
+ self.chinese.connect('clicked', self._chinese_cb)
+ toolbar_box.toolbar.insert(self.chinese, -1)
+ self.chinese.show()
+
+ # Soroban (Japanese abacus) 1:4
+ self.japanese = ToolButton( "Joff" )
+ self.japanese.set_tooltip(_('soroban'))
+ self.japanese.props.sensitive = True
+ self.japanese.connect('clicked', self._japanese_cb)
+ toolbar_box.toolbar.insert(self.japanese, -1)
+ self.japanese.show()
+
+ # Schety (Russian abacus) 0:10
+ self.russian = ToolButton( "Roff" )
+ self.russian.set_tooltip(_('schety'))
+ self.russian.props.sensitive = True
+ self.russian.connect('clicked', self._russian_cb)
+ toolbar_box.toolbar.insert(self.russian, -1)
+ self.russian.show()
+
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ separator.show()
+ toolbar_box.toolbar.insert(separator, -1)
+
+ # The ever-present Stop Button
+ stop_button = StopButton(self)
+ stop_button.props.accelerator = '<Ctrl>Q'
+ toolbar_box.toolbar.insert(stop_button, -1)
+ stop_button.show()
+
+ self.set_toolbar_box(toolbar_box)
+ toolbar_box.show()
+
+ except NameError:
+ # Use pre-0.86 toolbar design
+ self.toolbox = activity.ActivityToolbox(self)
+ self.set_toolbox(self.toolbox)
+
+ self.projectToolbar = ProjectToolbar(self)
+ self.toolbox.add_toolbar( _('Project'), self.projectToolbar )
+
+ self.toolbox.show()
+
+ # Create a canvas
+ canvas = gtk.DrawingArea()
+ canvas.set_size_request(gtk.gdk.screen_width(), \
+ gtk.gdk.screen_height())
+ self.set_canvas(canvas)
+ canvas.show()
+ self.show_all()
+
+ # Initialize the canvas
+ self.abacus = Abacus(canvas, os.path.join(activity.get_bundle_path(),
+ 'images/'), self)
+
+ """
+ # Read the slider positions from the Journal
+ try:
+ self.tw.A.spr.move_relative((int(self.metadata['A']),0))
+ self.tw.C.spr.move_relative((int(self.metadata['C']),0))
+ self.tw.C_tab_left.spr.move_relative((int(self.metadata['C']),0))
+ self.tw.C_tab_right.spr.move_relative((int(self.metadata['C'])+\
+ SWIDTH-100,0))
+ self.tw.D.spr.move_relative((int(self.metadata['D']),0))
+ self.tw.R.spr.move_relative((int(self.metadata['R']),0))
+ self.tw.R_tab_top.spr.move_relative((int(self.metadata['R']),0))
+ self.tw.R_tab_bot.spr.move_relative((int(self.metadata['R']),0))
+ self.tw.slider_on_top = self.metadata['slider']
+ if self.tw.slider_on_top == 'A':
+ self._show_a()
+ else:
+ self._show_c()
+ window._update_results_label(self.tw)
+ window._update_slider_labels(self.tw)
+ except:
+ self._show_c()
+ """
+
+ def _chinese_cb(self, button):
+ self._show_c()
+ return True
+
+ def _show_c(self):
+ self.chinese.set_icon("Con")
+ self.japanese.set_icon("Joff")
+ self.russian.set_icon("Roff")
+ self.abacus.chinese.show()
+ self.abacus.japanese.hide()
+ self.abacus.russian.hide()
+ self.abacus.mode = self.abacus.chinese
+
+ def _japanese_cb(self, button):
+ self._show_j()
+ return True
+
+ def _show_j(self):
+ self.chinese.set_icon("Coff")
+ self.japanese.set_icon("Jon")
+ self.russian.set_icon("Roff")
+ self.abacus.chinese.hide()
+ self.abacus.japanese.show()
+ self.abacus.russian.hide()
+ self.abacus.mode = self.abacus.japanese
+
+ def _russian_cb(self, button):
+ self._show_r()
+ return True
+
+ def _show_r(self):
+ self.chinese.set_icon("Coff")
+ self.japanese.set_icon("Joff")
+ self.russian.set_icon("Ron")
+ self.abacus.chinese.hide()
+ self.abacus.japanese.hide()
+ self.abacus.russian.show()
+ self.abacus.mode = self.abacus.russian
+
+ """
+ Write the slider positions to the Journal
+ """
+ """
+ def write_file(self, file_path):
+ _logger.debug("Write slider on top: " + self.tw.slider_on_top)
+ self.metadata['slider'] = self.tw.slider_on_top
+ x,y = self.tw.A.spr.get_xy()
+ _logger.debug("Write A offset: " + str(x))
+ self.metadata['A'] = str(x)
+ x,y = self.tw.C.spr.get_xy()
+ _logger.debug("Write C offset: " + str(x))
+ self.metadata['C'] = str(x)
+ x,y = self.tw.D.spr.get_xy()
+ _logger.debug("Write D offset: " + str(x))
+ self.metadata['D'] = str(x)
+ x,y = self.tw.R.spr.get_xy()
+ _logger.debug("Write r offset: " + str(x))
+ self.metadata['R'] = str(x)
+ """
+
+#
+# Project toolbar for pre-0.86 toolbars
+#
+class ProjectToolbar(gtk.Toolbar):
+
+ def __init__(self, pc):
+ gtk.Toolbar.__init__(self)
+ self.activity = pc
+
+ # Chinese style
+ self.activity.chinese = ToolButton( "Con" )
+ self.activity.chinese.set_tooltip(_('saunpan'))
+ self.activity.chinese.props.sensitive = True
+ self.activity.chinese.connect('clicked', self.activity._chinese_cb)
+ self.insert(self.activity.chinese, -1)
+ self.activity.chinese.show()
+
+ # Japanese style
+ self.activity.japanese = ToolButton( "Joff" )
+ self.activity.japanese.set_tooltip(_('soroban'))
+ self.activity.japanese.props.sensitive = True
+ self.activity.japanese.connect('clicked', self.activity._japanese_cb)
+ self.insert(self.activity.japanese, -1)
+ self.activity.japanese.show()
+
+ # Russian style
+ self.activity.russian = ToolButton( "Roff" )
+ self.activity.russian.set_tooltip(_('schety'))
+ self.activity.russian.props.sensitive = True
+ self.activity.russian.connect('clicked', self.activity._russian_cb)
+ self.insert(self.activity.russian, -1)
+ self.activity.russian.show()
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..fb332e0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,12 @@
+Copyright (c) 2010, 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.
+
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..eb3d224
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,6 @@
+1
+
+* Abacus activity
+* Chinese (suanpan), Japanese (soroban), and Russian (schety) models
+
+
diff --git a/abacus.py b/abacus.py
new file mode 100755
index 0000000..098e73b
--- /dev/null
+++ b/abacus.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+
+#Copyright (c) 2010, 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 pygtk
+pygtk.require('2.0')
+import gtk
+
+from gettext import gettext as _
+import os
+
+from abacus_window import Abacus
+
+def makepath(path):
+ """ Make a path if it doesn't previously exist """
+ from os import makedirs
+ from os.path import normpath, dirname, exists
+
+ dpath = normpath(dirname(path))
+ if not exists(dpath):
+ makedirs(dpath)
+
+class AbacusMain:
+ def __init__(self):
+ self.r = 0
+ self.tw = None
+
+ self.win = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ try:
+ data_file = open('.abacusrc', 'r')
+ except IOError:
+ data_file = open('.abacusrc', 'a+')
+ data_file.write(str(50)+'\n')
+ data_file.write(str(50)+'\n')
+ data_file.write(str(800)+'\n')
+ data_file.write(str(550)+'\n')
+ data_file.seek(0)
+ self.x = int(data_file.readline())
+ self.y = int(data_file.readline())
+ self.width = int(data_file.readline())
+ self.height = int(data_file.readline())
+ self.win.set_default_size(self.width, self.height)
+ self.win.move(self.x, self.y)
+ self.win.maximize()
+ self.win.set_title(_("Abacus"))
+ self.win.connect("delete_event", lambda w,e: gtk.main_quit())
+
+ menu = gtk.Menu()
+ menu_items = gtk.MenuItem(_("Saunpan"))
+ menu.append(menu_items)
+ menu_items.connect("activate", self._c_cb)
+ menu_items.show()
+ menu_items = gtk.MenuItem(_("Soroban"))
+ menu.append(menu_items)
+ menu_items.connect("activate", self._j_cb)
+ menu_items.show()
+ menu_items = gtk.MenuItem(_("Schety"))
+ menu.append(menu_items)
+ menu_items.connect("activate", self._r_cb)
+ menu_items.show()
+ menu_items = gtk.MenuItem(_("Quit"))
+ menu.append(menu_items)
+ menu_items.connect("activate", self.destroy)
+ menu_items.show()
+ root_menu = gtk.MenuItem("Tools")
+ root_menu.show()
+ root_menu.set_submenu(menu)
+
+ # A vbox to put a menu and the canvas in:
+ vbox = gtk.VBox(False, 0)
+ self.win.add(vbox)
+ vbox.show()
+
+ menu_bar = gtk.MenuBar()
+ vbox.pack_start(menu_bar, False, False, 2)
+ menu_bar.show()
+
+ menu_bar.append(root_menu)
+
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ sw.show()
+ canvas = gtk.DrawingArea()
+ width = gtk.gdk.screen_width()
+ height = gtk.gdk.screen_height()
+ canvas.set_size_request(width, height)
+ sw.add_with_viewport(canvas)
+ canvas.show()
+ vbox.pack_end(sw, True, True)
+
+ self.win.show_all()
+
+ if os.path.exists('/usr/share/abacus/images/'):
+ self.abacus = Abacus(canvas, '/usr/share/abacus/images/')
+ elif os.path.exists('/usr/local/share/abacus/images/'):
+ self.abacus = Abacus(canvas, '/usr/local/share/abacus/images/')
+ else:
+ self.abacus = Abacus(canvas, os.path.abspath('.')+'/images/')
+ self.abacus.win = self.win
+
+ """
+
+ canvas = gtk.DrawingArea()
+ vbox.pack_end(canvas, True, True)
+ canvas.show()
+
+ menu_bar.append(root_menu)
+ self.win.show_all()
+
+ # Join the activity
+ self.abacus = Abacus(canvas, os.path.join(os.path.abspath('.'),
+ 'images/'))
+ """
+
+ self.abacus.activity = self
+
+ def set_title(self, title):
+ self.win.set_title(title)
+
+ def _c_cb(self, widget):
+ self.abacus.chinese.show()
+ self.abacus.japanese.hide()
+ self.abacus.russian.hide()
+ self.abacus.mode = self.abacus.chinese
+ return True
+
+ def _j_cb(self, widget):
+ self.abacus.chinese.hide()
+ self.abacus.japanese.show()
+ self.abacus.russian.hide()
+ self.abacus.mode = self.abacus.japanese
+ return True
+
+ def _r_cb(self, widget):
+ self.abacus.chinese.hide()
+ self.abacus.japanese.hide()
+ self.abacus.russian.show()
+ self.abacus.mode = self.abacus.russian
+ return True
+
+ def destroy(self, event, data=None):
+ """ Callback for destroy event. """
+ gtk.main_quit()
+
+def main():
+ gtk.main()
+ return 0
+
+if __name__ == "__main__":
+ AbacusMain()
+ main()
diff --git a/abacus_window.py b/abacus_window.py
new file mode 100644
index 0000000..32aa053
--- /dev/null
+++ b/abacus_window.py
@@ -0,0 +1,507 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2010, 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 constants import *
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+from gettext import gettext as _
+import math
+import os
+
+try:
+ from sugar.graphics import style
+ GRID_CELL_SIZE = style.GRID_CELL_SIZE
+except:
+ GRID_CELL_SIZE = 0
+
+from sprites import *
+
+def load_image(path, name, w, h):
+ """ create a pixbuf from a SVG stored in a file """
+ return gtk.gdk.pixbuf_new_from_file_at_size(
+ os.path.join(path+name+'.svg'), int(w), int(h))
+
+class Abacus():
+
+ def __init__(self, canvas, path, parent=None):
+ """ Abacus class """
+ self.path = path
+ 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.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("button-press-event", self._button_press_cb)
+ self.canvas.connect("button-release-event", self._button_release_cb)
+ self.canvas.connect("motion-notify-event", self._mouse_move_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.dragpos = 0
+ self.press = None
+
+ self.chinese = Suanpan(self)
+ self.japanese = Soroban(self)
+ self.russian = Schety(self)
+
+ self.chinese.show()
+ self.japanese.hide()
+ self.russian.hide()
+ self.mode = self.chinese
+
+ def _button_press_cb(self, win, event):
+ win.grab_focus()
+ x, y = map(int, event.get_coords())
+ self.dragpos = y
+ self.press = self.sprites.find_sprite((x,y))
+ if self.press is not None and self.press.type != 'bead':
+ self.press = None
+ return True
+
+ def _mouse_move_cb(self, win, event):
+ if self.press is None:
+ self.dragpos = 0
+ return True
+ win.grab_focus()
+ x, y = map(int, event.get_coords())
+ dy = y-self.dragpos
+ self.mode.move_bead(self.press, dy)
+
+ def _button_release_cb(self, win, event):
+ if self.press == None:
+ return True
+ self.press = None
+ self.mode.label(self.mode.value())
+ return True
+
+ def _expose_cb(self, win, event):
+ self.sprites.redraw_sprites()
+ return True
+
+ def _destroy_cb(self, win, event):
+ gtk.main_quit()
+
+
+class Suanpan():
+
+ def __init__(self, abacus):
+ """ create a Chinese abacus: 15 by (5,2) """
+ self.abacus = abacus
+ self.num_rods = 15
+ self.bot_beads = 5
+ self.top_beads = 2
+ # 5 beads + 2 spaces, divider, 2 bead + 2 spaces
+ h = (self.bot_beads+2+1+self.top_beads+2)*BHEIGHT*self.abacus.scale
+ w = (self.num_rods+1)*(BWIDTH+BOFFSET)*self.abacus.scale
+ dy = 4*BHEIGHT*self.abacus.scale
+ x = (self.abacus.width-w)/2
+ y = (self.abacus.height-h)/2
+
+ # Draw the rods...
+ self.rods = []
+ o = (BWIDTH+BOFFSET-5)*self.abacus.scale/2
+ dx = (BWIDTH+BOFFSET)*self.abacus.scale
+ for i in range(self.num_rods):
+ self.rods.append(Sprite(self.abacus.sprites, x+i*dx+o, y,
+ load_image(self.abacus.path, "chinese_rod",
+ 10*self.abacus.scale, h)))
+
+ for i in self.rods:
+ i.type = 'rod'
+
+ # and then the beads.
+ self.beads = []
+ o = (BWIDTH-BOFFSET)/2*self.abacus.scale/2
+ for i in range(self.num_rods):
+ for b in range(self.top_beads):
+ self.beads.append(Sprite(self.abacus.sprites, x+i*dx+o,
+ y+b*BHEIGHT*self.abacus.scale,
+ load_image(self.abacus.path, "white",
+ BWIDTH*self.abacus.scale,
+ BHEIGHT*self.abacus.scale)))
+ for b in range(self.bot_beads):
+ self.beads.append(Sprite(self.abacus.sprites, x+i*dx+o,
+ y+(7+b)*BHEIGHT*self.abacus.scale,
+ load_image(self.abacus.path, "white",
+ BWIDTH*self.abacus.scale,
+ BHEIGHT*self.abacus.scale)))
+
+ for i in self.beads:
+ i.type = 'bead'
+ i.state = 0
+
+ # Draw the dividing bar on top
+ self.bar = Sprite(self.abacus.sprites, x, y+dy,
+ load_image(self.abacus.path, "divider_bar",
+ w, BHEIGHT*self.abacus.scale))
+
+ self.bar.type = 'frame'
+ self.bar.set_label_color('white')
+
+ def value(self):
+ """ Return a string representing the value of each rod """
+ string = ''
+ v = []
+ for r in range(self.num_rods+1): # +1 for overflow
+ v.append(0)
+
+ # Tally the values on each rod.
+ for i, b in enumerate(self.beads):
+ r = i/(self.top_beads+self.bot_beads)
+ j = i%(self.top_beads+self.bot_beads)
+ if b.state == 1:
+ if j < self.top_beads:
+ v[r+1] += 5
+ else:
+ v[r+1] += 1
+
+ # Carry to the left if a rod has a value > 9.
+ for j in range(self.num_rods):
+ if v[len(v)-j-1] > 9:
+ v[len(v)-j-1] -= 10
+ v[len(v)-j-2] += 1
+
+ # Convert values to a string.
+ for j in v:
+ if string != '' or j > 0:
+ string += str(j)
+ return(string)
+
+ def hide(self):
+ for i in self.rods:
+ i.hide()
+ for i in self.beads:
+ i.hide()
+ self.bar.hide()
+
+ def show(self):
+ for i in self.rods:
+ i.set_layer(100)
+ for i in self.beads:
+ i.set_layer(101)
+ self.bar.set_layer(102)
+
+ def label(self, string):
+ """ Label the crossbar with the string. (USed with self.value) """
+ self.bar.set_label(string)
+
+ def move_bead(self, bead, dy):
+ """ Move a bead (or beads) up or down a rod. """
+ i = self.beads.index(bead)
+ b = i%(self.top_beads+self.bot_beads)
+
+ if b < self.top_beads:
+ if dy > 0 and bead.state == 0:
+ bead.move_relative((0, 2*BHEIGHT*self.abacus.scale))
+ bead.state = 1
+ # make sure beads below this bead are also moved
+ if b == 0 and self.beads[i+1].state == 0:
+ self.beads[i+1].move_relative((0,
+ 2*BHEIGHT*self.abacus.scale))
+ self.beads[i+1].state = 1
+ elif dy < 0 and bead.state == 1:
+ bead.move_relative((0, -2*BHEIGHT*self.abacus.scale))
+ # make sure beads above this bead are also moved
+ if b == 1 and self.beads[i-1].state == 1:
+ self.beads[i-1].move_relative((0,
+ -2*BHEIGHT*self.abacus.scale))
+ self.beads[i-1].state = 0
+ bead.state = 0
+ else:
+ if dy < 0 and bead.state == 0:
+ bead.move_relative((0, -2*BHEIGHT*self.abacus.scale))
+ bead.state = 1
+ # make sure beads above this bead are also moved
+ for ii in range(b-self.top_beads+1):
+ if self.beads[i-ii].state == 0:
+ self.beads[i-ii].move_relative((0,
+ -2*BHEIGHT*self.abacus.scale))
+ self.beads[i-ii].state = 1
+ elif dy > 0 and bead.state == 1:
+ bead.move_relative((0, 2*BHEIGHT*self.abacus.scale))
+ bead.state = 0
+ # make sure beads below this bead are also moved
+ for ii in range(self.top_beads+self.bot_beads-b):
+ if self.beads[i+ii].state == 1:
+ self.beads[i+ii].move_relative((0,
+ 2*BHEIGHT*self.abacus.scale))
+ self.beads[i+ii].state = 0
+
+
+class Soroban():
+
+ def __init__(self, abacus):
+ """ create a Japanese abacus: 15 by (4,1) """
+ self.abacus = abacus
+ self.num_rods = 15
+ self.bot_beads = 4
+ self.top_beads = 1
+ # 4 beads + 2 spaces, divider, 1 bead + 2 spaces
+ h = (self.bot_beads+2+1+self.top_beads+2)*BHEIGHT*self.abacus.scale
+ w = (self.num_rods+1)*(BWIDTH+BOFFSET)*self.abacus.scale
+ dy = 3*BHEIGHT*self.abacus.scale
+ x = (self.abacus.width-w)/2
+ y = (self.abacus.height-h)/2
+
+ # Draw the rods...
+ self.rods = []
+ o = (BWIDTH+BOFFSET-5)*self.abacus.scale/2
+ dx = (BWIDTH+BOFFSET)*self.abacus.scale
+ for i in range(self.num_rods):
+ self.rods.append(Sprite(self.abacus.sprites, x+i*dx+o, y,
+ load_image(self.abacus.path, "japanese_rod",
+ 10, h)))
+
+ for i in self.rods:
+ i.type = 'rod'
+
+ # and then the beads.
+ self.beads = []
+ o = (BWIDTH-BOFFSET)/2*self.abacus.scale/2
+ for i in range(self.num_rods):
+ for b in range(self.top_beads):
+ self.beads.append(Sprite(self.abacus.sprites, x+i*dx+o,
+ y+b*BHEIGHT*self.abacus.scale,
+ load_image(self.abacus.path, "white",
+ BWIDTH*self.abacus.scale,
+ BHEIGHT*self.abacus.scale)))
+ for b in range(self.bot_beads):
+ self.beads.append(Sprite(self.abacus.sprites, x+i*dx+o,
+ y+(6+b)*BHEIGHT*self.abacus.scale,
+ load_image(self.abacus.path, "white",
+ BWIDTH*self.abacus.scale,
+ BHEIGHT*self.abacus.scale)))
+
+ for i in self.beads:
+ i.type = 'bead'
+ i.state = 0
+
+ # Draw the dividing bar on top
+ self.bar = Sprite(self.abacus.sprites, x, y+dy,
+ load_image(self.abacus.path, "divider_bar",
+ w, BHEIGHT*self.abacus.scale))
+
+ self.bar.type = 'frame'
+ self.bar.set_label_color('white')
+
+ def hide(self):
+ for i in self.rods:
+ i.hide()
+ for i in self.beads:
+ i.hide()
+ self.bar.hide()
+
+ def show(self):
+ for i in self.rods:
+ i.set_layer(100)
+ for i in self.beads:
+ i.set_layer(101)
+ self.bar.set_layer(102)
+
+ def value(self):
+ """ Return a string with the value of each rod. """
+ string = ''
+ v = 0
+ for i, b in enumerate(self.beads):
+ if b.state == 1:
+ if i%(self.top_beads+self.bot_beads) < self.top_beads:
+ v += 5
+ else:
+ v += 1
+ if i%(self.top_beads+self.bot_beads) == \
+ self.top_beads+self.bot_beads-1:
+ if string != '' or v > 0:
+ string += str(v)
+ v = 0
+ return(string)
+
+ def label(self, string):
+ """ Label the crossbar with the string. (USed with self.value) """
+ self.bar.set_label(string)
+
+ def move_bead(self, bead, dy):
+ """ Move a bead (or beads) up or down a rod. """
+ i = self.beads.index(bead)
+ b = i%(self.top_beads+self.bot_beads)
+
+ if b < self.top_beads:
+ if dy > 0 and bead.state == 0:
+ bead.move_relative((0, 2*BHEIGHT*self.abacus.scale))
+ bead.state = 1
+ elif dy < 0 and bead.state == 1:
+ bead.move_relative((0, -2*BHEIGHT*self.abacus.scale))
+ bead.state = 0
+ else:
+ if dy < 0 and bead.state == 0:
+ bead.move_relative((0, -2*BHEIGHT*self.abacus.scale))
+ bead.state = 1
+ # make sure beads above this bead are also moved
+ for ii in range(b-self.top_beads+1):
+ if self.beads[i-ii].state == 0:
+ self.beads[i-ii].move_relative((0,
+ -2*BHEIGHT*self.abacus.scale))
+ self.beads[i-ii].state = 1
+ elif dy > 0 and bead.state == 1:
+ bead.move_relative((0, 2*BHEIGHT*self.abacus.scale))
+ bead.state = 0
+ # make sure beads below this bead are also moved
+ for ii in range(self.top_beads+self.bot_beads-b):
+ if self.beads[i+ii].state == 1:
+ self.beads[i+ii].move_relative((0,
+ 2*BHEIGHT*self.abacus.scale))
+ self.beads[i+ii].state = 0
+
+
+class Schety():
+
+ def __init__(self, abacus):
+ """ create a Russian abacus: 15 by 10 """
+ self.abacus = abacus
+ self.num_rods = 15
+ self.bot_beads = 10
+ # 10 beads + 2 spaces
+ h = (self.bot_beads+2)*BHEIGHT*self.abacus.scale
+ w = (self.num_rods+1)*(BWIDTH+BOFFSET)*self.abacus.scale
+ x = (self.abacus.width-w)/2
+ y = (self.abacus.height-h)/2
+
+ # Draw the rods...
+ self.rods = []
+ o = (BWIDTH+BOFFSET-5)*self.abacus.scale/2
+ dx = (BWIDTH+BOFFSET)*self.abacus.scale
+ for i in range(self.num_rods):
+ self.rods.append(Sprite(self.abacus.sprites, x+i*dx+o, y,
+ load_image(self.abacus.path, "russian_rod",
+ 10, h)))
+
+ for i in self.rods:
+ i.type = 'rod'
+
+ # and then the beads.
+ self.beads = []
+ o = (BWIDTH-BOFFSET)/2*self.abacus.scale/2
+ for i in range(self.num_rods):
+ if i == 10:
+ for b in range(4):
+ if b in [1,2]:
+ color = 'black'
+ else:
+ color = 'white'
+ self.beads.append(Sprite(self.abacus.sprites, x+i*dx+o,
+ y+(8+b)*BHEIGHT*self.abacus.scale,
+ load_image(self.abacus.path, color,
+ BWIDTH*self.abacus.scale,
+ BHEIGHT*self.abacus.scale)))
+ else:
+ for b in range(self.bot_beads):
+ if b in [4,5]:
+ color = 'black'
+ else:
+ color = 'white'
+ self.beads.append(Sprite(self.abacus.sprites, x+i*dx+o,
+ y+(2+b)*BHEIGHT*self.abacus.scale,
+ load_image(self.abacus.path, color,
+ BWIDTH*self.abacus.scale,
+ BHEIGHT*self.abacus.scale)))
+
+ for i in self.beads:
+ i.type = 'bead'
+ i.state = 0
+
+ # Draw a bar for the label on top
+ self.bar = Sprite(self.abacus.sprites, x, y-BHEIGHT*self.abacus.scale,
+ load_image(self.abacus.path, "divider_bar",
+ w, BHEIGHT*self.abacus.scale))
+
+ self.bar.type = 'frame'
+ self.bar.set_label_color('white')
+
+ def hide(self):
+ for i in self.rods:
+ i.hide()
+ for i in self.beads:
+ i.hide()
+ self.bar.hide()
+
+ def show(self):
+ for i in self.rods:
+ i.set_layer(100)
+ for i in self.beads:
+ i.set_layer(101)
+ self.bar.set_layer(102)
+
+ def value(self):
+ # TODO: cascade and account for short rod
+ """ Return a string with the value of each rod. """
+ string = ''
+ v = 0
+ for i, b in enumerate(self.beads):
+ if b.state == 1:
+ v += 1
+ if i%self.bot_beads == self.bot_beads-1:
+ if string != '' or v > 0:
+ string += str(v)
+ v = 0
+ return(string)
+
+ def label(self, string):
+ """ Label the crossbar with the string. (USed with self.value) """
+ self.bar.set_label(string)
+
+ def move_bead(self, bead, dy):
+ # TODO: take into account short rod in rod calculations
+ i = self.beads.index(bead)
+ r = i/self.bot_beads
+ if r < 10:
+ o = 2
+ b = i%self.bot_beads
+ n = self.bot_beads
+ elif r == 10:
+ o = 8
+ b = i%self.bot_beads
+ n = 4
+ else:
+ s = 2
+ b = (i+6)%self.bot_beads
+ n = self.bot_beads
+ if dy < 0 and bead.state == 0:
+ bead.move_relative((0, -o*BHEIGHT*self.abacus.scale))
+ bead.state = 1
+ # make sure beads above this bead are also moved
+ for ii in range(b+1):
+ if self.beads[i-ii].state == 0:
+ self.beads[i-ii].move_relative((0,
+ -o*BHEIGHT*self.abacus.scale))
+ self.beads[i-ii].state = 1
+ elif dy > 0 and bead.state == 1:
+ bead.move_relative((0, o*BHEIGHT*self.abacus.scale))
+ bead.state = 0
+ # make sure beads below this bead are also moved
+ for ii in range(n-b):
+ if self.beads[i+ii].state == 1:
+ self.beads[i+ii].move_relative((0,
+ o*BHEIGHT*self.abacus.scale))
+ self.beads[i+ii].state = 0
diff --git a/activity/activity-abacus.svg b/activity/activity-abacus.svg
new file mode 100644
index 0000000..8e4e512
--- /dev/null
+++ b/activity/activity-abacus.svg
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY fill_color "#AAAAAA">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg xmlns="http://www.w3.org/2000/svg" width="45" height="45">
+ <rect
+ width="37.959229"
+ height="43"
+ x="-41.478813"
+ y="1"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="rect4"
+ style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2" />
+ <line
+ y1="14"
+ y2="14"
+ x1="43.966454"
+ x2="1.0335478"
+ style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"
+ id="line10-4" />
+ <g
+ transform="translate(82.754238,28.792372)"
+ id="g3788">
+ <g
+ transform="translate(-14.555086,-5.2669489)"
+ id="g3690">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(-4.1313576,-5.2669489)"
+ id="g3690-5">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-2" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684-5">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633-47"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7-4"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5-4"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3-3"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4-0"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(6.2923724,-5.2669489)"
+ id="g3690-8">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-6" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684-8">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633-8"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7-43"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5-1"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3-4"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4-9"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(-24.978814,-5.2669489)"
+ id="g3690-0">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-68" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684-9">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633-2"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7-6"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5-6"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3-49"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4-5"
+ style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ </g>
+</svg>
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100644
index 0000000..335adb9
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,8 @@
+[Activity]
+name = Abacus
+activity_version = 1
+license = GPLv3
+bundle_id = org.sugarlabs.AbacusActivity
+exec = sugar-activity AbacusActivity.AbacusActivity
+icon = activity-abacus
+show_launcher = yes
diff --git a/constants.py b/constants.py
new file mode 100644
index 0000000..ca7f6c2
--- /dev/null
+++ b/constants.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2009, Walter Bender
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+BWIDTH = 40
+BHEIGHT = 30
+BOFFSET = 10
+NRODS = 15
+
diff --git a/icons/Coff.svg b/icons/Coff.svg
new file mode 100644
index 0000000..aa6c56a
--- /dev/null
+++ b/icons/Coff.svg
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs41" />
+ <rect
+ width="37.959229"
+ height="43"
+ x="-41.479614"
+ y="1"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="rect4"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2" />
+ <line
+ y1="16.5"
+ y2="16.5"
+ x1="44.18692"
+ x2="1.2540125"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2"
+ id="line10-4" />
+ <g
+ transform="translate(0,0.06685352)"
+ id="g2971">
+ <line
+ id="line10-68"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ x2="6.7392497"
+ x1="6.7392497"
+ y2="4.2606754"
+ y1="40.605618" />
+ <path
+ d="m 10.73925,26.747115 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,30.676521 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,34.605927 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-5-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,38.535333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,6.1059274 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,10.035333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,22.816439 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(10.448677,0.06685352)"
+ id="g2971-9">
+ <line
+ id="line10-68-3"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ x2="6.7392497"
+ x1="6.7392497"
+ y2="4.2606754"
+ y1="40.605618" />
+ <path
+ d="m 10.73925,26.747115 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-74"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,30.676521 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,34.605927 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-5-6-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,38.535333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,6.1059274 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-7-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,10.035333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-6-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,22.816439 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-6-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(20.897353,0.06685352)"
+ id="g2971-3">
+ <line
+ id="line10-68-0"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ x2="6.7392497"
+ x1="6.7392497"
+ y2="4.2606754"
+ y1="40.605618" />
+ <path
+ d="m 10.73925,26.747115 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-78"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,30.676521 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-68"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,34.605927 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-5-6-8"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,38.535333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,6.1059274 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-7-3"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,10.035333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-6-1"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,22.816439 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-6-49"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(31.346028,0.06685352)"
+ id="g2971-0">
+ <line
+ id="line10-68-6"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ x2="6.7392497"
+ x1="6.7392497"
+ y2="4.2606754"
+ y1="40.605618" />
+ <path
+ d="m 10.73925,26.747115 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-8"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,30.676521 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,34.605927 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-5-6-26"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,38.535333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-64"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,6.1059274 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-7-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,10.035333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-6-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,22.816439 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-6-0"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+</svg>
diff --git a/icons/Con.svg b/icons/Con.svg
new file mode 100644
index 0000000..c55f777
--- /dev/null
+++ b/icons/Con.svg
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs41" />
+ <rect
+ width="37.959229"
+ height="43"
+ x="-41.479614"
+ y="1"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="rect4"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2" />
+ <line
+ y1="16.5"
+ y2="16.5"
+ x1="44.18692"
+ x2="1.2540125"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2"
+ id="line10-4" />
+ <g
+ transform="translate(0,0.06685352)"
+ id="g2971">
+ <line
+ id="line10-68"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ x2="6.7392497"
+ x1="6.7392497"
+ y2="4.2606754"
+ y1="40.605618" />
+ <path
+ d="m 10.73925,26.747115 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,30.676521 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,34.605927 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-5-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,38.535333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,6.1059274 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,10.035333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,22.816439 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(10.448677,0.06685352)"
+ id="g2971-9">
+ <line
+ id="line10-68-3"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ x2="6.7392497"
+ x1="6.7392497"
+ y2="4.2606754"
+ y1="40.605618" />
+ <path
+ d="m 10.73925,26.747115 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-74"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,30.676521 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,34.605927 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-5-6-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,38.535333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,6.1059274 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-7-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,10.035333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-6-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,22.816439 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-6-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(20.897353,0.06685352)"
+ id="g2971-3">
+ <line
+ id="line10-68-0"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ x2="6.7392497"
+ x1="6.7392497"
+ y2="4.2606754"
+ y1="40.605618" />
+ <path
+ d="m 10.73925,26.747115 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-78"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,30.676521 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-68"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,34.605927 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-5-6-8"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,38.535333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,6.1059274 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-7-3"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,10.035333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-6-1"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,22.816439 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-6-49"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(31.346028,0.06685352)"
+ id="g2971-0">
+ <line
+ id="line10-68-6"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ x2="6.7392497"
+ x1="6.7392497"
+ y2="4.2606754"
+ y1="40.605618" />
+ <path
+ d="m 10.73925,26.747115 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-8"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,30.676521 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,34.605927 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-5-6-26"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,38.535333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-64"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,6.1059274 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-2-7-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,10.035333 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-7-6-6-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.73925,22.816439 a 4,1.9646672 0 0 1 -8,0 4,1.9646672 0 1 1 8,0 z"
+ id="path3633-3-49-6-0"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+</svg>
diff --git a/icons/Joff.svg b/icons/Joff.svg
new file mode 100644
index 0000000..828b233
--- /dev/null
+++ b/icons/Joff.svg
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs41" />
+ <rect
+ width="37.959229"
+ height="43"
+ x="-41.478813"
+ y="1"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="rect4"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2" />
+ <line
+ y1="14"
+ y2="14"
+ x1="43.966454"
+ x2="1.0335478"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2"
+ id="line10-4" />
+ <g
+ transform="translate(82.754238,28.792372)"
+ id="g3788">
+ <g
+ transform="translate(-14.555086,-5.2669489)"
+ id="g3690">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(-4.1313576,-5.2669489)"
+ id="g3690-5">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-2" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684-5">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633-47"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3-3"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4-0"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(6.2923724,-5.2669489)"
+ id="g3690-8">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-6" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684-8">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633-8"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7-43"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5-1"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(-24.978814,-5.2669489)"
+ id="g3690-0">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-68" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684-9">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3-49"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/Jon.svg b/icons/Jon.svg
new file mode 100644
index 0000000..d0acfd3
--- /dev/null
+++ b/icons/Jon.svg
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs41" />
+ <rect
+ width="37.959229"
+ height="43"
+ x="-41.478813"
+ y="1"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="rect4"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2" />
+ <line
+ y1="14"
+ y2="14"
+ x1="43.966454"
+ x2="1.0335478"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2"
+ id="line10-4" />
+ <g
+ transform="translate(82.754238,28.792372)"
+ id="g3788">
+ <g
+ transform="translate(-14.555086,-5.2669489)"
+ id="g3690">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(-4.1313576,-5.2669489)"
+ id="g3690-5">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-2" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684-5">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633-47"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3-3"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4-0"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(6.2923724,-5.2669489)"
+ id="g3690-8">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-6" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684-8">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633-8"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7-43"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5-1"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="translate(-24.978814,-5.2669489)"
+ id="g3690-0">
+ <line
+ y1="15.974576"
+ y2="-18.025423"
+ x1="-50.911018"
+ x2="-50.911018"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-68" />
+ <g
+ transform="translate(0,0.94158681)"
+ id="g3684-9">
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,9.5844131)"
+ id="path3633-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,15.084513)"
+ id="path3633-7-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,20.584613)"
+ id="path3633-5-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,26.084713)"
+ id="path3633-3-49"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <path
+ d="m -62.542371,-12.870763 a 4.0042372,2.7648306 0 1 1 -8.008474,0 4.0042372,2.7648306 0 1 1 8.008474,0 z"
+ transform="matrix(0.99894183,0,0,0.99463598,15.565173,-3.4736993)"
+ id="path3633-4-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/Roff.svg b/icons/Roff.svg
new file mode 100644
index 0000000..8594f97
--- /dev/null
+++ b/icons/Roff.svg
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs41" />
+ <rect
+ width="37.959229"
+ height="43"
+ x="-41.479614"
+ y="1"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="rect4"
+ style="fill:#aaaaaa;fill-opacity:1;stroke:#000000;stroke-width:2" />
+ <line
+ y1="39.5"
+ y2="5.5"
+ x1="22.5"
+ x2="22.5"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-68" />
+ <g
+ transform="translate(0,-2.0365919)"
+ id="g3825">
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,15.573287)"
+ id="g3781">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,-2.9776469)"
+ id="g3781-4">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ </g>
+ <line
+ y1="39.500004"
+ y2="5.4999976"
+ x1="9.724575"
+ x2="9.724575"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-68-3" />
+ <g
+ transform="translate(-12.775424,-2.0365919)"
+ id="g3825-7">
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,15.573287)"
+ id="g3781-45">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,-2.9776469)"
+ id="g3781-4-4">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2-7-3"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6-6-0"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6-5-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49-6-8"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5-9-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ </g>
+ <line
+ y1="39.500004"
+ y2="5.4999976"
+ x1="35.084743"
+ x2="35.084743"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-68-3-8" />
+ <g
+ transform="translate(12.584746,-2.0365919)"
+ id="g3825-7-8">
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,15.573287)"
+ id="g3781-45-4">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2-2-3"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6-5-1"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6-4-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49-7-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5-4-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,-2.9776469)"
+ id="g3781-4-4-0">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2-7-3-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6-6-0-8"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6-5-7-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49-6-8-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5-9-6-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/Ron.svg b/icons/Ron.svg
new file mode 100644
index 0000000..3481473
--- /dev/null
+++ b/icons/Ron.svg
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs41" />
+ <rect
+ width="37.959229"
+ height="43"
+ x="-41.479614"
+ y="1"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="rect4"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2" />
+ <line
+ y1="39.5"
+ y2="5.5"
+ x1="22.5"
+ x2="22.5"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-68" />
+ <g
+ transform="translate(0,-2.0365919)"
+ id="g3825">
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,15.573287)"
+ id="g3781">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,-2.9776469)"
+ id="g3781-4">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ </g>
+ <line
+ y1="39.500004"
+ y2="5.4999976"
+ x1="9.724575"
+ x2="9.724575"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-68-3" />
+ <g
+ transform="translate(-12.775424,-2.0365919)"
+ id="g3825-7">
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,15.573287)"
+ id="g3781-45">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6-5"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,-2.9776469)"
+ id="g3781-4-4">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2-7-3"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6-6-0"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6-5-7"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49-6-8"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5-9-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ </g>
+ <line
+ y1="39.500004"
+ y2="5.4999976"
+ x1="35.084743"
+ x2="35.084743"
+ style="fill:#aaaaaa;stroke:#000000;stroke-width:2;stroke-miterlimit:0;stroke-dasharray:none"
+ id="line10-68-3-8" />
+ <g
+ transform="translate(12.584746,-2.0365919)"
+ id="g3825-7-8">
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,15.573287)"
+ id="g3781-45-4">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2-2-3"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6-5-1"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6-4-4"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49-7-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5-4-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <g
+ transform="matrix(1,0,0,0.67426444,15.635544,-2.9776469)"
+ id="g3781-4-4-0">
+ <path
+ d="m 10.864456,21.54732 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-2-7-3-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,27.049879 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-7-6-6-0-8"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,32.552439 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-5-6-5-7-9"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,38.054999 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-3-49-6-8-2"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="m 10.864456,16.04476 a 4,2.75 0 1 1 -7.9999992,0 4,2.75 0 1 1 7.9999992,0 z"
+ id="path3633-4-5-9-6-6"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ </g>
+</svg>
diff --git a/images/black.svg b/images/black.svg
new file mode 100644
index 0000000..1101eb4
--- /dev/null
+++ b/images/black.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="40"
+ height="30"
+ id="svg2">
+ <defs
+ id="defs4" />
+ <g
+ transform="translate(-3.329523e-6,-1022.3622)"
+ id="layer1">
+ <path
+ d="m 379.99999,425.21933 a 98.571434,49.999996 0 1 1 -197.14287,0 98.571434,49.999996 0 1 1 197.14287,0 z"
+ transform="matrix(0.18768115,0,0,0.27000002,-32.818832,922.55297)"
+ id="path2816"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:13.32689667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/chinese_rod.svg b/images/chinese_rod.svg
new file mode 100644
index 0000000..a8cb73c
--- /dev/null
+++ b/images/chinese_rod.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="10"
+ height="360"
+ id="svg2">
+ <rect
+ width="10"
+ height="360"
+ x="0"
+ y="0"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:3.0;stroke-opacity:1" />
+</svg>
diff --git a/images/divider_bar.svg b/images/divider_bar.svg
new file mode 100644
index 0000000..0c89eec
--- /dev/null
+++ b/images/divider_bar.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="750"
+ height="30"
+ id="svg2">
+ <rect
+ width="750"
+ height="30"
+ x="0"
+ y="0"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:3;stroke-opacity:1" />
+</svg>
diff --git a/images/japanese_rod.svg b/images/japanese_rod.svg
new file mode 100644
index 0000000..bc9a5ca
--- /dev/null
+++ b/images/japanese_rod.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="10"
+ height="300"
+ id="svg2">
+ <rect
+ width="10"
+ height="300"
+ x="0"
+ y="0"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:3.0;stroke-opacity:1" />
+</svg>
diff --git a/images/russian_rod.svg b/images/russian_rod.svg
new file mode 100644
index 0000000..a8cb73c
--- /dev/null
+++ b/images/russian_rod.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="10"
+ height="360"
+ id="svg2">
+ <rect
+ width="10"
+ height="360"
+ x="0"
+ y="0"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:3.0;stroke-opacity:1" />
+</svg>
diff --git a/images/white.svg b/images/white.svg
new file mode 100644
index 0000000..9b66d68
--- /dev/null
+++ b/images/white.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="40"
+ height="30"
+ id="svg2">
+ <defs
+ id="defs4" />
+ <g
+ transform="translate(-3.329523e-6,-1022.3622)"
+ id="layer1">
+ <path
+ d="m 379.99999,425.21933 a 98.571434,49.999996 0 1 1 -197.14287,0 98.571434,49.999996 0 1 1 197.14287,0 z"
+ transform="matrix(0.18768115,0,0,0.27000002,-32.818832,922.55297)"
+ id="path2816"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:13.32689667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..bd1e319
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+from sugar.activity import bundlebuilder
+if __name__ == "__main__":
+ bundlebuilder.start()
+
diff --git a/sprites.py b/sprites.py
new file mode 100644
index 0000000..045ab98
--- /dev/null
+++ b/sprites.py
@@ -0,0 +1,367 @@
+# -*- coding: utf-8 -*-
+
+#Copyright (c) 2007-8, Playful Invention Company.
+#Copyright (c) 2008-10 Walter Bender
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+"""
+
+sprites.py is a simple sprites library for managing graphics objects,
+'sprites', on a canvas. It manages multiple sprites with methods such
+as move, hide, set_layer, etc.
+
+There are two classes:
+
+class Sprites maintains a collection of sprites.
+class Sprite manages individual sprites within the collection.
+
+Example usage:
+ # Import the classes into your program.
+ from sprites import Sprites, Sprite
+
+ # Create a new sprite collection for a gtk Drawing Area.
+ my_drawing_area = gtk.DrawingArea()
+ self.sprite_list = Sprites(my_drawing_area)
+
+ # Create a "pixbuf" (in this example, from SVG).
+ my_pixbuf = svg_str_to_pixbuf("<svg>...some svg code...</svg>")
+
+ # Create a sprite at position x1, y1.
+ my_sprite = sprites.Sprite(self.sprite_list, x1, y1, my_pixbuf)
+
+ # Move the sprite to a new position.
+ my_sprite.move((x1+dx, y1+dy))
+
+ # Create another "pixbuf".
+ your_pixbuf = svg_str_to_pixbuf("<svg>...some svg code...</svg>")
+
+ # Create a sprite at position x2, y2.
+ your_sprite = sprites.Sprite(self.sprite_list, x2, y2, my_pixbuf)
+
+ # Assign the sprites to layers.
+ # In this example, your_sprite will be on top of my_sprite.
+ my_sprite.set_layer(100)
+ your_sprite.set_layer(200)
+
+ # Now put my_sprite on top of your_sprite.
+ my_sprite.set_layer(300)
+
+# method for converting SVG to a gtk pixbuf
+def svg_str_to_pixbuf(svg_string):
+ pl = gtk.gdk.PixbufLoader('svg')
+ pl.write(svg_string)
+ pl.close()
+ pixbuf = pl.get_pixbuf()
+ return pixbuf
+"""
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+import pango
+
+#
+# A class for the list of sprites and everything they share in common
+#
+
+class Sprites:
+ def __init__(self, canvas, area=None, gc=None):
+ self.canvas = canvas
+ if area == None:
+ self.area = self.canvas.window
+ self.gc = self.area.new_gc()
+ else:
+ self.area = area
+ self.gc = gc
+ self.cm = self.gc.get_colormap()
+ self.list = []
+
+ def get_sprite(self, i):
+ if i < 0 or i > len(self.list)-1:
+ return(None)
+ else:
+ return(self.list[i])
+
+ def length_of_list(self):
+ return(len(self.list))
+
+ def append_to_list(self, spr):
+ self.list.append(spr)
+
+ def insert_in_list(self, spr, i):
+ if i < 0:
+ self.list.insert(0, spr)
+ elif i > len(self.list)-1:
+ self.list.append(spr)
+ else:
+ self.list.insert(i, spr)
+
+ def remove_from_list(self, spr):
+ if spr in self.list:
+ self.list.remove(spr)
+
+ def find_sprite(self, pos):
+ list = self.list[:]
+ list.reverse()
+ for spr in list:
+ if spr.hit(pos): return spr
+ return None
+
+ def redraw_sprites(self):
+ for spr in self.list:
+ spr.draw()
+
+#
+# A class for the individual sprites
+#
+class Sprite:
+ def __init__(self, sprites, x, y, image):
+ self._sprites = sprites
+ self._x = int(x)
+ self._y = int(y)
+ self._scale = [12]
+ self._rescale = [True]
+ self._horiz_align = ["center"]
+ self._vert_align = ["middle"]
+ self._fd = None
+ self._bold = False
+ self._italic = False
+ self._color = None
+ self._width = 0
+ self._height = 0
+ self._margins = [0,0,0,0]
+ self.layer = 100
+ self.labels = []
+ self.images = []
+ self._dx = [] # image offsets
+ self._dy = []
+ self.set_image(image)
+ self._sprites.append_to_list(self)
+
+ def set_image(self, image, i=0, dx=0, dy=0):
+ while len(self.images) < i+1:
+ self.images.append(None)
+ self._dx.append(0)
+ self._dy.append(0)
+ self.images[i] = image
+ self._dx[i] = dx
+ self._dy[i] = dy
+ if isinstance(image, gtk.gdk.Pixbuf):
+ _w = image.get_width()
+ _h = image.get_height()
+ else:
+ _w, _h = image.get_size()
+ if i == 0: # Always reset width and height when base image changes.
+ self._width = _w + dx
+ self._height = _h + dy
+ else:
+ if _w + dx > self._width:
+ self._width = _w + dx
+ if _h + dy > self._height:
+ self._height = _h + dy
+
+ def move(self, pos):
+ self.inval()
+ self._x,self._y = int(pos[0]),int(pos[1])
+ self.inval()
+
+ def move_relative(self, pos):
+ self.inval()
+ self._x += int(pos[0])
+ self._y += int(pos[1])
+ self.inval()
+
+ def get_xy(self):
+ return (self._x, self._y)
+
+ def get_dimensions(self):
+ return (self._width, self._height)
+
+ def get_layer(self):
+ return self.layer
+
+ def set_shape(self, image, i=0):
+ self.inval()
+ self.set_image(image, i)
+ self.inval()
+
+ def set_layer(self, layer):
+ self._sprites.remove_from_list(self)
+ self.layer = layer
+ for i in range(self._sprites.length_of_list()):
+ if layer < self._sprites.get_sprite(i).layer:
+ self._sprites.insert_in_list(self, i)
+ self.inval()
+ return
+ self._sprites.append_to_list(self)
+ self.inval()
+
+ def set_label(self, new_label, i=0):
+ self._extend_labels_array(i)
+ if type(new_label) is str or type(new_label) is unicode:
+ # pango doesn't like nulls
+ self.labels[i] = new_label.replace("\0"," ")
+ else:
+ self.labels[i] = str(new_label)
+ self.inval()
+
+ def set_margins(self, l=0, t=0, r=0, b=0):
+ self._margins = [l,t,r,b]
+
+ def _extend_labels_array(self, i):
+ if self._fd is None:
+ self.set_font('Sans')
+ if self._color is None:
+ self._color = self._sprites.cm.alloc_color('black')
+ while len(self.labels) < i+1:
+ self.labels.append(" ")
+ self._scale.append(self._scale[0])
+ self._rescale.append(self._rescale[0])
+ self._horiz_align.append(self._horiz_align[0])
+ self._vert_align.append(self._vert_align[0])
+
+ def set_font(self, font):
+ self._fd = pango.FontDescription(font)
+
+ def set_label_color(self, rgb):
+ self._color = self._sprites.cm.alloc_color(rgb)
+
+ def set_label_attributes(self, scale, rescale=True, horiz_align="center",
+ vert_align="middle", i=0):
+ self._extend_labels_array(i)
+ self._scale[i] = scale
+ self._rescale[i] = rescale
+ self._horiz_align[i] = horiz_align
+ self._vert_align[i] = vert_align
+
+ def hide(self):
+ self.inval()
+ self._sprites.remove_from_list(self)
+
+ def inval(self):
+ self._sprites.area.invalidate_rect(
+ gtk.gdk.Rectangle(self._x,self._y,self._width,self._height), False)
+
+ def draw(self):
+ for i,img in enumerate(self.images):
+ if isinstance(img, gtk.gdk.Pixbuf):
+ self._sprites.area.draw_pixbuf(
+ self._sprites.gc, img, 0, 0, self._x+self._dx[i],
+ self._y+self._dy[i])
+ elif img is not None:
+ self._sprites.area.draw_drawable(
+ self._sprites.gc, img, 0, 0, self._x+self._dx[i],
+ self._y+self._dy[i], -1, -1)
+ if len(self.labels) > 0:
+ self.draw_label()
+
+ def hit(self, pos):
+ x, y = pos
+ if x < self._x:
+ return False
+ if x > self._x+self._width:
+ return False
+ if y < self._y:
+ return False
+ if y > self._y+self._height:
+ return False
+ return True
+
+ def draw_label(self):
+ my_width = self._width-self._margins[0]-self._margins[2]
+ if my_width < 0:
+ my_width = 0
+ my_height = self._height-self._margins[1]-self._margins[3]
+ for i in range(len(self.labels)):
+ pl = self._sprites.canvas.create_pango_layout(str(self.labels[i]))
+ self._fd.set_size(int(self._scale[i]*pango.SCALE))
+ pl.set_font_description(self._fd)
+ w = pl.get_size()[0]/pango.SCALE
+ if w > my_width:
+ if self._rescale[i] is True:
+ self._fd.set_size(
+ int(self._scale[i]*pango.SCALE*my_width/w))
+ pl.set_font_description(self._fd)
+ w = pl.get_size()[0]/pango.SCALE
+ else:
+ j = len(self.labels[i])-1
+ while(w > my_width and j > 0):
+ pl = self._sprites.canvas.create_pango_layout(
+ "…"+self.labels[i][len(self.labels[i])-j:])
+ self._fd.set_size(int(self._scale[i]*pango.SCALE))
+ pl.set_font_description(self._fd)
+ w = pl.get_size()[0]/pango.SCALE
+ j -= 1
+ if self._horiz_align[i] == "center":
+ x = int(self._x+self._margins[0]+(my_width-w)/2)
+ elif self._horiz_align[i] == 'left':
+ x = int(self._x+self._margins[0])
+ else: # right
+ x = int(self._x+self._width-w-self._margins[2])
+ h = pl.get_size()[1]/pango.SCALE
+ if self._vert_align[i] == "middle":
+ y = int(self._y+self._margins[1]+(my_height-h)/2)
+ elif self._vert_align[i] == "top":
+ y = int(self._y+self._margins[1])
+ else: # bottom
+ y = int(self._y+self._height-h-self._margins[3])
+ self._sprites.gc.set_foreground(self._color)
+ self._sprites.area.draw_layout(self._sprites.gc, x, y, pl)
+
+ def label_width(self):
+ max = 0
+ for i in range(len(self.labels)):
+ pl = self._sprites.canvas.create_pango_layout(self.labels[i])
+ self._fd.set_size(int(self._scale[i]*pango.SCALE))
+ pl.set_font_description(self._fd)
+ w = pl.get_size()[0]/pango.SCALE
+ if w > max:
+ max = w
+ return max
+
+ def label_safe_width(self):
+ return self._width-self._margins[0]-self._margins[2]
+
+ def label_safe_height(self):
+ return self._height-self._margins[1]-self._margins[3]
+
+ def label_left_top(self):
+ return (self._margins[0], self._margins[1])
+
+ def get_pixel(self, pos, i=0):
+ x, y = pos
+ x = x-self._x
+ y = y-self._y
+ if y > self.images[i].get_height()-1:
+ return (-1,-1,-1,-1)
+ try:
+ array = self.images[i].get_pixels()
+ if array is not None:
+ offset = (y*self.images[i].get_width()+x)*4
+ r,g,b,a = ord(array[offset]), ord(array[offset+1]),\
+ ord(array[offset+2]), ord(array[offset+3])
+ return (r,g,b,a)
+ else:
+ return (-1,-1,-1,-1)
+ except IndexError:
+ print "Index Error: %d %d" % (len(array), offset)
+ return (-1,-1,-1,-1)
+