Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWalter Bender <walter.bender@gmail.com>2012-06-04 23:45:15 (GMT)
committer Walter Bender <walter.bender@gmail.com>2012-06-04 23:45:15 (GMT)
commit449a3ae56e4d2c0f8a3bd6d86a575b2c6176640d (patch)
tree6d37a40d3c168138a443d1df8c2c08cb3f5af759
New activity
-rw-r--r--AmazonasTortugaActivity.py1061
-rw-r--r--COPYING22
-rw-r--r--NEWS68
-rw-r--r--TurtleArt/__init__.py0
-rw-r--r--TurtleArt/sprites.py467
-rw-r--r--TurtleArt/tabasics.py1362
-rw-r--r--TurtleArt/tablock.py970
-rw-r--r--TurtleArt/tacanvas.py777
-rw-r--r--TurtleArt/tacollaboration.py419
-rw-r--r--TurtleArt/taconstants.py428
-rw-r--r--TurtleArt/taexporthtml.py161
-rw-r--r--TurtleArt/taexportlogo.py272
-rw-r--r--TurtleArt/tagettext.py24
-rw-r--r--TurtleArt/tagplay.py300
-rw-r--r--TurtleArt/tajail.py60
-rw-r--r--TurtleArt/talogo.py908
-rw-r--r--TurtleArt/tapalette.py376
-rwxr-xr-xTurtleArt/tasprite_factory.py1233
-rw-r--r--TurtleArt/taturtle.py312
-rw-r--r--TurtleArt/tautils.py832
-rw-r--r--TurtleArt/tawindow.py3424
-rw-r--r--activity/activity-turtleart.svg74
-rw-r--r--activity/activity.info10
-rw-r--r--activity/application-x-turtle-art.svg120
-rw-r--r--activity/menu-icon.svg83
-rw-r--r--activity/mimetypes.xml7
-rw-r--r--activity/turtleart.svg83
-rwxr-xr-xamazonas-tortuga.py712
-rw-r--r--challenges/amazonas-1.svg140
-rw-r--r--challenges/amazonas-10.svg411
-rw-r--r--challenges/amazonas-11.svg184
-rw-r--r--challenges/amazonas-12.svg483
-rw-r--r--challenges/amazonas-13.svg256
-rw-r--r--challenges/amazonas-14.svg656
-rw-r--r--challenges/amazonas-15.svg864
-rw-r--r--challenges/amazonas-16.svg307
-rw-r--r--challenges/amazonas-17.svg316
-rw-r--r--challenges/amazonas-18.svg316
-rw-r--r--challenges/amazonas-19.svg316
-rw-r--r--challenges/amazonas-2.svg266
-rw-r--r--challenges/amazonas-20.svg329
-rw-r--r--challenges/amazonas-21.svg329
-rw-r--r--challenges/amazonas-22.svg316
-rw-r--r--challenges/amazonas-23.svg453
-rw-r--r--challenges/amazonas-24.svg453
-rw-r--r--challenges/amazonas-25.svg453
-rw-r--r--challenges/amazonas-26.svg361
-rw-r--r--challenges/amazonas-27.svg361
-rw-r--r--challenges/amazonas-28.svg369
-rw-r--r--challenges/amazonas-3.svg270
-rw-r--r--challenges/amazonas-4.svg331
-rw-r--r--challenges/amazonas-5.svg342
-rw-r--r--challenges/amazonas-6.svg346
-rw-r--r--challenges/amazonas-7.svg399
-rw-r--r--challenges/amazonas-8.svg403
-rw-r--r--challenges/amazonas-9.svg407
-rw-r--r--challenges/offsets23
-rw-r--r--collaboration/__init__.py0
-rw-r--r--collaboration/activity.py720
-rw-r--r--collaboration/buddy.py234
-rw-r--r--collaboration/connection_watcher.py122
-rw-r--r--collaboration/connectionmanager.py122
-rw-r--r--collaboration/dispatch/Makefile.am9
-rw-r--r--collaboration/dispatch/__init__.py10
-rw-r--r--collaboration/dispatch/dispatcher.py191
-rw-r--r--collaboration/dispatch/license.txt66
-rw-r--r--collaboration/dispatch/saferef.py254
-rwxr-xr-xcollaboration/neighborhood.py1048
-rw-r--r--collaboration/presenceservice.py271
-rw-r--r--collaboration/telepathyclient.py105
-rwxr-xr-xcollaboration/test.py14
-rw-r--r--collaboration/tubeconn.py114
-rw-r--r--collaboration/xocolor.py282
-rw-r--r--gnome_plugins/__init__.py0
-rw-r--r--gnome_plugins/collaboration_plugin.py350
-rw-r--r--gnome_plugins/plugin.py34
-rw-r--r--gnome_plugins/uploader_plugin.py209
-rw-r--r--icons/blocksoff.svg47
-rw-r--r--icons/blockson.svg55
-rw-r--r--icons/colorsoff.svg60
-rw-r--r--icons/colorson.svg68
-rw-r--r--icons/contract-coordinates.svg79
-rw-r--r--icons/debugoff.svg141
-rw-r--r--icons/debugon.svg146
-rw-r--r--icons/eraseroff.svg33
-rw-r--r--icons/eraseron.svg38
-rw-r--r--icons/expand-coordinates.svg79
-rw-r--r--icons/filesaveoff.svg201
-rw-r--r--icons/flowoff.svg21
-rw-r--r--icons/flowon.svg43
-rw-r--r--icons/help-off.svg54
-rw-r--r--icons/help-on.svg57
-rw-r--r--icons/help-toolbar.svg18
-rw-r--r--icons/hideshowoff.svg28
-rw-r--r--icons/hideshowon.svg39
-rw-r--r--icons/htmloff.svg128
-rw-r--r--icons/htmlon.svg128
-rw-r--r--icons/image-saveoff.svg116
-rw-r--r--icons/image-saveon.svg116
-rw-r--r--icons/load-from-journal.svg158
-rw-r--r--icons/logo-saveoff.svg146
-rw-r--r--icons/logo-saveon.svg146
-rw-r--r--icons/numbersoff.svg38
-rw-r--r--icons/numberson.svg46
-rw-r--r--icons/palette.svg17
-rw-r--r--icons/paletteoff.svg28
-rw-r--r--icons/paletteon.svg36
-rw-r--r--icons/penoff.svg41
-rw-r--r--icons/penon.svg61
-rw-r--r--icons/pippy-openoff.svg133
-rw-r--r--icons/pippy-openon.svg147
-rw-r--r--icons/resize+.svg41
-rw-r--r--icons/resize-.svg43
-rw-r--r--icons/run-fastoff.svg36
-rw-r--r--icons/run-faston.svg40
-rw-r--r--icons/run-slowoff.svg115
-rw-r--r--icons/run-slowon.svg131
-rw-r--r--icons/stopitoff.svg22
-rw-r--r--icons/stopiton.svg96
-rw-r--r--icons/ta-open.svg183
-rw-r--r--icons/trashoff.svg49
-rw-r--r--icons/trashon.svg57
-rw-r--r--icons/turtleoff.svg90
-rw-r--r--icons/turtleon.svg106
-rw-r--r--icons/view-Cartesian.svg30
-rw-r--r--icons/view-metric.svg47
-rw-r--r--icons/view-polar.svg25
-rw-r--r--images/1x1.svg107
-rw-r--r--images/1x1a.svg63
-rw-r--r--images/1x2.svg163
-rw-r--r--images/2x1.svg159
-rw-r--r--images/2x2.svg171
-rw-r--r--images/Cartesian.svg211
-rw-r--r--images/Cartesian_labeled.svg375
-rw-r--r--images/amazonas-0.jpgbin0 -> 44246 bytes
-rw-r--r--images/amazonas-1.jpgbin0 -> 59821 bytes
-rw-r--r--images/amazonas-2.jpgbin0 -> 58092 bytes
-rw-r--r--images/amazonas-3.jpgbin0 -> 47407 bytes
-rw-r--r--images/audiooff.svg71
-rw-r--r--images/audioon.svg80
-rw-r--r--images/audiosmall.svg71
-rw-r--r--images/cameraoff.svg15
-rw-r--r--images/camerasmall.svg51
-rw-r--r--images/descriptionoff.svg59
-rw-r--r--images/descriptionon.svg58
-rw-r--r--images/descriptionsmall.svg59
-rw-r--r--images/dupstack.svg98
-rw-r--r--images/emptybox.svg356
-rw-r--r--images/emptyheap.svg73
-rw-r--r--images/fileoff.svg67
-rw-r--r--images/fileon.svg63
-rw-r--r--images/filesmall.svg43
-rw-r--r--images/help.svg50
-rw-r--r--images/help1200.svg50
-rw-r--r--images/incompatible.svg381
-rw-r--r--images/info.svg35
-rw-r--r--images/journaloff.svg63
-rw-r--r--images/journalon.svg62
-rw-r--r--images/journalsmall.svg63
-rw-r--r--images/list.svg66
-rw-r--r--images/metric.svg489
-rw-r--r--images/negroot.svg74
-rw-r--r--images/nocode.svg125
-rw-r--r--images/nofile.svg108
-rw-r--r--images/noinput.svg93
-rw-r--r--images/nojournal.svg144
-rw-r--r--images/nomedia.svg133
-rw-r--r--images/nostack.svg76
-rw-r--r--images/notanumber.svg74
-rw-r--r--images/overflowerror.svg121
-rw-r--r--images/palettehorizontal.svg25
-rw-r--r--images/palettehshift.svg86
-rw-r--r--images/palettenext.svg30
-rw-r--r--images/palettevertical.svg26
-rw-r--r--images/palettevshift.svg86
-rw-r--r--images/polar.svg230
-rw-r--r--images/print.svg50
-rw-r--r--images/print1200.svg50
-rw-r--r--images/pythonoff.svg53
-rw-r--r--images/pythonon.svg52
-rw-r--r--images/pythonsmall.svg53
-rw-r--r--images/status.svg63
-rw-r--r--images/status1200.svg80
-rw-r--r--images/syntaxerror.svg120
-rw-r--r--images/videooff.svg17
-rw-r--r--images/videoon.svg69
-rw-r--r--images/videosmall.svg76
-rw-r--r--images/zerodivide.svg74
-rw-r--r--plugins/__init__.py0
-rw-r--r--plugins/plugin.py50
-rw-r--r--setup.cfg3
-rwxr-xr-xsetup.py32
-rw-r--r--util/RtfParser.py150
-rw-r--r--util/__init__.py0
-rw-r--r--util/configfile.py177
-rw-r--r--util/configwizard.py218
-rw-r--r--util/helpbutton.py118
-rw-r--r--util/menubuilder.py39
198 files changed, 39630 insertions, 0 deletions
diff --git a/AmazonasTortugaActivity.py b/AmazonasTortugaActivity.py
new file mode 100644
index 0000000..b0fd0b5
--- /dev/null
+++ b/AmazonasTortugaActivity.py
@@ -0,0 +1,1061 @@
+#Copyright (c) 2007, Playful Invention Company
+#Copyright (c) 2008-12, Walter Bender
+#Copyright (c) 2009-10 Raul Gutierrez Segales
+
+#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.
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import cairo
+import gobject
+
+import logging
+_logger = logging.getLogger('amazonas-tortuga-activity')
+
+from sugar.activity import activity
+try: # 0.86 toolbar widgets
+ from sugar.activity.widgets import ActivityToolbarButton, StopButton
+ from sugar.graphics.toolbarbox import ToolbarBox, ToolbarButton
+ HAS_TOOLBARBOX = True
+except ImportError:
+ HAS_TOOLBARBOX = False
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.datastore import datastore
+from sugar.graphics.combobox import ComboBox
+from sugar.graphics.toolcombobox import ToolComboBox
+from sugar.graphics.objectchooser import ObjectChooser
+from sugar import mime
+
+from sugar import profile
+import os
+import glob
+import tarfile
+
+from gettext import gettext as _
+
+from TurtleArt.tapalette import palette_names, help_strings
+from TurtleArt.taconstants import ICON_SIZE, BLOCK_SCALE, XO1, XO15, XO175, XO30
+from TurtleArt.taexporthtml import save_html
+from TurtleArt.taexportlogo import save_logo
+from TurtleArt.tautils import data_to_file, data_to_string, data_from_string, \
+ get_path, chooser, get_hardware
+from TurtleArt.tawindow import TurtleArtWindow
+from TurtleArt.tacollaboration import Collaboration
+
+if HAS_TOOLBARBOX:
+ from util.helpbutton import HelpButton
+
+
+class AmazonasTortugaActivity(activity.Activity):
+
+ def __init__(self, handle):
+ ''' Activity subclass for Turtle Art '''
+ super(AmazonasTortugaActivity, self).__init__(handle)
+
+ self._stop_help = False
+ self._check_ver_change(get_path(activity, 'data'))
+
+ self._setup_visibility_handler()
+
+ self.has_toolbarbox = HAS_TOOLBARBOX
+
+ self._level = 0
+ self._custom_filepath = None
+
+ self._setup_toolbar()
+
+ self._setup_canvas(self._setup_scrolled_window())
+ # FIX ME: not sure how or why self.canvas gets overwritten
+ # It is set to self.sw in _setup_canvas but None here.
+ # We need self.canvas for generating the preview image
+ self.canvas = self.sw
+
+ self._setup_palette_toolbar()
+
+ if not self.has_toolbarbox:
+ self._setup_help_toolbar()
+
+ self._setup_sharing()
+
+ if self._stop_help:
+ self._load_level()
+ else:
+ self.help_animation()
+
+ # Eye candy intro
+ def help_animation(self):
+ self._help_index = 0
+ self._stop_help = False
+ self.stop_turtle_button.set_icon('stopiton')
+ self._help_next()
+
+ def _help_next(self):
+ ''' Load the next frame in the animation '''
+ path = os.path.join(activity.get_bundle_path(),
+ 'images', 'amazonas-%d.jpg' % self._help_index)
+ self.tw.canvas.setxy(-160, 120, pendown=False)
+ self.tw.lc.insert_image(center=False, resize=False,
+ filepath=path)
+ if self._stop_help:
+ self._load_level()
+ return
+ self._help_index += 1
+ self._help_index %= 4 # FIX ME
+ self._help_timeout_id = gobject.timeout_add(2000, self._help_next)
+
+ # Activity toolbar callbacks
+
+ def do_save_as_html_cb(self, button):
+ ''' Write html out to datastore. '''
+ self.save_as_html.set_icon('htmlon')
+ _logger.debug('saving HTML code')
+ # Until we have URLs for datastore objects, always embed images.
+ embed_flag = True
+
+ # Generate HTML by processing TA code from stacks.
+ html = save_html(self, self.tw, embed_flag)
+ if len(html) == 0:
+ return
+
+ # Save the HTML code to the instance directory.
+ datapath = get_path(activity, 'instance')
+
+ save_type = '.html'
+ if len(self.tw.saved_pictures) > 0:
+ if self.tw.saved_pictures[0].endswith(('.svg')):
+ save_type = '.xml'
+
+ html_file = os.path.join(datapath, 'portfolio' + save_type)
+ f = file(html_file, 'w')
+ f.write(html)
+ f.close()
+
+ if not embed_flag:
+ # We need to make a tar ball that includes the images.
+ tar_path = os.path.join(datapath, 'portfolio.tar')
+ tar_fd = tarfile.open(tar_path, 'w')
+ try:
+ tar_fd.add(html_file, 'portfolio.html')
+ image_list = glob.glob(os.path.join(datapath, 'image*'))
+ for i in image_list:
+ tar_fd.add(i, os.path.basename(i))
+ finally:
+ tar_fd.close()
+
+ dsobject = datastore.create()
+ dsobject.metadata['title'] = self.metadata['title'] + ' ' + \
+ _('presentation')
+ dsobject.metadata['icon-color'] = profile.get_color().to_string()
+ if embed_flag:
+ if save_type == '.xml':
+ dsobject.metadata['mime_type'] = 'application/xml'
+ else:
+ dsobject.metadata['mime_type'] = 'text/html'
+ dsobject.set_file_path(html_file)
+ else:
+ dsobject.metadata['mime_type'] = 'application/x-tar'
+ dsobject.set_file_path(tar_path)
+ dsobject.metadata['activity'] = 'org.laptop.WebActivity'
+ datastore.write(dsobject)
+ dsobject.destroy()
+
+ gobject.timeout_add(250, self.save_as_html.set_icon, 'htmloff')
+
+ self.tw.saved_pictures = [] # Clear queue of pictures we have viewed.
+ return
+
+ def do_save_as_logo_cb(self, button):
+ ''' Write UCB logo code to datastore. '''
+ self.save_as_logo.set_icon('logo-saveon')
+ logo_code_path = self._dump_logo_code()
+ if logo_code_path is None:
+ return
+
+ dsobject = datastore.create()
+ dsobject.metadata['title'] = self.metadata['title'] + '.lg'
+ dsobject.metadata['mime_type'] = 'text/plain'
+ dsobject.metadata['icon-color'] = profile.get_color().to_string()
+ dsobject.set_file_path(logo_code_path)
+ datastore.write(dsobject)
+ dsobject.destroy()
+
+ gobject.timeout_add(250, self.save_as_logo.set_icon, 'logo-saveoff')
+ return
+
+ def do_load_image_cb(self, button):
+ ''' Load an image from the Journal. '''
+ chooser = ObjectChooser(parent=self,
+ what_filter=mime.GENERIC_TYPE_IMAGE)
+
+ if chooser is not None:
+ try:
+ result = chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ jobject = chooser.get_selected_object()
+ if jobject and jobject.file_path:
+ name = jobject.metadata['title']
+ mime_type = jobject.metadata['mime_type']
+ _logger.debug('result of choose: %s (%s)' % \
+ (name, str(mime_type)))
+ finally:
+ chooser.destroy()
+ del chooser
+
+ if name is not None:
+ _logger.debug('opening %s ' % jobject.file_path)
+ self._custom_filepath = jobject.file_path
+ self._load_level(custom=True)
+
+ def do_load_ta_project_cb(self, button):
+ ''' Load a project from the Journal. '''
+ chooser(self, 'org.laptop.TurtleArtActivity', self._load_ta_project)
+
+ def _load_ta_project(self, dsobject):
+ ''' Load a TA project from the datastore. '''
+ try:
+ _logger.debug('opening %s ' % dsobject.file_path)
+ self.read_file(dsobject.file_path, False)
+ except:
+ _logger.debug("couldn't open %s" % dsobject.file_path)
+
+ def do_load_python_cb(self, button):
+ ''' Load Python code from the Journal. '''
+ self.load_python.set_icon('pippy-openon')
+ self.tw.load_python_code_from_file(fname=None, add_new_block=True)
+ gobject.timeout_add(250, self.load_python.set_icon, 'pippy-openoff')
+
+ def do_save_as_image_cb(self, button):
+ ''' Save the canvas to the Journal. '''
+ self.save_as_image.set_icon('image-saveon')
+ _logger.debug('saving image to journal')
+
+ self.tw.save_as_image()
+ gobject.timeout_add(250, self.save_as_image.set_icon, 'image-saveoff')
+ return
+
+ def do_keep_cb(self, button):
+ ''' Save a snapshot of the project to the Journal. '''
+ tmpfile = self._dump_ta_code()
+ if tmpfile is not None:
+ dsobject = datastore.create()
+ dsobject.metadata['title'] = self.metadata['title'] + ' ' + \
+ _('snapshot')
+ dsobject.metadata['icon-color'] = profile.get_color().to_string()
+ dsobject.metadata['mime_type'] = 'application/x-turtle-art'
+ dsobject.metadata['activity'] = 'org.laptop.TurtleConfusionActivity'
+ dsobject.set_file_path(tmpfile)
+ datastore.write(dsobject)
+ dsobject.destroy()
+ os.remove(tmpfile)
+ return
+
+ # Main/palette toolbar button callbacks
+
+ def do_palette_cb(self, button):
+ ''' Show/hide palette '''
+ if self.tw.palette:
+ self.tw.hideshow_palette(False)
+ self.do_hidepalette()
+ if self.has_toolbarbox and self.tw.selected_palette is not None:
+ self.palette_buttons[self.tw.selected_palette].set_icon(
+ palette_names[self.tw.selected_palette] + 'off')
+ else:
+ self.tw.hideshow_palette(True)
+ self.do_showpalette()
+ if self.has_toolbarbox:
+ self.palette_buttons[0].set_icon(palette_names[0] + 'on')
+
+ def do_palette_buttons_cb(self, button, i):
+ ''' Palette selector buttons '''
+ if self.tw.selected_palette is not None:
+ if not self.has_toolbarbox:
+ self.palette_buttons[self.tw.selected_palette].set_icon(
+ palette_names[self.tw.selected_palette] + 'off')
+ if self.tw.selected_palette == i:
+ # Hide the palette if it is already selected.
+ self.tw.hideshow_palette(False)
+ self.do_hidepalette()
+ return
+
+ if not self.has_toolbarbox:
+ self.palette_buttons[i].set_icon(palette_names[i] + 'on')
+ '''
+ else:
+ self._help_button.set_current_palette(palette_names[i])
+ '''
+ self.tw.show_palette(i)
+ self.do_showpalette()
+
+ # These methods are called both from toolbar buttons and blocks.
+
+ def _do_hover_help_toggle(self, button):
+ ''' Toggle hover help '''
+ if self.tw.no_help:
+ self.tw.no_help = False
+ self._hover_help_toggle.set_icon('help-off')
+ self._hover_help_toggle.set_tooltip(_('Turn off hover help'))
+ else:
+ self.tw.no_help = True
+ self.tw.last_label = None
+ self.tw.status_spr.hide()
+ self._hover_help_toggle.set_icon('help-on')
+ self._hover_help_toggle.set_tooltip(_('Turn on hover help'))
+
+ def do_hidepalette(self):
+ ''' Hide the palette. '''
+ if hasattr(self, 'palette_button'):
+ self.palette_button.set_icon('paletteon')
+ self.palette_button.set_tooltip(_('Show palette'))
+
+ def do_showpalette(self):
+ ''' Show the palette. '''
+ if hasattr(self, 'palette_button'):
+ self.palette_button.set_icon('paletteoff')
+ self.palette_button.set_tooltip(_('Hide palette'))
+
+ def do_hideshow_cb(self, button):
+ ''' Toggle visibility. '''
+ self.tw.hideshow_button()
+ if self.tw.hide: # We just hid the blocks.
+ self.blocks_button.set_icon('hideshowon')
+ self.blocks_button.set_tooltip(_('Show blocks'))
+ else:
+ self.blocks_button.set_icon('hideshowoff')
+ self.blocks_button.set_tooltip(_('Hide blocks'))
+ # Update palette buttons too.
+ if not self.tw.palette:
+ self.do_hidepalette()
+ else:
+ self.do_showpalette()
+
+ def do_hide_blocks(self):
+ ''' Hide blocks. '''
+ self.blocks_button.set_icon('hideshowon')
+ self.blocks_button.set_tooltip(_('Show blocks'))
+ self.do_hidepalette()
+
+ def do_show_blocks(self):
+ ''' Show blocks. '''
+ if not hasattr(self, 'blocks_button'):
+ return
+ self.blocks_button.set_icon('hideshowoff')
+ self.blocks_button.set_tooltip(_('Hide blocks'))
+ self.do_showpalette()
+
+ def do_eraser_cb(self, button):
+ ''' Clear the screen and recenter. '''
+ self.eraser_button.set_icon('eraseroff')
+ self.recenter()
+ self.tw.eraser_button()
+ gobject.timeout_add(250, self.eraser_button.set_icon, 'eraseron')
+
+ def restore_challenge(self):
+ ''' Restore the current challange after a clear screen '''
+ if self._custom_filepath is None:
+ self._load_level()
+ else:
+ self._load_level(custom=True)
+
+ def do_run_cb(self, button):
+ ''' Callback for run button (rabbit) '''
+ self.run_button.set_icon('run-faston')
+ self.tw.lc.trace = 0
+ self.tw.run_button(0)
+ gobject.timeout_add(1000, self.run_button.set_icon, 'run-fastoff')
+
+ def do_step_cb(self, button):
+ ''' Callback for step button (turtle) '''
+ self.step_button.set_icon('run-slowon')
+ self.tw.lc.trace = 1
+ self.tw.run_button(3)
+ gobject.timeout_add(1000, self.step_button.set_icon, 'run-slowoff')
+
+ def do_debug_cb(self, button):
+ ''' Callback for debug button (bug) '''
+ self.debug_button.set_icon('debugon')
+ self.tw.lc.trace = 1
+ self.tw.run_button(9)
+ gobject.timeout_add(1000, self.debug_button.set_icon, 'debugoff')
+
+ def do_stop_cb(self, button):
+ ''' Callback for stop button. '''
+ self._stop_help = True
+ self.stop_turtle_button.set_icon('stopitoff')
+ self.tw.stop_button()
+ self.step_button.set_icon('run-slowoff')
+ self.run_button.set_icon('run-fastoff')
+
+ def do_samples_cb(self, button):
+ ''' Sample-projects open dialog '''
+ self.tw.load_file(True)
+ self.tw.run_button(0)
+
+ def recenter(self):
+ ''' Recenter scrolled window around canvas. '''
+ hadj = self.sw.get_hadjustment()
+ hadj.set_value(0)
+ self.sw.set_hadjustment(hadj)
+ vadj = self.sw.get_vadjustment()
+ vadj.set_value(0)
+ self.sw.set_vadjustment(vadj)
+
+ def do_fullscreen_cb(self, button):
+ ''' Hide the Sugar toolbars. '''
+ self.fullscreen()
+ self.recenter()
+
+ def do_grow_blocks_cb(self, button):
+ ''' Grow the blocks. '''
+ self.do_resize_blocks(1)
+
+ def do_shrink_blocks_cb(self, button):
+ ''' Shrink the blocks. '''
+ self.do_resize_blocks(-1)
+
+ def do_resize_blocks(self, inc):
+ ''' Scale the blocks. '''
+ if self.tw.block_scale in BLOCK_SCALE:
+ i = BLOCK_SCALE.index(self.tw.block_scale) + inc
+ else:
+ i = BLOCK_SCALE[3] # 2.0
+ if i < 0:
+ self.tw.block_scale = BLOCK_SCALE[0]
+ elif i == len(BLOCK_SCALE):
+ self.tw.block_scale = BLOCK_SCALE[-1]
+ else:
+ self.tw.block_scale = BLOCK_SCALE[i]
+ self.tw.resize_blocks()
+
+ def do_cartesian_cb(self, button):
+ ''' Display Cartesian-coordinate grid. '''
+ if self.tw.cartesian:
+ self.tw.set_cartesian(False)
+ else:
+ self.tw.set_cartesian(True)
+
+ def do_polar_cb(self, button):
+ ''' Display polar-coordinate grid. '''
+ if self.tw.polar:
+ self.tw.set_polar(False)
+ else:
+ self.tw.set_polar(True)
+
+ def do_metric_cb(self, button):
+ ''' Display metric-coordinate grid. '''
+ if self.tw.metric:
+ self.tw.set_metric(False)
+ else:
+ self.tw.set_metric(True)
+
+ def do_rescale_cb(self, button):
+ ''' Rescale coordinate system (100==height/2 or 100 pixels). '''
+ if self.tw.cartesian:
+ cartesian = True
+ self.tw.set_cartesian(False)
+ else:
+ cartesian = False
+ if self.tw.polar:
+ polar = True
+ self.tw.set_polar(False)
+ else:
+ polar = False
+ if self.tw.metric:
+ metric = True
+ self.tw.set_metric(False)
+ else:
+ polar = False
+ if self.tw.coord_scale == 1:
+ self.tw.coord_scale = self.tw.height / 200
+ self.rescale_button.set_icon('contract-coordinates')
+ self.rescale_button.set_tooltip(_('Rescale coordinates down'))
+ else:
+ self.tw.coord_scale = 1
+ self.rescale_button.set_icon('expand-coordinates')
+ self.rescale_button.set_tooltip(_('Rescale coordinates up'))
+ self.tw.eraser_button()
+ if cartesian:
+ self.tw.set_cartesian(True)
+ if polar:
+ self.tw.set_polar(True)
+ if metric:
+ self.tw.set_metric(True)
+
+ def _do_help_cb(self, button):
+ if os.path.exists(os.path.join(
+ activity.get_bundle_path(), 'challenges',
+ 'help-' + str(self._level + 1) + '.ta')):
+ self.read_file(os.path.join(
+ activity.get_bundle_path(), 'challenges',
+ 'help-' + str(self._level + 1) + '.ta'), run_it=False)
+ '''
+ else:
+ self.hover_help_label.set_label(_("no help available"))
+ '''
+
+ def get_document_path(self, async_cb, async_err_cb):
+ ''' View TA code as part of view source. '''
+ ta_code_path = self._dump_ta_code()
+ if ta_code_path is not None:
+ async_cb(ta_code_path)
+
+ def _dump_logo_code(self):
+ ''' Save Logo code to temporary file. '''
+ datapath = get_path(activity, 'instance')
+ tmpfile = os.path.join(datapath, 'tmpfile.lg')
+ code = save_logo(self.tw)
+ if len(code) == 0:
+ _logger.debug('save_logo returned None')
+ return None
+ try:
+ f = file(tmpfile, 'w')
+ f.write(code)
+ f.close()
+ except Exception, e:
+ _logger.error("Couldn't save Logo code: " + str(e))
+ tmpfile = None
+ return tmpfile
+
+ def _dump_ta_code(self):
+ ''' Save TA code to temporary file. '''
+ datapath = get_path(activity, 'instance')
+ tmpfile = os.path.join(datapath, 'tmpfile.ta')
+ try:
+ data_to_file(self.tw.assemble_data_to_save(), tmpfile)
+ except Exception, e:
+ _logger.error("Couldn't save project code: " + str(e))
+ tmpfile = None
+ return tmpfile
+
+ def __visibility_notify_cb(self, window, event):
+ ''' Callback method for when the activity's visibility changes. '''
+ if event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED:
+ self.tw.background_plugins()
+ elif event.state in \
+ [gtk.gdk.VISIBILITY_UNOBSCURED, gtk.gdk.VISIBILITY_PARTIAL]:
+ self.tw.foreground_plugins()
+
+ def _keep_clicked_cb(self, button):
+ ''' Keep-button clicked. '''
+ self.jobject_new_patch()
+
+ def _setup_toolbar(self):
+ ''' Setup toolbar according to Sugar version. '''
+ if self.has_toolbarbox:
+ self._toolbox = ToolbarBox()
+
+ activity_toolbar_button = ActivityToolbarButton(self)
+
+ edit_toolbar = gtk.Toolbar()
+ edit_toolbar_button = ToolbarButton(label=_('Edit'),
+ page=edit_toolbar,
+ icon_name='toolbar-edit')
+ view_toolbar = gtk.Toolbar()
+ view_toolbar_button = ToolbarButton(label=_('View'),
+ page=view_toolbar,
+ icon_name='toolbar-view')
+ self._palette_toolbar = gtk.Toolbar()
+ self._palette_toolbar_button = ToolbarButton(
+ page=self._palette_toolbar, icon_name='palette')
+
+ self._make_load_save_buttons(activity_toolbar_button)
+
+ activity_toolbar_button.show()
+ self._toolbox.toolbar.insert(activity_toolbar_button, -1)
+ edit_toolbar_button.show()
+ self._toolbox.toolbar.insert(edit_toolbar_button, -1)
+ view_toolbar_button.show()
+ self._toolbox.toolbar.insert(view_toolbar_button, -1)
+ self._palette_toolbar_button.show()
+ self._toolbox.toolbar.insert(self._palette_toolbar_button, -1)
+
+ self._make_project_buttons(self._toolbox.toolbar)
+
+ self._add_separator(self._toolbox.toolbar, expand=True,
+ visible=False)
+
+ self.samples_button = self._add_button(
+ 'ta-open', _('Load example'), self.do_samples_cb,
+ self._toolbox.toolbar)
+
+ self._help_button = self._add_button(
+ 'help-toolbar', _('Help'), self._do_help_cb,
+ self._toolbox.toolbar)
+
+ stop_button = StopButton(self)
+ stop_button.props.accelerator = '<Ctrl>Q'
+ self._toolbox.toolbar.insert(stop_button, -1)
+ stop_button.show()
+
+ _logger.debug('set_toolbar_box')
+ self.set_toolbar_box(self._toolbox)
+ self._palette_toolbar_button.set_expanded(True)
+ else:
+ self._toolbox = activity.ActivityToolbox(self)
+ self.set_toolbox(self._toolbox)
+
+ project_toolbar = gtk.Toolbar()
+ self._toolbox.add_toolbar(_('Project'), project_toolbar)
+
+ view_toolbar = gtk.Toolbar()
+ self._toolbox.add_toolbar(_('View'), view_toolbar)
+ view_toolbar_button = view_toolbar
+ edit_toolbar = gtk.Toolbar()
+ self._toolbox.add_toolbar(_('Edit'), edit_toolbar)
+ edit_toolbar_button = edit_toolbar
+ journal_toolbar = gtk.Toolbar()
+ self._toolbox.add_toolbar(_('Import/Export'), journal_toolbar)
+
+ self._make_palette_buttons(project_toolbar, palette_button=True)
+
+ self._add_separator(project_toolbar)
+
+ self._make_project_buttons(project_toolbar)
+ self._make_load_save_buttons(journal_toolbar)
+
+ self._add_button('edit-copy', _('Copy'), self._copy_cb,
+ edit_toolbar_button, '<Ctrl>c')
+ self._add_button('edit-paste', _('Paste'), self._paste_cb,
+ edit_toolbar_button, '<Ctrl>v')
+ self._add_button('view-fullscreen', _('Fullscreen'),
+ self.do_fullscreen_cb, view_toolbar_button,
+ '<Alt>Return')
+ self._add_button('view-Cartesian', _('Cartesian coordinates'),
+ self.do_cartesian_cb, view_toolbar_button)
+ self._add_button('view-polar', _('Polar coordinates'),
+ self.do_polar_cb, view_toolbar_button)
+ if get_hardware() in [XO1, XO15, XO175]:
+ self._add_button('view-metric', _('Metric coordinates'),
+ self.do_metric_cb, view_toolbar_button)
+ self._add_separator(view_toolbar, visible=False)
+ self.coordinates_label = self._add_label(_('xcor') + ' = 0 ' + \
+ _('ycor') + ' = 0 ' + _('heading') + ' = 0', view_toolbar)
+ self._add_separator(view_toolbar, expand=True, visible=False)
+ """
+ self.rescale_button = self._add_button(
+ 'expand-coordinates', _('Rescale coordinates up'),
+ self.do_rescale_cb, view_toolbar_button)
+ """
+ self.resize_up_button = self._add_button(
+ 'resize+', _('Grow blocks'), self.do_grow_blocks_cb,
+ view_toolbar_button)
+ self.resize_down_button = self._add_button(
+ 'resize-', _('Shrink blocks'), self.do_shrink_blocks_cb,
+ view_toolbar_button)
+ self._hover_help_toggle = self._add_button(
+ 'help-off', _('Turn off hover help'),
+ self._do_hover_help_toggle, view_toolbar)
+
+ edit_toolbar.show()
+ view_toolbar.show()
+ self._toolbox.show()
+
+ if not self.has_toolbarbox:
+ self._toolbox.set_current_toolbar(1)
+
+ def _setup_palette_toolbar(self):
+ # The palette toolbar must be setup *after* plugins are loaded.
+ if self.has_toolbarbox:
+ self.palette_buttons = []
+ for i, palette_name in enumerate(palette_names):
+ if i == 0:
+ palette_group = None
+ else:
+ palette_group = self.palette_buttons[0]
+ _logger.debug('palette_buttons.append %s', palette_name)
+ self.palette_buttons.append(self._radio_button_factory(
+ palette_name + 'off',
+ self._palette_toolbar_button,
+ self.do_palette_buttons_cb, i,
+ help_strings[palette_name],
+ palette_group))
+ self._add_separator(self._palette_toolbar, expand=True,
+ visible=False)
+ self._make_palette_buttons(self._palette_toolbar_button)
+ self._palette_toolbar.show()
+
+ def _make_load_save_buttons(self, toolbar):
+ self.save_as_image = self._add_button(
+ 'image-saveoff', _('Save as image'), self.do_save_as_image_cb,
+ toolbar)
+ self.save_as_logo = self._add_button(
+ 'logo-saveoff', _('Save as Logo'), self.do_save_as_logo_cb,
+ toolbar)
+ self.keep_button = self._add_button(
+ 'filesaveoff', _('Save snapshot'), self.do_keep_cb, toolbar)
+ if not self.has_toolbarbox:
+ self._add_separator(toolbar)
+ self.load_ta_project = self._add_button(
+ 'load-from-journal', _('Import project from the Journal'),
+ self.do_load_ta_project_cb, toolbar)
+ if not self.has_toolbarbox:
+ self.samples_button = self._add_button(
+ 'ta-open', _('Load example'), self.do_samples_cb, toolbar)
+
+ def _make_palette_buttons(self, toolbar, palette_button=False):
+ ''' Creates the palette and block buttons for both toolbar types'''
+ self._levels = self._get_levels(activity.get_bundle_path())
+ self._levels_combo = self._combo_factory(self._levels,
+ _('Select a challenge'),
+ toolbar, self._levels_cb)
+ self._add_separator(toolbar, expand=True, visible=False)
+ if palette_button: # old-style toolbars need this button
+ self.palette_button = self._add_button(
+ 'paletteoff', _('Hide palette'), self.do_palette_cb,
+ toolbar, _('<Ctrl>p'))
+ self.blocks_button = self._add_button(
+ 'hideshowoff', _('Hide blocks'), self.do_hideshow_cb, toolbar,
+ _('<Ctrl>b'))
+
+ def _make_project_buttons(self, toolbar):
+ ''' Creates the turtle buttons for both toolbar types'''
+ self.eraser_button = self._add_button(
+ 'eraseron', _('Clean'), self.do_eraser_cb, toolbar, _('<Ctrl>e'))
+ self.run_button = self._add_button(
+ 'run-fastoff', _('Run'), self.do_run_cb, toolbar, _('<Ctrl>r'))
+ self.step_button = self._add_button(
+ 'run-slowoff', _('Step'), self.do_step_cb, toolbar, _('<Ctrl>w'))
+ self.stop_turtle_button = self._add_button(
+ 'stopitoff', _('Stop turtle'), self.do_stop_cb, toolbar,
+ _('<Ctrl>s'))
+
+ def _check_ver_change(self, datapath):
+ ''' Check to see if the version has changed. '''
+ # We don't do anything with this info at the moment.
+ try:
+ version = os.environ['SUGAR_BUNDLE_VERSION']
+ except KeyError:
+ version = 'unknown'
+
+ filename = 'version.dat'
+ version_data = []
+ new_version = True
+ try:
+ file_handle = open(os.path.join(datapath, filename), 'r')
+ if file_handle.readline() == version:
+ new_version = False
+ file_handle.close()
+ except IOError:
+ _logger.debug("Couldn't read version number.")
+
+ version_data.append(version)
+ try:
+ file_handle = open(os.path.join(datapath, filename), 'w')
+ file_handle.writelines(version_data)
+ file_handle.close()
+ except IOError:
+ _logger.debug("Couldn't write version number.")
+
+ return new_version
+
+ def _setup_scrolled_window(self):
+ ''' Create a scrolled window to contain the turtle canvas. '''
+ self.sw = gtk.ScrolledWindow()
+ self.set_canvas(self.sw)
+ self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.sw.show()
+
+ canvas = gtk.DrawingArea()
+ canvas.set_size_request(gtk.gdk.screen_width() * 2,
+ gtk.gdk.screen_height() * 2)
+ self.sw.add_with_viewport(canvas)
+ hadj = self.sw.get_hadjustment()
+ hadj.connect('value-changed', self._scroll_cb)
+ vadj = self.sw.get_vadjustment()
+ vadj.connect('value-changed', self._scroll_cb)
+ self.hadj_value = 0
+ self.vadj_value = 0
+ canvas.show()
+ self.sw.show()
+ self.show_all()
+ return canvas
+
+ def _scroll_cb(self, window):
+ ''' The scrolling window has been changed, so move the
+ floating palettes. '''
+ hadj = self.sw.get_hadjustment()
+ self.hadj_value = hadj.get_value()
+ vadj = self.sw.get_vadjustment()
+ self.vadj_value = vadj.get_value()
+ if not self.tw.hw in [XO1]:
+ gobject.idle_add(self.tw.move_palettes, self.hadj_value,
+ self.vadj_value)
+
+ def _setup_canvas(self, canvas_window):
+ ''' Initialize the turtle art canvas. '''
+ win = canvas_window.get_window() # self._canvas.get_window()
+ cr = win.cairo_create()
+ surface = cr.get_target()
+ self.turtle_canvas = surface.create_similar(
+ cairo.CONTENT_COLOR, gtk.gdk.screen_width() * 2,
+ gtk.gdk.screen_height() * 2)
+ bundle_path = activity.get_bundle_path()
+ self.tw = TurtleArtWindow(canvas_window, bundle_path, self,
+ profile.get_color().to_string(),
+ profile.get_nick_name(),
+ turtle_canvas=self.turtle_canvas)
+ self.tw.window.grab_focus()
+ path = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'data')
+ self.tw.save_folder = path
+
+ self.tw.lc.scale = 100
+
+ # Try restoring an existing project...
+ if self._jobject and self._jobject.file_path:
+ self.read_file(self._jobject.file_path)
+ else: # ...or else, load a Start Block onto the canvas.
+ self.tw.load_start()
+
+ def _setup_sharing(self):
+ ''' Setup the Collabora stack. '''
+ self._collaboration = Collaboration(self.tw, self)
+ self._collaboration.setup()
+
+ def _setup_visibility_handler(self):
+ ''' Notify me when the visibility state changes. '''
+ self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
+ self.connect('visibility-notify-event', self.__visibility_notify_cb)
+
+ def write_file(self, file_path):
+ ''' Write the project to the Journal. '''
+ _logger.debug('Write file: %s' % file_path)
+ self.metadata['mime_type'] = 'application/x-turtle-art'
+ data_to_file(self.tw.assemble_data_to_save(), file_path)
+
+ def read_file(self, file_path, run_it=True):
+ ''' Read a project in and then run it. '''
+ if hasattr(self, 'tw'):
+ _logger.debug('Read file: %s' % (file_path))
+ # Could be a deprecated gtar or tar file...
+ if file_path.endswith(('.gtar', '.tar')):
+ import tempfile
+ import shutil
+
+ tar_fd = tarfile.open(file_path, 'r')
+ tmpdir = tempfile.mkdtemp()
+ try:
+ # We'll get 'ta_code.ta' and possibly a 'ta_image.png'
+ # but we will ignore the .png file
+ # If run_it is True, we want to create a new project
+ tar_fd.extractall(tmpdir)
+ self.tw.load_files(os.path.join(tmpdir, 'ta_code.ta'),
+ run_it)
+ finally:
+ shutil.rmtree(tmpdir)
+ tar_fd.close()
+ # ...otherwise, assume it is a .ta file.
+ else:
+ _logger.debug('Trying to open a .ta file:' + file_path)
+ self.tw.load_files(file_path, run_it)
+
+ # Finally, run the project.
+ if run_it:
+ self.tw.run_button(0)
+ self._stop_help = True
+ else:
+ _logger.debug('Deferring reading file %s' % (file_path))
+
+ def jobject_new_patch(self):
+ ''' Save instance to Journal. '''
+ oldj = self._jobject
+ self._jobject = datastore.create()
+ self._jobject.metadata['title'] = oldj.metadata['title']
+ self._jobject.metadata['title_set_by_user'] = \
+ oldj.metadata['title_set_by_user']
+ self._jobject.metadata['activity_id'] = self.get_id()
+ self._jobject.metadata['keep'] = '0'
+ self._jobject.metadata['preview'] = ''
+ self._jobject.metadata['icon-color'] = profile.get_color().to_string()
+ self._jobject.file_path = ''
+ datastore.write(
+ self._jobject, reply_handler=self._internal_jobject_create_cb,
+ error_handler=self._internal_jobject_error_cb)
+ self._jobject.destroy()
+
+ def _copy_cb(self, button):
+ ''' Copy to the clipboard. '''
+ clipboard = gtk.Clipboard()
+ _logger.debug('Serialize the project and copy to clipboard.')
+ data = self.tw.assemble_data_to_save(False, False)
+ if data is not []:
+ text = data_to_string(data)
+ clipboard.set_text(text)
+ self.tw.paste_offset = 20
+
+ def _paste_cb(self, button):
+ ''' Paste from the clipboard. '''
+ clipboard = gtk.Clipboard()
+ _logger.debug('Paste to the project.')
+ text = clipboard.wait_for_text()
+ if text is not None:
+ if self.tw.selected_blk is not None and \
+ self.tw.selected_blk.name == 'string':
+ for i in text:
+ self.tw.process_alphanumeric_input(i, -1)
+ self.tw.selected_blk.resize()
+ else:
+ self.tw.process_data(data_from_string(text),
+ self.tw.paste_offset)
+ self.tw.paste_offset += 20
+
+ def _add_label(self, string, toolbar, width=None):
+ ''' Add a label to a toolbar. '''
+ label = gtk.Label(string)
+ label.set_line_wrap(True)
+ if width is not None:
+ label.set_size_request(width, -1)
+ label.show()
+ toolitem = gtk.ToolItem()
+ toolitem.add(label)
+ toolbar.insert(toolitem, -1)
+ toolitem.show()
+ return label
+
+ def _add_separator(self, toolbar, expand=False, visible=True):
+ ''' Add a separator to a toolbar. '''
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = visible
+ separator.set_expand(expand)
+ if hasattr(toolbar, 'insert'):
+ toolbar.insert(separator, -1)
+ else:
+ toolbar.props.page.insert(separator, -1)
+ separator.show()
+
+ def _add_button(self, name, tooltip, callback, toolbar, accelerator=None,
+ arg=None):
+ ''' Add a button to a toolbar. '''
+ button = ToolButton(name)
+ button.set_tooltip(tooltip)
+ if arg is None:
+ button.connect('clicked', callback)
+ else:
+ button.connect('clicked', callback, arg)
+ if accelerator is not None:
+ try:
+ button.props.accelerator = accelerator
+ except AttributeError:
+ pass
+ button.show()
+ if hasattr(toolbar, 'insert'): # Add button to the main toolbar...
+ toolbar.insert(button, -1)
+ else: # ...or a secondary toolbar.
+ toolbar.props.page.insert(button, -1)
+
+ if not name in help_strings:
+ help_strings[name] = tooltip
+ return button
+
+ def _combo_factory(self, options, tooltip, toolbar, callback, default=0):
+ ''' Combo box factory '''
+ combo = ComboBox()
+ if hasattr(combo, 'set_tooltip_text'):
+ combo.set_tooltip_text(tooltip)
+ combo.connect('changed', callback)
+ for i, option in enumerate(options):
+ combo.append_item(i, option.replace('-', ' '), None)
+ combo.set_active(default)
+ combo.show()
+ tool = ToolComboBox(combo)
+ tool.show()
+ if hasattr(toolbar, 'insert'):
+ toolbar.insert(tool, -1)
+ else:
+ toolbar.props.page.insert(tool, -1)
+ return combo
+
+ def _get_levels(self, path):
+ ''' Look for level files in lessons directory. '''
+ levels = glob.glob(os.path.join(activity.get_bundle_path(),
+ 'challenges', '*.svg'))
+
+ level_files = []
+ for i in range(len(levels)):
+ level_files.append('amazonas-%d' % (i+1))
+
+ self.offsets = {}
+ offset_fd = open(os.path.join(activity.get_bundle_path(), 'challenges',
+ 'offsets'))
+ for line in offset_fd:
+ try:
+ idx, offsets = line.strip('\n').split(':')
+ xoffset, yoffset = offsets.split(',')
+ self.offsets[int(idx)] = (int(xoffset), int(yoffset))
+ except ValueError:
+ pass
+ offset_fd.close()
+ return level_files
+
+ def _levels_cb(self, combobox=None):
+ ''' The combo box has changed. '''
+ if hasattr(self, '_levels_combo'):
+ i = self._levels_combo.get_active()
+ if i != -1: # and i != self._level:
+ self._level = i
+ self._load_level()
+ self._custom_filepath = None
+
+ def _load_level(self, custom=False):
+ self.tw.canvas.clearscreen()
+ if custom:
+ self.tw.canvas.setxy(0, 0, pendown=False)
+ self.tw.lc.insert_image(center=True,
+ filepath=self._custom_filepath,
+ resize=True, offset=False)
+ else:
+ self.tw.canvas.setxy(int(-gtk.gdk.screen_width() / 2), 100,
+ pendown=False)
+ self.tw.lc.insert_image(center=False, resize=False,
+ filepath=os.path.join(
+ activity.get_bundle_path(), 'images',
+ 'amazonas-tortuga.png'))
+ # Slight offset to account for stroke width
+ if self._level + 1 in self.offsets:
+ xoffset = self.offsets[self._level + 1][0]
+ yoffset = self.offsets[self._level + 1][1]
+ else:
+ xoffset = 0
+ yoffset = 0
+ self.tw.canvas.setxy(-2.5 + xoffset, -2.5 + yoffset, pendown=False)
+ self.tw.lc.insert_image(center=False,
+ filepath=os.path.join(
+ activity.get_bundle_path(), 'challenges',
+ self._levels[self._level] + '.svg'), resize=False,
+ offset=True)
+ self.tw.canvas.setxy(0, 0, pendown=False)
+
+ def _radio_button_factory(self, button_name, toolbar, cb, arg, tooltip,
+ group):
+ ''' Add a radio button to a toolbar '''
+ button = RadioToolButton(group=group)
+ button.set_named_icon(button_name)
+ if cb is not None:
+ if arg is None:
+ button.connect('clicked', cb)
+ else:
+ button.connect('clicked', cb, arg)
+ if hasattr(toolbar, 'insert'): # Add button to the main toolbar...
+ toolbar.insert(button, -1)
+ else: # ...or a secondary toolbar.
+ toolbar.props.page.insert(button, -1)
+ button.show()
+ if tooltip is not None:
+ button.set_tooltip(tooltip)
+ return button
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..ff7d256
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,22 @@
+Copyright (c) 2007, Playful Invention Company
+Copyright (c) 2008-11, Walter Bender
+Copyright (c) 2009-11 Raúl Gutiérrez Segalés
+
+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.
+
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..b2eeb0c
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,68 @@
+10
+
+ENHANCEMENTS
+* Added help for all levels
+* Sync up with Turtle Blocks 139
+* New translations
+
+BUG FIX
+* Restore challenge after 'clean'
+
+9
+
+ENHANCEMENTS
+* Added new help files written by Ignacio Rodriguez
+* Sync with current Turtle Blocks
+* New translations
+
+8
+
+ENHANCEMENTS
+* Cairo conversion
+* New translations
+
+7
+
+ENHANCEMENT
+* new translations
+
+6
+
+ENHANCEMENT
+* Refactoring of toolbars to meet new reference design
+
+BUG FIX
+* Fixed race-condition that sometimes prevented turtle art from
+ launching (#3044)
+
+5
+
+ENHANCEMENT
+* Added offset to challenge to make it easier to align the turtle to the graphic
+* Added hints to the Help toolbar (with help from Ignacio Rodriguez)
+* Command-line version for running from GNOME shell
+
+4
+
+ENHANCEMENTS
+* Restore challenge after running clean block
+* Improved clarity of activity-icon graphics
+* Show dimensions on challenge graphics
+
+3
+
+ENHANCEMENT
+* Import custom challenge from Journal
+
+BUG FIXES
+* Fixed problem with erase button
+
+2
+
+BUG FIXES
+* Fixed problem with old-style toolbars
+
+1
+
+ New activity that combines Turtle Blocks and Barry Newell's Turtle
+ Confusion challenges
diff --git a/TurtleArt/__init__.py b/TurtleArt/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/TurtleArt/__init__.py
diff --git a/TurtleArt/sprites.py b/TurtleArt/sprites.py
new file mode 100644
index 0000000..c400d88
--- /dev/null
+++ b/TurtleArt/sprites.py
@@ -0,0 +1,467 @@
+# -*- coding: utf-8 -*-
+
+#Copyright (c) 2007-8, Playful Invention Company.
+#Copyright (c) 2008-11 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 gtk.DrawingArea. 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 associated with your widget
+ self.sprite_list = Sprites(widget)
+
+ # 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)
+
+ cr = self.window.cairo_create()
+ # In your activity's do_expose_event, put in a call to redraw_sprites
+ self.sprites.redraw_sprites(event.area, cairo_context)
+
+# 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 pango
+import pangocairo
+import cairo
+
+
+class Sprites:
+ ''' A class for the list of sprites and everything they share in common '''
+
+ def __init__(self, widget):
+ ''' Initialize an empty array of sprites '''
+ self.widget = widget
+ self.list = []
+ self.cr = None
+
+ def set_cairo_context(self, cr):
+ ''' Cairo context may be set or reset after __init__ '''
+ self.cr = cr
+
+ def get_sprite(self, i):
+ ''' Return a sprint from the array '''
+ if i < 0 or i > len(self.list) - 1:
+ return(None)
+ else:
+ return(self.list[i])
+
+ def length_of_list(self):
+ ''' How many sprites are there? '''
+ return(len(self.list))
+
+ def append_to_list(self, spr):
+ ''' Append a new sprite to the end of the list. '''
+ self.list.append(spr)
+
+ def insert_in_list(self, spr, i):
+ ''' Insert a sprite at position 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 find_in_list(self, spr):
+ if spr in self.list:
+ return True
+ return False
+
+ def remove_from_list(self, spr):
+ ''' Remove a sprite from the list. '''
+ if spr in self.list:
+ self.list.remove(spr)
+
+ def find_sprite(self, pos, region=False):
+ ''' Search based on (x, y) position. Return the 'top/first' one. '''
+ list = self.list[:]
+ list.reverse()
+ for spr in list:
+ if spr.hit(pos, readpixel=not region):
+ return spr
+ return None
+
+ def redraw_sprites(self, area=None, cr=None):
+ ''' Redraw the sprites that intersect area. '''
+ # I think I need to do this to save Cairo some work
+ if cr is None:
+ cr = self.cr
+ else:
+ self.cr = cr
+ if cr is None:
+ print 'sprites.redraw_sprites: no Cairo context'
+ return
+ for spr in self.list:
+ if area == None:
+ spr.draw(cr=cr)
+ else:
+ intersection = spr.rect.intersect(area)
+ if intersection.width > 0 or intersection.height > 0:
+ spr.draw(cr=cr)
+
+
+class Sprite:
+ ''' A class for the individual sprites '''
+
+ def __init__(self, sprites, x, y, image):
+ ''' Initialize an individual sprite '''
+ self._sprites = sprites
+ self.save_xy = (x, y) # remember initial (x, y) position
+ self.rect = gtk.gdk.Rectangle(int(x), int(y), 0, 0)
+ 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._margins = [0, 0, 0, 0]
+ self.layer = 100
+ self.labels = []
+ self.cached_surfaces = []
+ self._dx = [] # image offsets
+ self._dy = []
+ self.type = None
+ self.set_image(image)
+ self._sprites.append_to_list(self)
+
+ def set_image(self, image, i=0, dx=0, dy=0):
+ ''' Add an image to the sprite. '''
+ while len(self.cached_surfaces) < i + 1:
+ self.cached_surfaces.append(None)
+ self._dx.append(0)
+ self._dy.append(0)
+ self._dx[i] = dx
+ self._dy[i] = dy
+ if isinstance(image, gtk.gdk.Pixbuf) or \
+ isinstance(image, cairo.ImageSurface):
+ 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.rect.width = w + dx
+ self.rect.height = h + dy
+ else:
+ if w + dx > self.rect.width:
+ self.rect.width = w + dx
+ if h + dy > self.rect.height:
+ self.rect.height = h + dy
+ if isinstance(image, cairo.ImageSurface):
+ self.cached_surfaces[i] = image
+ else: # Convert to Cairo surface
+ surface = cairo.ImageSurface(
+ cairo.FORMAT_ARGB32, self.rect.width, self.rect.height)
+ context = cairo.Context(surface)
+ context = gtk.gdk.CairoContext(context)
+ context.set_source_pixbuf(image, 0, 0)
+ context.rectangle(0, 0, self.rect.width, self.rect.height)
+ context.fill()
+ self.cached_surfaces[i] = surface
+
+ def move(self, pos):
+ ''' Move to new (x, y) position '''
+ self.inval()
+ self.rect.x, self.rect.y = int(pos[0]), int(pos[1])
+ self.inval()
+
+ def move_relative(self, pos):
+ ''' Move to new (x+dx, y+dy) position '''
+ self.inval()
+ self.rect.x += int(pos[0])
+ self.rect.y += int(pos[1])
+ self.inval()
+
+ def get_xy(self):
+ ''' Return current (x, y) position '''
+ return (self.rect.x, self.rect.y)
+
+ def get_dimensions(self):
+ ''' Return current size '''
+ return (self.rect.width, self.rect.height)
+
+ def get_layer(self):
+ ''' Return current layer '''
+ return self.layer
+
+ def set_shape(self, image, i=0):
+ ''' Set the current image associated with the sprite '''
+ self.inval()
+ self.set_image(image, i)
+ self.inval()
+
+ def set_layer(self, layer=None):
+ ''' Set the layer for a sprite '''
+ self._sprites.remove_from_list(self)
+ if layer is not None:
+ 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):
+ ''' Set the label drawn on the sprite '''
+ 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):
+ ''' Set the margins for drawing the label '''
+ self._margins = [l, t, r, b]
+
+ def _extend_labels_array(self, i):
+ ''' Append to the labels attribute list '''
+ if self._fd is None:
+ self.set_font('Sans')
+ if self._color is None:
+ self._color = (0., 0., 0.)
+ 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):
+ ''' Set the font for a label '''
+ self._fd = pango.FontDescription(font)
+
+ def set_label_color(self, rgb):
+ ''' Set the font color for a label '''
+ COLORTABLE = {'black': '#000000', 'white': '#FFFFFF',
+ 'red': '#FF0000', 'yellow': '#FFFF00',
+ 'green': '#00FF00', 'cyan': '#00FFFF',
+ 'blue': '#0000FF', 'purple': '#FF00FF',
+ 'gray': '#808080'}
+ if rgb.lower() in COLORTABLE:
+ rgb = COLORTABLE[rgb.lower()]
+ # Convert from '#RRGGBB' to floats
+ self._color = (int('0x' + rgb[1:3], 16) / 256.,
+ int('0x' + rgb[3:5], 16) / 256.,
+ int('0x' + rgb[5:7], 16) / 256.)
+ return
+
+ def set_label_attributes(self, scale, rescale=True, horiz_align="center",
+ vert_align="middle", i=0):
+ ''' Set the various label attributes '''
+ 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):
+ ''' Hide a sprite '''
+ self.inval()
+ self._sprites.remove_from_list(self)
+
+ def restore(self):
+ ''' Restore a hidden sprite '''
+ self.set_layer()
+
+ def inval(self):
+ ''' Invalidate a region for gtk '''
+ self._sprites.widget.queue_draw_area(self.rect.x,
+ self.rect.y,
+ self.rect.width,
+ self.rect.height)
+
+ def draw(self, cr=None):
+ ''' Draw the sprite (and label) '''
+ if cr is None:
+ print 'sprite.draw: no Cairo context.'
+ return
+ for i, surface in enumerate(self.cached_surfaces):
+ cr.set_source_surface(surface,
+ self.rect.x + self._dx[i],
+ self.rect.y + self._dy[i])
+ cr.rectangle(self.rect.x + self._dx[i],
+ self.rect.y + self._dy[i],
+ self.rect.width,
+ self.rect.height)
+ cr.fill()
+ if len(self.labels) > 0:
+ self.draw_label(cr)
+
+ def hit(self, pos, readpixel=False):
+ ''' Is (x, y) on top of the sprite? '''
+ x, y = pos
+ if x < self.rect.x:
+ return False
+ if x > self.rect.x + self.rect.width:
+ return False
+ if y < self.rect.y:
+ return False
+ if y > self.rect.y + self.rect.height:
+ return False
+ if readpixel:
+ r, g, b, a = self.get_pixel(pos)
+ if r == g == b == a == 0:
+ return False
+ if a == -1:
+ return False
+ return self._sprites.find_in_list(self)
+
+ def draw_label(self, cr):
+ ''' Draw the label based on its attributes '''
+ # Create a pangocairo context
+ cr = pangocairo.CairoContext(cr)
+ my_width = self.rect.width - self._margins[0] - self._margins[2]
+ if my_width < 0:
+ my_width = 0
+ my_height = self.rect.height - self._margins[1] - self._margins[3]
+ for i in range(len(self.labels)):
+ pl = cr.create_layout()
+ pl.set_text(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]:
+ 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.set_text(
+ "…" + 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.rect.x + self._margins[0] + (my_width - w) / 2)
+ elif self._horiz_align[i] == 'left':
+ x = int(self.rect.x + self._margins[0])
+ else: # right
+ x = int(self.rect.x + self.rect.width - w - self._margins[2])
+ h = pl.get_size()[1] / pango.SCALE
+ if self._vert_align[i] == "middle":
+ y = int(self.rect.y + self._margins[1] + (my_height - h) / 2)
+ elif self._vert_align[i] == "top":
+ y = int(self.rect.y + self._margins[1])
+ else: # bottom
+ y = int(self.rect.y + self.rect.height - h - self._margins[3])
+ cr.save()
+ cr.translate(x, y)
+ cr.set_source_rgb(self._color[0], self._color[1], self._color[2])
+ cr.update_layout(pl)
+ cr.show_layout(pl)
+ cr.restore()
+
+ def label_width(self):
+ ''' Calculate the width of a label '''
+ cr = pangocairo.CairoContext(self._sprites.cr)
+ if cr is not None:
+ max = 0
+ for i in range(len(self.labels)):
+ pl = cr.create_layout()
+ pl.set_text(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
+ else:
+ return self.rect.width
+
+ def label_safe_width(self):
+ ''' Return maximum width for a label '''
+ return self.rect.width - self._margins[0] - self._margins[2]
+
+ def label_safe_height(self):
+ ''' Return maximum height for a label '''
+ return self.rect.height - self._margins[1] - self._margins[3]
+
+ def label_left_top(self):
+ ''' Return the upper-left corner of the label safe zone '''
+ return(self._margins[0], self._margins[1])
+
+ def get_pixel(self, pos, i=0):
+ ''' Return the pixel at (x, y) '''
+ x = int(pos[0] - self.rect.x)
+ y = int(pos[1] - self.rect.y)
+ if x < 0 or x > (self.rect.width - 1) or \
+ y < 0 or y > (self.rect.height - 1):
+ return(-1, -1, -1, -1)
+ # Create a new 1x1 cairo surface.
+ cs = cairo.ImageSurface(cairo.FORMAT_RGB24, 1, 1)
+ cr = cairo.Context(cs)
+ cr.set_source_surface(self.cached_surfaces[i], -x, -y)
+ cr.rectangle(0, 0, 1, 1)
+ cr.set_operator(cairo.OPERATOR_SOURCE)
+ cr.fill()
+ cs.flush() # Ensure all the writing is done.
+ pixels = cs.get_data() # Read the pixel.
+ return (ord(pixels[2]), ord(pixels[1]), ord(pixels[0]), 0)
diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py
new file mode 100644
index 0000000..e26950d
--- /dev/null
+++ b/TurtleArt/tabasics.py
@@ -0,0 +1,1362 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2011, 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.
+
+"""
+This file contains the constants that by-in-large determine the
+behavior of Turtle Art. Notably, the block palettes are defined
+below. If you want to add a new block to Turtle Art, you could
+simply add a block of code to this file or to turtle_block_plugin.py,
+which contains additional blocks. (Even better, write your own plugin!!)
+
+
+Adding a new palette is simply a matter of:
+ palette = make_palette('mypalette', # the name of your palette
+ colors=["#00FF00", "#00A000"],
+ help_string=_('Palette of my custom commands'))
+
+For example, if we want to add a new turtle command, 'uturn', we'd use the
+add_block method in the Palette class.
+ palette.add_block('uturn', # the name of your block
+ style='basic-style', # the block style
+ label=_('u turn'), # the label for the block
+ prim_name='uturn', # code reference (see below)
+ help_string=_('turns the turtle 180 degrees'))
+
+ # Next, you need to define what your block will do:
+ # def_prim takes 3 arguments: the primitive name, the number of
+ # arguments -- 0 in this case -- and the function to call -- in this
+ # case, the canvas.seth function to set the heading.
+ self.tw.lc.def_prim('uturn', 0,
+ lambda self: self.tw.canvas.seth(self.tw.canvas.heading + 180))
+
+That's it. When you next run Turtle Art, you will have a 'uturn' block
+on the 'mypalette' palette.
+
+You will have to create icons for the palette-selector buttons. These
+are kept in the icons subdirectory. You need two icons:
+mypaletteoff.svg and mypaletteon.svg, where 'mypalette' is the same
+string as the entry you used in instantiating the Palette class. Note
+that the icons should be the same size (55x55) as the others. (This is
+the default icon size for Sugar toolbars.)
+"""
+
+from time import time
+from math import sqrt
+from random import uniform
+
+from gettext import gettext as _
+
+from tapalette import make_palette, define_logo_function
+from talogo import primitive_dictionary, logoerror
+from tautils import convert, chr_to_ord, round_int, strtype
+from taconstants import BLACK, WHITE, CONSTANTS, XO30
+
+def _num_type(x):
+ """ Is x a number type? """
+ if type(x) == int:
+ return True
+ if type(x) == float:
+ return True
+ if type(x) == ord:
+ return True
+ return False
+
+
+def _millisecond():
+ """ Current time in milliseconds """
+ return time() * 1000
+
+
+class Palettes():
+ """ a class for creating the palettes of blocks """
+
+ def __init__(self, parent):
+ self.tw = parent
+
+ self._turtle_palette()
+
+ self._pen_palette()
+
+ self._color_palette()
+
+ self._numbers_palette()
+
+ self._flow_palette()
+
+ self._blocks_palette()
+
+ self._trash_palette()
+
+ # Palette definitions
+
+ def _turtle_palette(self):
+ """ The basic Turtle Art turtle palette """
+
+ palette = make_palette('turtle',
+ colors=["#00FF00", "#00A000"],
+ help_string=_('Palette of turtle commands'))
+
+ primitive_dictionary['move'] = self._prim_move
+ palette.add_block('forward',
+ style='basic-style-1arg',
+ label=_('forward'),
+ prim_name='forward',
+ default=100,
+ logo_command='forward',
+ help_string=_('moves turtle forward'))
+ self.tw.lc.def_prim('forward', 1,
+ lambda self, x: primitive_dictionary['move'](
+ self.tw.canvas.forward, x))
+
+ palette.add_block('back',
+ style='basic-style-1arg',
+ label=_('back'),
+ prim_name='back',
+ default=100,
+ logo_command='back',
+ help_string=_('moves turtle backward'))
+ self.tw.lc.def_prim('back', 1,
+ lambda self, x: primitive_dictionary['move'](
+ self.tw.canvas.forward, -x))
+
+ primitive_dictionary['clean'] = self._prim_clean
+ palette.add_block('clean',
+ style='basic-style-extended-vertical',
+ label=_('clean'),
+ prim_name='clean',
+ logo_command='clean',
+ help_string=_('clears the screen and reset the \
+turtle'))
+ self.tw.lc.def_prim('clean', 0,
+ lambda self: primitive_dictionary['clean']())
+
+ primitive_dictionary['right'] = self._prim_right
+ palette.add_block('left',
+ style='basic-style-1arg',
+ label=_('left'),
+ prim_name='left',
+ default=90,
+ logo_command='left',
+ help_string=_('turns turtle counterclockwise (angle \
+in degrees)'))
+ self.tw.lc.def_prim('left', 1,
+ lambda self, x: primitive_dictionary['right'](-x))
+
+ palette.add_block('right',
+ style='basic-style-1arg',
+ label=_('right'),
+ prim_name='right',
+ default=90,
+ logo_command='right',
+ help_string=_('turns turtle clockwise (angle in \
+degrees)'))
+ self.tw.lc.def_prim('right', 1,
+ lambda self, x: primitive_dictionary['right'](x))
+
+ primitive_dictionary['arc'] = self._prim_arc
+ palette.add_block('arc',
+ hidden=True,
+ style='basic-style-2arg',
+ label=[_('arc'), _('angle'), _('radius')],
+ prim_name='arc',
+ default=[90, 100],
+ logo_command='taarc',
+ help_string=_('moves turtle along an arc'))
+ self.tw.lc.def_prim('arc', 2,
+ lambda self, x, y: primitive_dictionary['arc'](
+ self.tw.canvas.arc, x, y))
+ define_logo_function('taarc', 'to taarc :a :r\rrepeat round :a \
+[right 1 forward (0.0175 * :r)]\rend\r')
+
+ palette.add_block('setxy2',
+ style='basic-style-2arg',
+ label=[_('set xy'), _('x'), _('y')],
+ prim_name='setxy2',
+ logo_command='tasetxy',
+ default=[0, 0],
+ help_string=_('moves turtle to position xcor, ycor; \
+(0, 0) is in the center of the screen.'))
+ self.tw.lc.def_prim('setxy2', 2,
+ lambda self, x, y: primitive_dictionary['move'](
+ self.tw.canvas.setxy, x, y))
+ define_logo_function('tasetxy', 'to tasetxy :x :y\rsetxy :x :y\rend\r')
+
+ primitive_dictionary['set'] = self._prim_set
+ palette.add_block('seth',
+ style='basic-style-1arg',
+ label=_('set heading'),
+ prim_name='seth',
+ default=0,
+ logo_command='seth',
+ help_string=_('sets the heading of the turtle (0 is \
+towards the top of the screen.)'))
+ self.tw.lc.def_prim('seth', 1,
+ lambda self, x: primitive_dictionary['set'](
+ 'heading', self.tw.canvas.seth, x))
+
+ palette.add_block('xcor',
+ style='box-style',
+ label=_('xcor'),
+ help_string=_('holds current x-coordinate value of \
+the turtle (can be used in place of a number block)'),
+ value_block=True,
+ prim_name='xcor',
+ logo_command='xcor')
+ self.tw.lc.def_prim(
+ 'xcor', 0, lambda self: self.tw.canvas.xcor / self.tw.coord_scale)
+
+ palette.add_block('ycor',
+ style='box-style',
+ label=_('ycor'),
+ help_string=_('holds current y-coordinate value of \
+the turtle (can be used in place of a number block)'),
+ value_block=True,
+ prim_name='ycor',
+ logo_command='ycor')
+ self.tw.lc.def_prim(
+ 'ycor', 0, lambda self: self.tw.canvas.ycor / self.tw.coord_scale)
+
+ palette.add_block('heading',
+ style='box-style',
+ label=_('heading'),
+ help_string=_('holds current heading value of the \
+turtle (can be used in place of a number block)'),
+ value_block=True,
+ prim_name='heading',
+ logo_command='heading')
+ self.tw.lc.def_prim(
+ 'heading', 0, lambda self: self.tw.canvas.heading)
+
+ palette.add_block('turtle-label',
+ hidden=True,
+ style='blank-style',
+ label=['turtle'])
+
+ # Deprecated
+ palette.add_block('setxy',
+ hidden=True,
+ style='basic-style-2arg',
+ label=[_('set xy'), _('x'), _('y')],
+ prim_name='setxy',
+ default=[0, 0],
+ logo_command='tasetxypenup',
+ help_string=_('moves turtle to position xcor, ycor; \
+(0, 0) is in the center of the screen.'))
+ self.tw.lc.def_prim('setxy', 2,
+ lambda self, x, y: primitive_dictionary['move'](
+ self.tw.canvas.setxy, x, y, pendown=False))
+ define_logo_function('tasetxypenup', 'to tasetxypenup :x :y\rpenup\r\
+setxy :x :y\rpendown\rend\r')
+
+ def _pen_palette(self):
+ """ The basic Turtle Art pen palette """
+
+ palette = make_palette('pen',
+ colors=["#00FFFF", "#00A0A0"],
+ help_string=_('Palette of pen commands'))
+
+ palette.add_block('penup',
+ style='basic-style-extended-vertical',
+ label=_('pen up'),
+ prim_name='penup',
+ logo_command='penup',
+ help_string=_('Turtle will not draw when moved.'))
+ self.tw.lc.def_prim('penup', 0,
+ lambda self: self.tw.canvas.setpen(False))
+
+ palette.add_block('pendown',
+ style='basic-style-extended-vertical',
+ label=_('pen down'),
+ prim_name='pendown',
+ logo_command='pendown',
+ help_string=_('Turtle will draw when moved.'))
+ self.tw.lc.def_prim('pendown', 0,
+ lambda self: self.tw.canvas.setpen(True))
+
+ palette.add_block('setpensize',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('set pen size'),
+ prim_name='setpensize',
+ default=5,
+ logo_command='setpensize',
+ help_string=_('sets size of the line drawn by the \
+turtle'))
+ self.tw.lc.def_prim('setpensize', 1,
+ lambda self, x: primitive_dictionary['set'](
+ 'pensize', self.tw.canvas.setpensize, x))
+ define_logo_function('tasetpensize', 'to tasetpensize :a\rsetpensize \
+round :a\rend\r')
+
+ palette.add_block('fillscreen',
+ hidden=True,
+ style='basic-style-2arg',
+ label=[_('fill screen'), _('color'), _('shade')],
+ prim_name='fillscreen',
+ default=[60, 80],
+ logo_command='tasetbackground',
+ help_string=_('fills the background with (color, \
+shade)'))
+ self.tw.lc.def_prim('fillscreen', 2,
+ lambda self, x, y: self.tw.canvas.fillscreen(x, y))
+ define_logo_function('tasetbackground', 'to tasetbackground :color \
+:shade\rtasetshade :shade\rsetbackground :color\rend\r')
+
+ palette.add_block('pensize',
+ hidden=True,
+ style='box-style',
+ label=_('pen size'),
+ help_string=_('holds current pen size (can be used \
+in place of a number block)'),
+ value_block=True,
+ prim_name='pensize',
+ logo_command='pensize')
+ self.tw.lc.def_prim('pensize', 0, lambda self: self.tw.canvas.pensize)
+ define_logo_function('tapensize', 'to tapensize\routput first round \
+pensize\rend\r')
+
+ palette.add_block('startfill',
+ style='basic-style-extended-vertical',
+ label=_('start fill'),
+ prim_name='startfill',
+ help_string=_('starts filled polygon (used with end \
+fill block)'))
+ self.tw.lc.def_prim('startfill', 0,
+ lambda self: self.tw.canvas.start_fill())
+
+ palette.add_block('stopfill',
+ style='basic-style-extended-vertical',
+ label=_('end fill'),
+ prim_name='stopfill',
+ help_string=_('completes filled polygon (used with \
+start fill block)'))
+ self.tw.lc.def_prim('stopfill', 0,
+ lambda self: self.tw.canvas.stop_fill())
+
+ def _color_palette(self):
+ """ The basic Turtle Art color palette """
+
+ if self.tw.hw == XO30:
+ palette = make_palette('pen',
+ colors=["#00FFFF", "#00A0A0"],
+ help_string=_('Palette of pen colors'))
+ else:
+ palette = make_palette('colors',
+ colors=["#00FFFF", "#00A0A0"],
+ help_string=_('Palette of pen colors'))
+
+ palette.add_block('setcolor',
+ style='basic-style-1arg',
+ label=_('set color'),
+ prim_name='setcolor',
+ default=0,
+ logo_command='tasetpencolor',
+ help_string=_('sets color of the line drawn by the \
+turtle'))
+ self.tw.lc.def_prim('setcolor', 1,
+ lambda self, x: primitive_dictionary['set'](
+ 'color', self.tw.canvas.setcolor, x))
+
+ palette.add_block('setshade',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('set shade'),
+ prim_name='setshade',
+ default=50,
+ logo_command='tasetshade',
+ help_string=_('sets shade of the line drawn by the \
+turtle'))
+ self.tw.lc.def_prim('setshade', 1,
+ lambda self, x: primitive_dictionary['set'](
+ 'shade', self.tw.canvas.setshade, x))
+
+ palette.add_block('setgray',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('set gray'),
+ prim_name='setgray',
+ default=100,
+ help_string=_('sets gray level of the line drawn by \
+the turtle'))
+ self.tw.lc.def_prim('setgray', 1,
+ lambda self, x: primitive_dictionary['set'](
+ 'gray', self.tw.canvas.setgray, x))
+
+ palette.add_block('color',
+ style='box-style',
+ label=_('color'),
+ help_string=_('holds current pen color (can be used \
+in place of a number block)'),
+ value_block=True,
+ prim_name='color',
+ logo_command='pencolor')
+ self.tw.lc.def_prim('color', 0, lambda self: self.tw.canvas.color)
+
+ palette.add_block('shade',
+ hidden=True,
+ style='box-style',
+ label=_('shade'),
+ help_string=_('holds current pen shade'),
+ value_block=True,
+ prim_name='shade',
+ logo_command=':shade')
+ self.tw.lc.def_prim('shade', 0, lambda self: self.tw.canvas.shade)
+
+ palette.add_block('gray',
+ hidden=True,
+ style='box-style',
+ label=_('gray'),
+ help_string=_('holds current gray level (can be used \
+in place of a number block)'),
+ value_block=True,
+ prim_name='gray')
+ self.tw.lc.def_prim('gray', 0, lambda self: self.tw.canvas.gray)
+
+ self._make_constant(palette, 'red', _('red'), CONSTANTS['red'])
+ self._make_constant(palette, 'orange', _('orange'),
+ CONSTANTS['orange'])
+ self._make_constant(palette, 'yellow', _('yellow'),
+ CONSTANTS['yellow'])
+ self._make_constant(palette, 'green', _('green'), CONSTANTS['green'])
+ self._make_constant(palette, 'cyan', _('cyan'), CONSTANTS['cyan'])
+ self._make_constant(palette, 'blue', _('blue'), CONSTANTS['blue'])
+ self._make_constant(palette, 'purple', _('purple'),
+ CONSTANTS['purple'])
+ self._make_constant(palette, 'white', _('white'), WHITE)
+ self._make_constant(palette, 'black', _('black'), BLACK)
+
+ # deprecated blocks
+ palette.add_block('settextcolor',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('set text color'),
+ prim_name='settextcolor',
+ default=0,
+ help_string=_('sets color of text drawn by the \
+turtle'))
+ self.tw.lc.def_prim('settextcolor', 1,
+ lambda self, x: self.tw.canvas.settextcolor(x))
+
+ palette.add_block('settextsize',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('set text size'),
+ prim_name='settextsize',
+ default=0,
+ help_string=_('sets size of text drawn by the \
+turtle'))
+ self.tw.lc.def_prim('settextsize', 1,
+ lambda self, x: self.tw.canvas.settextsize(x))
+
+ # In order to map Turtle Art colors to the standard UCB Logo palette,
+ # we need to define a somewhat complex set of functions.
+ define_logo_function('tacolor', '\
+to tasetpalette :i :r :g :b :myshade \r\
+make "s ((:myshade - 50) / 50) \r\
+ifelse lessp :s 0 [ \r\
+make "s (1 + (:s *0.8)) \r\
+make "r (:r * :s) \r\
+make "g (:g * :s) \r\
+make "b (:b * :s) \r\
+] [ \
+make "s (:s * 0.9) \r\
+make "r (:r + ((99-:r) * :s)) \r\
+make "g (:g + ((99-:g) * :s)) \r\
+make "b (:b + ((99-:b) * :s)) \r\
+] \
+setpalette :i (list :r :g :b) \r\
+end \r\
+\
+to rgb :myi :mycolors :myshade \r\
+make "myr first :mycolors \r\
+make "mycolors butfirst :mycolors \r\
+make "myg first :mycolors \r\
+make "mycolors butfirst :mycolors \r\
+make "myb first :mycolors \r\
+make "mycolors butfirst :mycolors \r\
+tasetpalette :myi :myr :myg :myb :myshade \r\
+output :mycolors \r\
+end \r\
+\
+to processcolor :mycolors :myshade \r\
+if emptyp :mycolors [stop] \r\
+make "i :i + 1 \r\
+processcolor (rgb :i :mycolors :myshade) :myshade \r\
+end \r\
+\
+to tasetshade :shade \r\
+make "myshade modulo :shade 200 \r\
+if greaterp :myshade 99 [make "myshade (199-:myshade)] \r\
+make "i 7 \r\
+make "mycolors :colors \r\
+processcolor :mycolors :myshade \r\
+end \r\
+\
+to tasetpencolor :c \r\
+make "color (modulo (round :c) 100) \r\
+setpencolor :color + 8 \r\
+end \r\
+\
+make "colors [ \
+99 0 0 99 5 0 99 10 0 99 15 0 99 20 0 \
+99 25 0 99 30 0 99 35 0 99 40 0 99 45 0 \
+99 50 0 99 55 0 99 60 0 99 65 0 99 70 0 \
+99 75 0 99 80 0 99 85 0 99 90 0 99 95 0 \
+99 99 0 90 99 0 80 99 0 70 99 0 60 99 0 \
+50 99 0 40 99 0 30 99 0 20 99 0 10 99 0 \
+ 0 99 0 0 99 5 0 99 10 0 99 15 0 99 20 \
+ 0 99 25 0 99 30 0 99 35 0 99 40 0 99 45 \
+ 0 99 50 0 99 55 0 99 60 0 99 65 0 99 70 \
+ 0 99 75 0 99 80 0 99 85 0 99 90 0 99 95 \
+ 0 99 99 0 95 99 0 90 99 0 85 99 0 80 99 \
+ 0 75 99 0 70 99 0 65 99 0 60 99 0 55 99 \
+ 0 50 99 0 45 99 0 40 99 0 35 99 0 30 99 \
+ 0 25 99 0 20 99 0 15 99 0 10 99 0 5 99 \
+ 0 0 99 5 0 99 10 0 99 15 0 99 20 0 99 \
+25 0 99 30 0 99 35 0 99 40 0 99 45 0 99 \
+50 0 99 55 0 99 60 0 99 65 0 99 70 0 99 \
+75 0 99 80 0 99 85 0 99 90 0 99 95 0 99 \
+99 0 99 99 0 90 99 0 80 99 0 70 99 0 60 \
+99 0 50 99 0 40 99 0 30 99 0 20 99 0 10] \r\
+make "shade 50 \r\
+tasetshade :shade \r')
+
+ def _numbers_palette(self):
+ """ The basic Turtle Art numbers palette """
+
+ palette = make_palette('numbers',
+ colors=["#FF00FF", "#A000A0"],
+ help_string=_('Palette of numeric operators'))
+
+ primitive_dictionary['plus'] = self._prim_plus
+ palette.add_block('plus2',
+ style='number-style',
+ label='+',
+ special_name=_('plus'),
+ string_or_number=True,
+ prim_name='plus',
+ logo_command='sum',
+ help_string=_('adds two alphanumeric inputs'))
+ self.tw.lc.def_prim(
+ 'plus', 2, lambda self, x, y: primitive_dictionary['plus'](x, y))
+
+ primitive_dictionary['minus'] = self._prim_minus
+ palette.add_block('minus2',
+ style='number-style-porch',
+ label='–',
+ special_name=_('minus'),
+ prim_name='minus',
+ logo_command='taminus',
+ help_string=_('subtracts bottom numeric input from \
+top numeric input'))
+ self.tw.lc.def_prim(
+ 'minus', 2, lambda self, x, y: primitive_dictionary['minus'](x, y))
+ define_logo_function('taminus', 'to taminus :y :x\routput sum :x minus \
+:y\rend\r')
+
+ primitive_dictionary['product'] = self._prim_product
+ palette.add_block('product2',
+ style='number-style',
+ label='×',
+ special_name=_('multiply'),
+ prim_name='product',
+ logo_command='product',
+ help_string=_('multiplies two numeric inputs'))
+ self.tw.lc.def_prim(
+ 'product', 2,
+ lambda self, x, y: primitive_dictionary['product'](x, y))
+
+ primitive_dictionary['division'] = self._prim_careful_divide
+ palette.add_block('division2',
+ style='number-style-porch',
+ label='/',
+ special_name=_('divide'),
+ prim_name='division',
+ logo_command='quotient',
+ help_string=_('divides top numeric input (numerator) \
+by bottom numeric input (denominator)'))
+ self.tw.lc.def_prim(
+ 'division', 2,
+ lambda self, x, y: primitive_dictionary['division'](x, y))
+
+ primitive_dictionary['id'] = self._prim_identity
+ palette.add_block('identity2',
+ style='number-style-1arg',
+ label='←',
+ special_name=_('identity'),
+ prim_name='id',
+ help_string=_('identity operator used for extending \
+blocks'))
+ self.tw.lc.def_prim('id', 1,
+ lambda self, x: primitive_dictionary['id'](x))
+
+ primitive_dictionary['remainder'] = self._prim_mod
+ palette.add_block('remainder2',
+ style='number-style-porch',
+ label=_('mod'),
+ special_name=_('mod'),
+ prim_name='remainder',
+ logo_command='remainder',
+ help_string=_('modular (remainder) operator'))
+ self.tw.lc.def_prim('remainder', 2,
+ lambda self, x, y: primitive_dictionary['remainder'](x, y))
+
+ primitive_dictionary['sqrt'] = self._prim_sqrt
+ palette.add_block('sqrt',
+ hidden=True,
+ style='number-style-1arg',
+ label=_('√'),
+ special_name=_('square root'),
+ prim_name='sqrt',
+ logo_command='tasqrt',
+ help_string=_('calculates square root'))
+ self.tw.lc.def_prim('sqrt', 1,
+ lambda self, x: primitive_dictionary['sqrt'](x))
+
+ primitive_dictionary['random'] = self._prim_random
+ palette.add_block('random',
+ hidden=True,
+ style='number-style-block',
+ label=[_('random'), _('min'), _('max')],
+ default=[0, 100],
+ prim_name='random',
+ logo_command='tarandom',
+ help_string=_('returns random number between minimum \
+(top) and maximum (bottom) values'))
+ self.tw.lc.def_prim(
+ 'random', 2, lambda self, x, y: primitive_dictionary['random'](
+ x, y))
+ define_logo_function('tarandom', 'to tarandom :min :max\r \
+output (random (:max - :min)) + :min\rend\r')
+
+ palette.add_block('number',
+ style='box-style',
+ label='100',
+ default=100,
+ special_name=_('number'),
+ help_string=_('used as numeric input in mathematic \
+operators'))
+
+ primitive_dictionary['more'] = self._prim_more
+ palette.add_block('greater2',
+ hidden=True,
+ style='compare-porch-style',
+ label='>',
+ special_name=_('greater than'),
+ string_or_number=True,
+ prim_name='greater?',
+ logo_command='greater?',
+ help_string=_('logical greater-than operator'))
+ self.tw.lc.def_prim(
+ 'greater?', 2,
+ lambda self, x, y: primitive_dictionary['more'](x, y))
+
+ if self.tw.canvas.width > 1024:
+ self._make_constant(palette, 'true', _('True'), 1)
+
+ primitive_dictionary['less'] = self._prim_less
+ palette.add_block('less2',
+ hidden=True,
+ style='compare-porch-style',
+ label='<',
+ special_name=_('less than'),
+ string_or_number=True,
+ prim_name='less?',
+ logo_command='less?',
+ help_string=_('logical less-than operator'))
+ self.tw.lc.def_prim(
+ 'less?', 2, lambda self, x, y: primitive_dictionary['less'](x, y))
+
+ if self.tw.canvas.width > 1024:
+ self._make_constant(palette, 'false', _('False'), 0)
+
+ primitive_dictionary['equal'] = self._prim_equal
+ palette.add_block('equal2',
+ hidden=True,
+ style='compare-style',
+ label='=',
+ string_or_number=True,
+ special_name=_('equal'),
+ prim_name='equal?',
+ logo_command='equal?',
+ help_string=_('logical equal-to operator'))
+ self.tw.lc.def_prim(
+ 'equal?', 2, lambda self, x, y: primitive_dictionary['equal'](x, y))
+
+ palette.add_block('not',
+ hidden=True,
+ style='not-style',
+ label=_('not'),
+ prim_name='not',
+ logo_command='not',
+ help_string=_('logical NOT operator'))
+ self.tw.lc.def_prim('not', 1, lambda self, x: not x)
+
+ primitive_dictionary['and'] = self._prim_and
+ palette.add_block('and2',
+ hidden=True,
+ style='boolean-style',
+ label=_('and'),
+ prim_name='and',
+ logo_command='and',
+ special_name=_('and'),
+ help_string=_('logical AND operator'))
+ self.tw.lc.def_prim(
+ 'and', 2, lambda self, x, y: primitive_dictionary['and'](x, y))
+
+ primitive_dictionary['or'] = self._prim_or
+ palette.add_block('or2',
+ hidden=True,
+ style='boolean-style',
+ label=_('or'),
+ prim_name='or',
+ logo_command='or',
+ special_name=_('or'),
+ help_string=_('logical OR operator'))
+ self.tw.lc.def_prim(
+ 'or', 2, lambda self, x, y: primitive_dictionary['or'](x, y))
+
+ def _flow_palette(self):
+ """ The basic Turtle Art flow palette """
+
+ palette = make_palette('flow',
+ colors=["#FFC000", "#A08000"],
+ help_string=_('Palette of flow operators'))
+
+ primitive_dictionary['wait'] = self._prim_wait
+ palette.add_block('wait',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('wait'),
+ prim_name='wait',
+ default=1,
+ logo_command='wait',
+ help_string=_('pauses program execution a specified \
+number of seconds'))
+ self.tw.lc.def_prim('wait', 1, primitive_dictionary['wait'], True)
+
+ primitive_dictionary['forever'] = self._prim_forever
+ palette.add_block('forever',
+ hidden=True,
+ style='flow-style',
+ label=_('forever'),
+ prim_name='forever',
+ default=[None, 'vspace'],
+ logo_command='forever',
+ help_string=_('loops forever'))
+ self.tw.lc.def_prim('forever', 1, primitive_dictionary['forever'], True)
+
+ primitive_dictionary['repeat'] = self._prim_repeat
+ palette.add_block('repeat',
+ style='flow-style-1arg',
+ label=[' ', _('repeat')],
+ prim_name='repeat',
+ default=[4, None, 'vspace'],
+ logo_command='repeat',
+ special_name=_('repeat'),
+ help_string=_('loops specified number of times'))
+ self.tw.lc.def_prim('repeat', 2, primitive_dictionary['repeat'], True)
+
+ primitive_dictionary['if'] = self._prim_if
+ palette.add_block('if',
+ hidden=True,
+ style='flow-style-boolean',
+ label=[_('if'), ' ', _('then')],
+ prim_name='if',
+ default=[None, None, 'vspace'],
+ special_name=_('if then'),
+ logo_command='if',
+ help_string=_('if-then operator that uses boolean \
+operators from Numbers palette'))
+ self.tw.lc.def_prim('if', 2, primitive_dictionary['if'], True)
+
+ primitive_dictionary['ifelse'] = self._prim_ifelse
+ palette.add_block('ifelse',
+ hidden=True,
+ style='flow-style-else',
+ label=[_('if'), ' ', _('then else')],
+ prim_name='ifelse',
+ default=[None, 'vspace', None, 'vspace'],
+ logo_command='ifelse',
+ special_name=_('if then else'),
+ help_string=_('if-then-else operator that uses \
+boolean operators from Numbers palette'))
+ self.tw.lc.def_prim('ifelse', 3, primitive_dictionary['ifelse'], True)
+
+ palette.add_block('hspace',
+ style='flow-style-tail',
+ label=' ',
+ prim_name='nop',
+ special_name=_('horizontal space'),
+ help_string=_('jogs stack right'))
+ self.tw.lc.def_prim('nop', 0, lambda self: None)
+
+ palette.add_block('vspace',
+ style='basic-style-extended-vertical',
+ label=' ',
+ prim_name='nop',
+ special_name=_('vertical space'),
+ help_string=_('jogs stack down'))
+ self.tw.lc.def_prim('nop', 0, lambda self: None)
+
+ primitive_dictionary['stopstack'] = self._prim_stopstack
+ palette.add_block('stopstack',
+ hidden=True,
+ style='basic-style-tail',
+ label=_('stop action'),
+ prim_name='stopstack',
+ logo_command='stop',
+ help_string=_('stops current action'))
+ self.tw.lc.def_prim('stopstack', 0,
+ lambda self: primitive_dictionary['stopstack']())
+
+ def _blocks_palette(self):
+ """ The basic Turtle Art blocks palette """
+
+ palette = make_palette('blocks',
+ colors=["#FFFF00", "#A0A000"],
+ help_string=_('Palette of variable blocks'))
+
+ primitive_dictionary['start'] = self._prim_start
+ palette.add_block('start',
+ style='basic-style-head',
+ label=_('start'),
+ prim_name='start',
+ logo_command='to start\r',
+ help_string=_('connects action to toolbar run \
+buttons'))
+ self.tw.lc.def_prim('start', 0,
+ lambda self: primitive_dictionary['start']())
+
+ primitive_dictionary['setbox'] = self._prim_setbox
+ palette.add_block('storeinbox1',
+ style='basic-style-1arg',
+ label=_('store in box 1'),
+ string_or_number=True,
+ prim_name='storeinbox1',
+ default=100,
+ logo_command='make "box1',
+ help_string=_('stores numeric value in Variable 1'))
+ self.tw.lc.def_prim('storeinbox1', 1,
+ lambda self, x: primitive_dictionary['setbox'](
+ 'box1', None, x))
+
+ palette.add_block('storeinbox2',
+ style='basic-style-1arg',
+ label=_('store in box 2'),
+ string_or_number=True,
+ prim_name='storeinbox2',
+ default=100,
+ logo_command='make "box2',
+ help_string=_('stores numeric value in Variable 2'))
+ self.tw.lc.def_prim('storeinbox2', 1,
+ lambda self, x: primitive_dictionary['setbox'](
+ 'box2', None, x))
+
+ palette.add_block('string',
+ hidden=True,
+ style='box-style',
+ label=_('text'),
+ default=_('text'),
+ special_name=_('text'),
+ help_string=_('string value'))
+
+ palette.add_block('box1',
+ style='box-style',
+ label=_('box 1'),
+ prim_name='box1',
+ logo_command=':box1',
+ help_string=_('Variable 1 (numeric value)'),
+ value_block=True)
+ self.tw.lc.def_prim('box1', 0, lambda self: self.tw.lc.boxes['box1'])
+
+ palette.add_block('box2',
+ style='box-style',
+ label=_('box 2'),
+ prim_name='box2',
+ logo_command=':box2',
+ help_string=_('Variable 2 (numeric value)'),
+ value_block=True)
+ self.tw.lc.def_prim('box2', 0, lambda self: self.tw.lc.boxes['box2'])
+
+ primitive_dictionary['box'] = self._prim_box
+ palette.add_block('box',
+ hidden=True,
+ style='number-style-1strarg',
+ label=_('box'),
+ prim_name='box',
+ string_or_number=True,
+ default=_('my box'),
+ logo_command='box',
+ help_string=_('named variable (numeric value)'))
+ self.tw.lc.def_prim('box', 1,
+ lambda self, x: primitive_dictionary['box'](x))
+
+ palette.add_block('storein',
+ hidden=True,
+ style='basic-style-2arg',
+ label=[_('store in'), _('box'), _('value')],
+ string_or_number=True,
+ prim_name='storeinbox',
+ logo_command='storeinbox',
+ default=[_('my box'), 100],
+ help_string=_('stores numeric value in named \
+variable'))
+ self.tw.lc.def_prim('storeinbox', 2,
+ lambda self, x, y: primitive_dictionary['setbox'](
+ 'box3', x, y))
+
+ palette.add_block('hat',
+ hidden=True,
+ style='basic-style-head-1arg',
+ label=_('action'),
+ prim_name='nop3',
+ string_or_number=True,
+ default=_('action'),
+ logo_command='to action',
+ help_string=_('top of nameable action stack'))
+ self.tw.lc.def_prim('nop3', 1, lambda self, x: None)
+
+ palette.add_block('hat1',
+ style='basic-style-head',
+ label=_('action 1'),
+ prim_name='nop1',
+ logo_command='to stack1\r',
+ help_string=_('top of Action 1 stack'))
+ self.tw.lc.def_prim('nop1', 0, lambda self: None)
+
+ palette.add_block('hat2',
+ style='basic-style-head',
+ label=_('action 2'),
+ prim_name='nop2',
+ logo_command='to stack2\r',
+ help_string=_('top of Action 2 stack'))
+ self.tw.lc.def_prim('nop2', 0, lambda self: None)
+
+ primitive_dictionary['stack'] = self._prim_stack
+ palette.add_block('stack',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('action'),
+ string_or_number=True,
+ prim_name='stack',
+ logo_command='action',
+ default=_('action'),
+ help_string=_('invokes named action stack'))
+ self.tw.lc.def_prim('stack', 1, primitive_dictionary['stack'], True)
+
+ primitive_dictionary['stack1'] = self._prim_stack1
+ palette.add_block('stack1',
+ style='basic-style-extended-vertical',
+ label=_('action 1'),
+ prim_name='stack1',
+ logo_command='stack1',
+ help_string=_('invokes Action 1 stack'))
+ self.tw.lc.def_prim('stack1', 0, primitive_dictionary['stack1'], True)
+
+ primitive_dictionary['stack2'] = self._prim_stack2
+ palette.add_block('stack2',
+ style='basic-style-extended-vertical',
+ label=_('action 2'),
+ prim_name='stack2',
+ logo_command='stack2',
+ help_string=_('invokes Action 2 stack'))
+ self.tw.lc.def_prim('stack2', 0, primitive_dictionary['stack2'], True)
+
+ def _trash_palette(self):
+ """ The basic Turtle Art turtle palette """
+
+ palette = make_palette('trash',
+ colors=["#FFFF00", "#A0A000"],
+ help_string=_('trash'))
+
+ palette.add_block('empty',
+ style='blank-style',
+ label=_('empty trash'),
+ help_string=_('permanently deletes items in trash'))
+
+ palette.add_block('restoreall',
+ style='blank-style',
+ label=_('restore all'),
+ help_string=_('restore all blocks from trash'))
+
+ palette.add_block('trashall',
+ style='blank-style',
+ label=_('clear all'),
+ help_string=_('move all blocks to trash'))
+
+ # Block primitives
+
+ def _prim_clean(self):
+ ''' Clean block '''
+ self.tw.lc.prim_clear
+ self.tw.display_coordinates()
+ self.tw.parent.restore_challenge()
+
+ def _prim_and(self, x, y):
+ """ Logical and """
+ return x & y
+
+ def _prim_arc(self, cmd, value1, value2):
+ """ Turtle draws an arc of degree, radius """
+ cmd(float(value1), float(value2))
+ self.tw.lc.update_label_value(
+ 'xcor', self.tw.canvas.xcor / self.tw.coord_scale)
+ self.tw.lc.update_label_value(
+ 'ycor', self.tw.canvas.ycor / self.tw.coord_scale)
+ self.tw.lc.update_label_value('heading', self.tw.canvas.heading)
+
+ def _prim_box(self, x):
+ """ Retrieve value from named box """
+ if type(convert(x, float, False)) == float:
+ if int(float(x)) == x:
+ x = int(x)
+ try:
+ return self.tw.lc.boxes['box3' + str(x)]
+ except KeyError:
+ raise logoerror("#emptybox")
+
+ def _prim_forever(self, blklist):
+ """ Do list forever """
+ while True:
+ self.tw.lc.icall(self.tw.lc.evline, blklist[:])
+ yield True
+ if self.tw.lc.procstop:
+ break
+ self.tw.lc.ireturn()
+ yield True
+
+ def _prim_if(self, boolean, blklist):
+ """ If bool, do list """
+ if boolean:
+ self.tw.lc.icall(self.tw.lc.evline, blklist[:])
+ yield True
+ self.tw.lc.ireturn()
+ yield True
+
+ def _prim_ifelse(self, boolean, list1, list2):
+ """ If bool, do list1, else do list2 """
+ if boolean:
+ self.tw.lc.ijmp(self.tw.lc.evline, list1[:])
+ yield True
+ else:
+ self.tw.lc.ijmp(self.tw.lc.evline, list2[:])
+ yield True
+
+ def _prim_move(self, cmd, value1, value2=None, pendown=True):
+ """ Turtle moves by method specified in value1 """
+ if value2 is None:
+ cmd(value1)
+ else:
+ cmd(float(value1), float(value2), pendown=pendown)
+ self.tw.lc.update_label_value('xcor',
+ self.tw.canvas.xcor / self.tw.coord_scale)
+ self.tw.lc.update_label_value('ycor',
+ self.tw.canvas.ycor / self.tw.coord_scale)
+
+ def _prim_or(self, x, y):
+ """ Logical or """
+ return x | y
+
+ def _prim_repeat(self, num, blklist):
+ """ Repeat list num times. """
+ num = self.tw.lc.int(num)
+ for i in range(num):
+ self.tw.lc.icall(self.tw.lc.evline, blklist[:])
+ yield True
+ if self.tw.lc.procstop:
+ break
+ self.tw.lc.ireturn()
+ yield True
+
+ def _prim_right(self, value):
+ """ Turtle rotates clockwise """
+ self.tw.canvas.right(float(value))
+ self.tw.lc.update_label_value('heading', self.tw.canvas.heading)
+
+ def _prim_set(self, name, cmd, value=None):
+ """ Set a value and update the associated value blocks """
+ if value is not None:
+ cmd(value)
+ self.tw.lc.update_label_value(name, value)
+
+ def _prim_setbox(self, name, x, val):
+ """ Define value of named box """
+ if x is not None:
+ if type(convert(x, float, False)) == float:
+ if int(float(x)) == x:
+ x = int(x)
+ self.tw.lc.boxes[name + str(x)] = val
+ return
+
+ self.tw.lc.boxes[name] = val
+ self.tw.lc.update_label_value(name, val)
+
+ def _prim_stack(self, x):
+ """ Process a named stack """
+ if type(convert(x, float, False)) == float:
+ if int(float(x)) == x:
+ x = int(x)
+ if 'stack3' + str(x) not in self.tw.lc.stacks or \
+ self.tw.lc.stacks['stack3' + str(x)] is None:
+ raise logoerror("#nostack")
+ self.tw.lc.icall(self.tw.lc.evline,
+ self.tw.lc.stacks['stack3' + str(x)][:])
+ yield True
+ self.tw.lc.procstop = False
+ self.tw.lc.ireturn()
+ yield True
+
+ def _prim_stack1(self):
+ """ Process Stack 1 """
+ if self.tw.lc.stacks['stack1'] is None:
+ raise logoerror("#nostack")
+ self.tw.lc.icall(self.tw.lc.evline,
+ self.tw.lc.stacks['stack1'][:])
+ yield True
+ self.tw.lc.procstop = False
+ self.tw.lc.ireturn()
+ yield True
+
+ def _prim_stack2(self):
+ """ Process Stack 2 """
+ if self.tw.lc.stacks['stack2'] is None:
+ raise logoerror("#nostack")
+ self.tw.lc.icall(self.tw.lc.evline, self.tw.lc.stacks['stack2'][:])
+ yield True
+ self.tw.lc.procstop = False
+ self.tw.lc.ireturn()
+ yield True
+
+ def _prim_start(self):
+ """ Start block: recenter """
+ if self.tw.running_sugar:
+ self.tw.activity.recenter()
+
+ def _prim_stopstack(self):
+ """ Stop execution of a stack """
+ self.tw.lc.procstop = True
+
+ def _prim_wait(self, wait_time):
+ """ Show the turtle while we wait """
+ self.tw.active_turtle.show()
+ endtime = _millisecond() + wait_time * 1000.
+ while _millisecond() < endtime:
+ yield True
+ self.tw.active_turtle.hide()
+ self.tw.lc.ireturn()
+ yield True
+
+ # Math primitivies
+
+ def _prim_careful_divide(self, x, y):
+ """ Raise error on divide by zero """
+ if type(x) == list and _num_type(y):
+ z = []
+ for i in range(len(x)):
+ try:
+ z.append(x[i] / y)
+ except ZeroDivisionError:
+ raise logoerror("#zerodivide")
+ return z
+ try:
+ return x / y
+ except ZeroDivisionError:
+ raise logoerror("#zerodivide")
+ except TypeError:
+ try:
+ return self._string_to_num(x) / self._string_to_num(y)
+ except ZeroDivisionError:
+ raise logoerror("#zerodivide")
+ except ValueError:
+ raise logoerror("#syntaxerror")
+ except TypeError:
+ raise logoerror("#notanumber")
+
+ def _prim_equal(self, x, y):
+ """ Numeric and logical equal """
+ if type(x) == list and type(y) == list:
+ for i in range(len(x)):
+ if x[i] != y[i]:
+ return False
+ return True
+ try:
+ return float(x) == float(y)
+ except ValueError:
+ typex, typey = False, False
+ if strtype(x):
+ typex = True
+ if strtype(y):
+ typey = True
+ if typex and typey:
+ return x == y
+ try:
+ return self._string_to_num(x) == self._string_to_num(y)
+ except TypeError:
+ raise logoerror("#syntaxerror")
+
+ def _prim_less(self, x, y):
+ """ Compare numbers and strings """
+ try:
+ return float(x) < float(y)
+ except ValueError:
+ typex, typey = False, False
+ if strtype(x):
+ typex = True
+ if strtype(y):
+ typey = True
+ if typex and typey:
+ return x < y
+ try:
+ return self._string_to_num(x) < self._string_to_num(y)
+ except TypeError:
+ raise logoerror("#notanumber")
+
+ def _prim_more(self, x, y):
+ """ Compare numbers and strings """
+ return self._prim_less(y, x)
+
+ def _prim_plus(self, x, y):
+ """ Add numbers, concat strings """
+ if _num_type(x) and _num_type(y):
+ return(x + y)
+ elif type(x) == list and type(y) == list:
+ z = []
+ for i in range(len(x)):
+ z.append(x[i] + y[i])
+ return(z)
+ else:
+ if _num_type(x):
+ xx = str(round_int(x))
+ else:
+ xx = str(x)
+ if _num_type(y):
+ yy = str(round_int(y))
+ else:
+ yy = str(y)
+ return(xx + yy)
+
+ def _prim_minus(self, x, y):
+ """ Numerical subtraction """
+ if _num_type(x) and _num_type(y):
+ return(x - y)
+ elif type(x) == list and type(y) == list:
+ z = []
+ for i in range(len(x)):
+ z.append(x[i] - y[i])
+ return(z)
+ try:
+ return self._string_to_num(x) - self._string_to_num(y)
+ except TypeError:
+ raise logoerror("#notanumber")
+
+ def _prim_product(self, x, y):
+ """ Numerical multiplication """
+ if _num_type(x) and _num_type(y):
+ return(x * y)
+ elif type(x) == list and _num_type(y):
+ z = []
+ for i in range(len(x)):
+ z.append(x[i] * y)
+ return(z)
+ elif type(y) == list and _num_type(x):
+ z = []
+ for i in range(len(y)):
+ z.append(y[i] * x)
+ return(z)
+ try:
+ return self._string_to_num(x) * self._string_to_num(y)
+ except TypeError:
+ raise logoerror("#notanumber")
+
+ def _prim_mod(self, x, y):
+ """ Numerical mod """
+ if _num_type(x) and _num_type(y):
+ return(x % y)
+ try:
+ return self._string_to_num(x) % self._string_to_num(y)
+ except TypeError:
+ raise logoerror("#notanumber")
+ except ValueError:
+ raise logoerror("#syntaxerror")
+
+ def _prim_sqrt(self, x):
+ """ Square root """
+ if _num_type(x):
+ if x < 0:
+ raise logoerror("#negroot")
+ return sqrt(x)
+ try:
+ return sqrt(self._string_to_num(x))
+ except ValueError:
+ raise logoerror("#negroot")
+ except TypeError:
+ raise logoerror("#notanumber")
+
+ def _prim_random(self, x, y):
+ """ Random integer """
+ if _num_type(x) and _num_type(y):
+ return(int(round(uniform(x, y), 0)))
+ xx, xflag = chr_to_ord(x)
+ yy, yflag = chr_to_ord(y)
+ if xflag and yflag:
+ return chr(int(round(uniform(xx, yy), 0)))
+ if not xflag:
+ xx = self._string_to_num(x)
+ if not yflag:
+ yy = self._string_to_num(y)
+ try:
+ return(int(round(uniform(xx, yy), 0)))
+ except TypeError:
+ raise logoerror("#notanumber")
+
+ def _prim_identity(self, x):
+ """ Identity function """
+ return(x)
+
+ # Utilities
+
+ def _string_to_num(self, x):
+ """ Try to comvert a string to a number """
+ if type(x) is float:
+ return(x)
+ if type(x) is int:
+ return(x)
+ if type(x) is ord:
+ return(int(x))
+ if type(x) is list:
+ raise logoerror("#syntaxerror")
+ xx = convert(x.replace(self.tw.decimal_point, '.'), float)
+ if type(xx) is float:
+ return xx
+ else:
+ xx, xflag = chr_to_ord(x)
+ if xflag:
+ return xx
+ else:
+ raise logoerror("#syntaxerror")
+
+ def _make_constant(self, palette, block_name, label, constant):
+ """ Factory for constant blocks """
+ palette.add_block(block_name, style='box-style',
+ label=label, prim_name=block_name,
+ logo_command=block_name)
+ self.tw.lc.def_prim(block_name, 0, lambda self: constant)
diff --git a/TurtleArt/tablock.py b/TurtleArt/tablock.py
new file mode 100644
index 0000000..f835994
--- /dev/null
+++ b/TurtleArt/tablock.py
@@ -0,0 +1,970 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2010,11 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.
+
+import gtk
+from gettext import gettext as _
+
+from taconstants import EXPANDABLE, EXPANDABLE_ARGS, OLD_NAMES, CONSTANTS, \
+ STANDARD_STROKE_WIDTH, BLOCK_SCALE, BOX_COLORS, GRADIENT_COLOR
+from tapalette import palette_blocks, block_colors, expandable_blocks, \
+ content_blocks, block_names, block_primitives, block_styles, \
+ special_block_colors
+from tasprite_factory import SVG, svg_str_to_pixbuf
+import sprites
+
+from tautils import debug_output, error_output
+
+
+class Blocks:
+
+ """ A class for the list of blocks and everything they share in common """
+
+ def __init__(self, font_scale_factor=1, decimal_point='.'):
+ self.list = []
+ self.font_scale_factor = font_scale_factor
+ self.decimal_point = decimal_point
+
+ def get_block(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, block):
+ self.list.append(block)
+
+ def remove_from_list(self, block):
+ if block in self.list:
+ self.list.remove(block)
+
+ def print_list(self, block_type=None):
+ for i, block in enumerate(self.list):
+ if block_type is None or block_type == block.type:
+ print "%d: %s" % (i, block.name)
+
+ def set_scale(self, scale):
+ for b in self.list:
+ for i in range(len(b._font_size)):
+ b._font_size[i] *= b.scale * scale / self.font_scale_factor
+ self.font_scale_factor = scale
+
+ def spr_to_block(self, spr):
+ for b in self.list:
+ if spr == b.spr:
+ return b
+ return None
+
+ def get_next_block(self, block):
+ if block is None:
+ return None
+ try:
+ i = self.list.index(block)
+ except ValueError:
+ return None
+ i += 1
+ if i < len(self.list):
+ return self.list[i]
+ else:
+ return self.list[0]
+
+ def get_next_block_of_same_type(self, block):
+ if block is None:
+ return None
+ type = block.type
+ i = 0
+ while block is not None:
+ block = self.get_next_block(block)
+ if block is not None:
+ if block.type == type:
+ return block
+ if i == len(self.list):
+ break
+ i += 1
+ return None
+
+ def get_similar_blocks(self, block_type, name):
+ block_list = []
+ if type(name) == type(''):
+ for block in self.list:
+ if block.type == block_type and block.name == name:
+ block_list.append(block)
+ else:
+ for block in self.list:
+ if block.type == block_type and block.name in name:
+ block_list.append(block)
+ return block_list
+
+
+class Block:
+ """ A class for the individual blocks """
+
+ def __init__(self, block_list, sprite_list, name, x, y, type='block',
+ values=[], scale=BLOCK_SCALE[0],
+ colors=['#A0A0A0', '#808080']):
+
+ self.block_list = block_list
+ self.spr = None
+ self.shapes = [None, None]
+ self.name = name
+ self.colors = colors
+ self._custom_colors = False
+ self.scale = scale
+ self.docks = None
+ self.connections = None
+ self.status = None
+ self.values = []
+ self.primitive = None
+ self.type = type
+ self.dx = 0
+ self.ex = 0
+ self.ey = 0
+ self._ei = 0
+ self._font_size = [6.0, 4.5]
+ self._image = None
+ self._visible = True
+
+ self.block_methods = {
+ 'basic-style': self._make_basic_style,
+ 'blank-style': self._make_blank_style,
+ 'basic-style-head': self._make_basic_style_head,
+ 'basic-style-head-1arg': self._make_basic_style_head_1arg,
+ 'basic-style-tail': self._make_basic_style_tail,
+ 'basic-style-extended': [self._make_basic_style, 16, 16],
+ 'basic-style-extended-vertical': [self._make_basic_style, 0, 4],
+ 'basic-style-1arg': self._make_basic_style_1arg,
+ 'basic-style-2arg': self._make_basic_style_2arg,
+ 'basic-style-3arg': self._make_basic_style_3arg,
+ 'basic-style-var-arg': self._make_basic_style_var_arg,
+ 'invisible': self._make_invisible_style,
+ 'bullet-style': self._make_bullet_style,
+ 'box-style': self._make_box_style,
+ 'box-style-media': self._make_media_style,
+ 'number-style': self._make_number_style,
+ 'number-style-block': self._make_number_style_block,
+ 'number-style-porch': self._make_number_style_porch,
+ 'number-style-1arg': self._make_number_style_1arg,
+ 'number-style-1strarg': self._make_number_style_1strarg,
+ 'number-style-var-arg': self._make_number_style_var_arg,
+ 'compare-style': self._make_compare_style,
+ 'compare-porch-style': self._make_compare_porch_style,
+ 'boolean-style': self._make_boolean_style,
+ 'not-style': self._make_not_style,
+ 'flow-style': self._make_flow_style,
+ 'flow-style-tail': self._make_flow_style_tail,
+ 'flow-style-1arg': self._make_flow_style_1arg,
+ 'flow-style-boolean': self._make_flow_style_boolean,
+ 'flow-style-else': self._make_flow_style_else,
+ 'collapsible-top': [self._make_collapsible_style_top, True, True],
+ 'collapsible-top-no-arm': [self._make_collapsible_style_top,
+ False, True],
+ 'collapsible-top-no-label': [self._make_collapsible_style_top,
+ True, False],
+ 'collapsible-top-no-arm-no-label': [
+ self._make_collapsible_style_top, False, False],
+ 'collapsible-bottom': self._make_collapsible_style_bottom,
+ 'portfolio-style-2x2': self._make_portfolio_style_2x2,
+ 'portfolio-style-1x1': self._make_portfolio_style_1x1,
+ 'portfolio-style-2x1': self._make_portfolio_style_2x1,
+ 'portfolio-style-1x2': self._make_portfolio_style_1x2}
+
+ if self.name in OLD_NAMES:
+ self.name = OLD_NAMES[self.name]
+
+ for i in range(len(self._font_size)):
+ self._font_size[i] *= self.scale * \
+ self.block_list.font_scale_factor
+
+ for v in (values):
+ self.values.append(v)
+
+ # If there is already a block with the same name, reuse it
+ copy_block = None
+ if self.cloneable():
+ for b in self.block_list.list:
+ if b.scale == self.scale and b.name == self.name:
+ copy_block = b
+ break
+ self._new_block_from_factory(sprite_list, x, y, copy_block)
+
+ if name in block_primitives:
+ self.primitive = block_primitives[self.name]
+
+ self.block_list.append_to_list(self)
+
+ def get_visibility(self):
+ ''' Should block be visible on the palette? '''
+ return self._visible
+
+ def set_visibility(self, state):
+ ''' Should block be visible? '''
+ self._visible = state
+ if self._visible:
+ self.spr.restore()
+ else:
+ self.spr.hide()
+
+ def expandable(self):
+ """ Can this block be expanded? """
+ if self.name in EXPANDABLE:
+ return True
+ if self.name in expandable_blocks:
+ return True
+ if self.name in EXPANDABLE_ARGS:
+ return True
+ return False
+
+ def cloneable(self):
+ """ Is it safe to clone this block? """
+ if self.expandable():
+ return False
+ if self.name in block_styles['box-style']:
+ return False
+ if self.name in ['sandwichtop', 'sandwichtop_no_label']:
+ return False
+ return True
+
+ def highlight(self):
+ """ We may want to highlight a block... """
+ if self.spr is not None:
+ self.spr.set_shape(self.shapes[1])
+
+ def unhighlight(self):
+ """ Or unhighlight it. """
+ if self.spr is not None:
+ self.spr.set_shape(self.shapes[0])
+
+ def resize(self):
+ """ We need to resize some blocks on the fly so the labels fit. """
+ if self.spr is None:
+ return
+ dx = (self.spr.label_width() - self.spr.label_safe_width()) / \
+ self.scale
+ if dx != 0:
+ self.dx += dx
+ if self.dx < 0:
+ self.dx = 0
+ self.refresh()
+
+ def set_image(self, image, x, y):
+ """ Some blocks get a skin. """
+ if self.spr is None:
+ return
+ self._image = image
+ self.spr.set_image(image, 1, x, y)
+
+ def scale_image(self, x, y, w, h):
+ """ The skin might need scaling. """
+ if self.spr is None:
+ return
+ if self._image is not None:
+ tmp = self._image.scale_simple(w, h, gtk.gdk.INTERP_NEAREST)
+ self.spr.set_image(tmp, 1, x, y)
+
+ def rescale(self, scale):
+ """ We may want to rescale blocks as well. """
+ if self.spr is None:
+ return
+ for i in range(len(self._font_size)):
+ self._font_size[i] /= self.scale
+ self.scale = scale
+ for i in range(len(self._font_size)):
+ self._font_size[i] *= self.scale
+ self._set_label_attributes()
+ self.svg.set_scale(self.scale)
+ self.refresh()
+ self.spr.inval()
+
+ def set_colors(self, colors):
+ self.colors = colors[:]
+ self._custom_colors = True
+ self.refresh()
+
+ def refresh(self):
+ if self.spr is None:
+ return
+ self._make_block(self.svg)
+ self._set_margins()
+ self.spr.set_shape(self.shapes[0])
+
+ def add_arg(self, keep_expanding=True):
+ """ We may want to add additional slots for arguments ("innies"). """
+ if self.spr is None:
+ return
+ h = self.svg.get_height()
+ self._ei += 1
+ if self.type == 'block' and keep_expanding:
+ self.svg.set_show(True)
+ else:
+ self.svg.set_show(False)
+ self.refresh()
+ return self.svg.get_height() - h
+
+ def expand_in_y(self, dy):
+ """ We may want to grow a block vertically. """
+ if self.spr is None:
+ return
+ self.ey += dy
+ if self.type == 'block':
+ if self.ey > 0:
+ self.svg.set_hide(True)
+ else:
+ self.svg.set_hide(False)
+ self.svg.set_show(True)
+ else:
+ self.svg.set_hide(False)
+ self.svg.set_show(False)
+ self.refresh()
+
+ def expand_in_x(self, dx):
+ """ We may want to grow a block horizontally. """
+ if self.spr is None:
+ return
+ self.ex += dx
+ if self.type == 'block':
+ self.svg.set_hide(True)
+ self.svg.set_show(True)
+ else:
+ self.svg.set_hide(False)
+ self.svg.set_show(False)
+ self.refresh()
+
+ def reset_x(self):
+ if self.spr is None:
+ return 0
+ dx = -self.ex
+ self.ex = 0
+ self.svg.set_hide(False)
+ if self.type == 'block':
+ self.svg.set_show(True)
+ else:
+ self.svg.set_show(False)
+ self.refresh()
+ return dx
+
+ def reset_y(self):
+ if self.spr is None:
+ return 0
+ dy = -self.ey
+ self.ey = 0
+ self.svg.set_hide(False)
+ if self.type == 'block':
+ self.svg.set_show(True)
+ else: # 'proto'
+ self.svg.set_show(False)
+ self.refresh()
+ return dy
+
+ def get_expand_x_y(self):
+ if self.spr is None:
+ return(0, 0)
+ return (self.ex, self.ey)
+
+ def _new_block_from_factory(self, sprite_list, x, y, copy_block=None):
+ self.svg = SVG()
+ self.svg.set_scale(self.scale)
+ self.svg.set_innie([False])
+ self.svg.set_outie(False)
+ self.svg.set_tab(True)
+ self.svg.set_slot(True)
+
+ if copy_block is not None:
+ self._left = copy_block._left
+ self._top = copy_block._top
+ self._right = copy_block._right
+ self._bottom = copy_block._bottom
+ self.dx = copy_block.dx
+ self.ex = copy_block.ex
+ self.ey = copy_block.ey
+ self.width = copy_block.width
+ self.height = copy_block.height
+ self.shapes[0] = copy_block.shapes[0]
+ if sprite_list is not None:
+ self.spr = sprites.Sprite(sprite_list, x, y, self.shapes[0])
+ self.spr._margins = copy_block.spr._margins[:]
+ if len(copy_block.shapes) > 1:
+ self.shapes[1] = copy_block.shapes[1]
+ self.docks = copy_block.docks[:]
+ else:
+ if self.expandable() and self.type == 'block':
+ self.svg.set_show(True)
+
+ self._make_block(self.svg)
+
+ if sprite_list is not None:
+ self.spr = sprites.Sprite(sprite_list, x, y, self.shapes[0])
+ self._set_margins()
+
+ self._set_label_attributes()
+ if (self.name == 'number' or self.name == 'string') and \
+ len(self.values) > 0:
+ for i, v in enumerate(self.values):
+ if v is not None:
+ if self.name == 'number':
+ self._set_labels(i,
+ str(v).replace('.', self.block_list.decimal_point))
+ else:
+ self._set_labels(i, str(v))
+ elif self.type == 'block' and self.name in CONSTANTS:
+ self._set_labels(0, block_names[self.name][0] + ' = ' + \
+ str(CONSTANTS[self.name]))
+
+ elif self.name in block_names:
+ for i, n in enumerate(block_names[self.name]):
+ self._set_labels(i, n)
+
+ if copy_block is None and self.spr is not None:
+ if self.spr.label_width() > self.spr.label_safe_width():
+ self.resize()
+
+ def _set_margins(self):
+ if self.spr is None:
+ return
+ self.spr.set_margins(self.svg.margins[0], self.svg.margins[1],
+ self.svg.margins[2], self.svg.margins[3])
+
+ def _set_label_attributes(self):
+ if self.spr is None:
+ return
+ if self.name in content_blocks:
+ n = len(self.values)
+ if n == 0:
+ n = 1 # Force a scale to be set, even if there is no value.
+ else:
+ if self.name in block_names:
+ n = len(block_names[self.name])
+ else:
+ debug_output('WARNING: unknown block name %s' % (self.name))
+ n = 0
+ for i in range(n):
+ if self.name in block_styles['compare-porch-style']:
+ self.spr.set_label_attributes(int(self._font_size[0] + 0.5),
+ True, 'center', 'bottom', i)
+ elif self.name in block_styles['number-style-porch']:
+ self.spr.set_label_attributes(int(self._font_size[0] + 0.5),
+ True, 'right', 'bottom', i)
+
+ elif self.name in block_styles['flow-style-boolean'] or \
+ self.name in block_styles['flow-style-else']:
+ self.spr.set_label_attributes(int(self._font_size[0] + 0.5),
+ True, 'left', 'middle', 0)
+ self.spr.set_label_attributes(int(self._font_size[1] + 0.5),
+ True, 'right', 'top', 1)
+ self.spr.set_label_attributes(int(self._font_size[1] + 0.5),
+ True, 'right', 'bottom', 2)
+ elif i == 1: # top
+ self.spr.set_label_attributes(int(self._font_size[1] + 0.5),
+ True, 'right', 'top', i)
+ elif i == 2: # bottom
+ self.spr.set_label_attributes(int(self._font_size[1] + 0.5),
+ True, 'right', 'bottom', i)
+ else:
+ self.spr.set_label_attributes(int(self._font_size[0] + 0.5),
+ True, 'center', 'middle', i)
+
+ def _set_labels(self, i, label):
+ if self.spr is None:
+ return
+ self.spr.set_label(label, i)
+
+ def _make_block(self, svg):
+ self._left = 0
+ self._top = 0
+ self._right = 0
+ self._bottom = 0
+ self.svg.set_stroke_width(STANDARD_STROKE_WIDTH)
+ self.svg.clear_docks()
+ for k in block_styles.keys():
+ if self.name in block_styles[k]:
+ if type(self.block_methods[k]) == type([]):
+ self.block_methods[k][0](svg, self.block_methods[k][1],
+ self.block_methods[k][2])
+ else:
+ self.block_methods[k](svg)
+ return
+ error_output('block type not found %s' % (self.name))
+ self.block_methods['basic-style'](svg)
+
+ def _set_colors(self, svg):
+ if self._custom_colors:
+ self.svg.set_colors(self.colors)
+ return
+ if self.name in BOX_COLORS:
+ self.colors = BOX_COLORS[self.name]
+ elif self.name in special_block_colors:
+ self.colors = special_block_colors[self.name]
+ else:
+ for p in range(len(palette_blocks)):
+ if self.name in palette_blocks[p]:
+ self.colors = block_colors[p]
+ self.svg.set_colors(self.colors)
+
+ def _make_basic_style(self, svg, extend_x=0, extend_y=0):
+ self.svg.expand(self.dx + self.ex + extend_x, self.ey + extend_y)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]], ['flow',
+ False, self.svg.docks[1][0], self.svg.docks[1][1]]]
+
+ def _make_blank_style(self, svg, extend_x=0, extend_y=0):
+ self.svg.expand(self.dx + self.ex + extend_x, self.ey + extend_y)
+ self.svg.set_slot(False)
+ self.svg.set_tab(False)
+ self.svg.set_tail(False)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = []
+
+ def _make_basic_style_head(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ self.svg.set_slot(False)
+ self.svg.set_cap(True)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['unavailable', False, 0, 0],
+ ['flow', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]]]
+
+ def _make_basic_style_head_1arg(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ self.svg.set_innie([True])
+ self.svg.set_slot(False)
+ self.svg.set_cap(True)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['unavailable', False, 0, 0],
+ ['string', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_basic_style_tail(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ self.svg.set_tab(False)
+ self.svg.set_tail(True)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['unavailable', False, 0, 0]]
+
+ def _make_basic_style_1arg(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ self.svg.set_innie([True])
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]]]
+
+ def _make_basic_style_2arg(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ self.svg.set_innie([True, True])
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['number', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['flow', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]]]
+
+ def _make_basic_style_3arg(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ self.svg.set_innie([True, True, True])
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['number', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['number', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]],
+ ['flow', False, self.svg.docks[4][0],
+ self.svg.docks[4][1]]]
+
+ def _make_basic_style_var_arg(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ innie = [True]
+ for i in range(self._ei):
+ innie.append(True)
+ self.svg.set_innie(innie)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+ for i in range(self._ei):
+ self.docks.append(['number', False, self.svg.docks[i + 2][0],
+ self.svg.docks[i + 2][1]])
+ self.docks.append(['flow', False, self.svg.docks[self._ei + 2][0],
+ self.svg.docks[self._ei + 2][1]])
+
+ def _make_bullet_style(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ innie = [True, True]
+ for i in range(self._ei):
+ innie.append(True)
+ self.svg.set_innie(innie)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['string', False, self.svg.docks[1][0],
+ self.svg.docks[1][1], '['],
+ ['string', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]]]
+ for i in range(self._ei):
+ self.docks.append(['string', False, self.svg.docks[i + 3][0],
+ self.svg.docks[i + 3][1]])
+ self.docks.append(['flow', False, self.svg.docks[self._ei + 3][0],
+ self.svg.docks[self._ei + 3][1], ']'])
+
+ def _make_box_style(self, svg):
+ self.svg.expand(60 + self.dx + self.ex, self.ey)
+ self._make_block_graphics(svg, self.svg.basic_box)
+ self.docks = [['number', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['unavailable', False, 0, 0]]
+
+ def _make_media_style(self, svg):
+ self.svg.expand(40 + self.dx + self.ex, 10 + self.ey)
+ self._make_block_graphics(svg, self.svg.basic_box)
+ self.docks = [['number', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['unavailable', False, 0, 0]]
+
+ def _make_number_style(self, svg):
+ self.svg.expand(self.dx + self.ex, self.ey)
+ self.svg.set_innie([True, True])
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ """
+ NOTE: The "outie" is added last, so the dock order in NUMBER_STYLE
+ blocks needs to be modified.
+ """
+ self.docks = [['number', True, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['number', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_number_style_var_arg(self, svg):
+ self.svg.expand(self.dx + self.ex, self.ey)
+ innie = [True]
+ for i in range(self._ei + 1):
+ innie.append(True)
+ self.svg.set_innie(innie)
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['number', True, self.svg.docks[2 + self._ei][0],
+ self.svg.docks[2 + self._ei][1]],
+ ['number', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]]]
+ for i in range(self._ei + 1):
+ self.docks.append(['number', False, self.svg.docks[i + 1][0],
+ self.svg.docks[i + 1][1]])
+ self.docks.append(['unavailable', False, 0, 0])
+
+ def _make_number_style_block(self, svg):
+ self.svg.expand(self.dx + self.ex, self.ey)
+ self.svg.set_innie([True, True])
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['number', True, self.svg.docks[2][0],
+ self.svg.docks[2][1], '('],
+ ['number', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['unavailable', False, 0, 0, ')']]
+
+ def _make_number_style_1arg(self, svg):
+ self.svg.expand(self.dx + self.ex, self.ey)
+ self.svg.set_innie([True])
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['number', True, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['number', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]]]
+
+ def _make_number_style_1strarg(self, svg):
+ self.svg.expand(self.dx + self.ex, self.ey)
+ self.svg.set_innie([True])
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['number', True, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['string', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['unavailable', False, 0, 0]]
+
+ def _make_number_style_porch(self, svg):
+ self.svg.expand(self.dx + self.ex, self.ey)
+ self.svg.set_innie([True, True])
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self.svg.set_porch(True)
+ self._make_block_graphics(svg, self.svg.basic_block)
+ self.docks = [['number', True, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['number', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_compare_style(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ self._make_block_graphics(svg, self.svg.boolean_compare)
+ self.docks = [['bool', True, self.svg.docks[0][0],
+ self.svg.docks[0][1], '('],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['number', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['unavailable', False, 0, 0, ')']]
+
+ def _make_compare_porch_style(self, svg):
+ self.svg.set_porch(True)
+ self._make_compare_style(svg)
+
+ def _make_boolean_style(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ self._make_block_graphics(svg, self.svg.boolean_and_or)
+ self.docks = [['bool', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['bool', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['bool', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]]]
+
+ def _make_not_style(self, svg):
+ self.svg.expand(15 + self.dx + self.ex, self.ey)
+ self._make_block_graphics(svg, self.svg.boolean_not)
+ self.docks = [['bool', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['bool', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_flow_style(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self._make_block_graphics(svg, self.svg.basic_flow)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1], '['],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1], ']']]
+
+ def _make_flow_style_tail(self, svg):
+ self.svg.expand(10 + self.dx + self.ex, self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(False)
+ self._make_block_graphics(svg, self.svg.basic_flow)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_flow_style_1arg(self, svg):
+ self.svg.expand(self.dx + self.ex, self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_innie([True])
+ self._make_block_graphics(svg, self.svg.basic_flow)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1], '['],
+ ['flow', False, self.svg.docks[3][0],
+ self.svg.docks[3][1], ']']]
+
+ def _make_flow_style_boolean(self, svg):
+ self.svg.expand(self.dx + self.ex, self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_boolean(True)
+ self._make_block_graphics(svg, self.svg.basic_flow)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['bool', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1], '['],
+ ['flow', False, self.svg.docks[3][0],
+ self.svg.docks[3][1], ']']]
+
+ def _make_flow_style_else(self, svg):
+ self.svg.expand(self.dx + self.ex, self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_else(True)
+ self.svg.set_boolean(True)
+ self._make_block_graphics(svg, self.svg.basic_flow)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['bool', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[3][0],
+ self.svg.docks[3][1], '['],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1], ']['],
+ ['flow', False, self.svg.docks[4][0],
+ self.svg.docks[4][1], ']']]
+
+ def _make_collapsible_style_top(self, svg, arm=True, label=True):
+ self.svg.expand(self.dx + self.ex, self.ey)
+ self.svg.set_arm(arm)
+ self.svg.set_show(not arm)
+ self._make_block_graphics(svg, self.svg.sandwich_top, label)
+ if label:
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]]]
+ else:
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_collapsible_style_bottom(self, svg):
+ self.svg.expand(self.dx + self.ex, self.ey)
+ self._make_block_graphics(svg, self.svg.sandwich_bottom)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]], ['flow', False,
+ self.svg.docks[1][0], self.svg.docks[1][1]]]
+
+ def _make_invisible_style(self, svg):
+ self._make_block_graphics(svg, self.svg.invisible)
+ # force dock positions to be the same
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]], ['flow', False,
+ self.svg.docks[0][0], self.svg.docks[0][1]]]
+
+ # Depreciated block styles
+
+ def _make_portfolio_style_2x2(self, svg):
+ self.svg.expand(30 + self.dx + self.ex, 10 + self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_innie([True, True, False, True])
+ self._make_block_graphics(svg, self.svg.portfolio)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['string', False, self.svg.docks[6][0],
+ self.svg.docks[6][1]],
+ ['media', False, self.svg.docks[5][0],
+ self.svg.docks[5][1]],
+ ['media', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['media', False, self.svg.docks[4][0],
+ self.svg.docks[4][1]],
+ ['media', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['flow', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]]]
+
+ def _make_portfolio_style_2x1(self, svg):
+ self.svg.expand(30 + self.dx + self.ex, 10 + self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_innie([True, True])
+ self._make_block_graphics(svg, self.svg.portfolio)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['string', False, self.svg.docks[4][0],
+ self.svg.docks[4][1]],
+ ['media', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]],
+ ['media', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]]]
+
+ def _make_portfolio_style_1x2(self, svg):
+ self.svg.expand(30 + self.dx + self.ex, 15 + self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_innie([True, True, False, True])
+ self.svg.set_draw_innies(False)
+ self._make_block_graphics(svg, self.svg.portfolio)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['string', False, self.svg.docks[4][0],
+ self.svg.docks[4][1]],
+ ['media', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]],
+ ['media', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_portfolio_style_1x1(self, svg):
+ self.svg.expand(30 + self.dx + self.ex, 15 + self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_innie([True, True])
+ self.svg.set_draw_innies(False)
+ self._make_block_graphics(svg, self.svg.portfolio)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['string', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]],
+ ['media', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_block_graphics(self, svg, function, arg=None):
+ self._set_colors(svg)
+ self.svg.set_gradient(True, GRADIENT_COLOR)
+ if arg is None:
+ self.shapes[0] = svg_str_to_pixbuf(function())
+ else:
+ self.shapes[0] = svg_str_to_pixbuf(function(arg))
+ self.width = self.svg.get_width()
+ self.height = self.svg.get_height()
+ self.svg.set_gradient(False)
+ if arg is None:
+ self.shapes[1] = svg_str_to_pixbuf(function())
+ else:
+ self.shapes[1] = svg_str_to_pixbuf(function(arg))
diff --git a/TurtleArt/tacanvas.py b/TurtleArt/tacanvas.py
new file mode 100644
index 0000000..246fe35
--- /dev/null
+++ b/TurtleArt/tacanvas.py
@@ -0,0 +1,777 @@
+#Copyright (c) 2007-8, Playful Invention Company.
+#Copyright (c) 2008-11, Walter Bender
+#Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+
+#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.
+
+import gtk
+from math import sin, cos, atan, pi, sqrt
+import os
+import pango
+import cairo
+import pangocairo
+import base64
+from gettext import gettext as _
+
+from sprites import Sprite
+from tasprite_factory import SVG
+from tautils import image_to_base64, get_path, data_to_string, round_int, \
+ debug_output
+from taconstants import BLACK, WHITE
+
+
+def wrap100(n):
+ ''' A variant on mod... 101 -> 99; 199 -> 1 '''
+ n = int(n)
+ n %= 200
+ if n > 99:
+ n = 199 - n
+ return n
+
+
+def calc_shade(c, s, invert=False):
+ ''' Convert a color to the current shade (lightness/darkness). '''
+ # Assumes 16 bit input values
+ if invert:
+ if s < 0:
+ return int(c / (1 + s * 0.8))
+ return int((c - 65536 * s * 0.9) / (1 - (s * 0.9)))
+ else:
+ if s < 0:
+ return int(c * (1 + s * 0.8))
+ return int(c + (65536 - c) * s * 0.9)
+
+
+def calc_gray(c, g, invert=False):
+ ''' Gray is a psuedo saturation calculation. '''
+ # Assumes 16 bit input values
+ if g == 100:
+ return c
+ if invert:
+ if g == 0:
+ return c
+ else:
+ return int(((c * 100) - (32768 * (100 - g))) / g)
+ else:
+ return int(((c * g) + (32768 * (100 - g))) / 100)
+
+
+colors = {}
+DEGTOR = pi / 180.
+RTODEG = 180. / pi
+
+COLOR_TABLE = (
+ 0xFF0000, 0xFF0D00, 0xFF1A00, 0xFF2600, 0xFF3300,
+ 0xFF4000, 0xFF4D00, 0xFF5900, 0xFF6600, 0xFF7300,
+ 0xFF8000, 0xFF8C00, 0xFF9900, 0xFFA600, 0xFFB300,
+ 0xFFBF00, 0xFFCC00, 0xFFD900, 0xFFE600, 0xFFF200,
+ 0xFFFF00, 0xE6FF00, 0xCCFF00, 0xB3FF00, 0x99FF00,
+ 0x80FF00, 0x66FF00, 0x4DFF00, 0x33FF00, 0x1AFF00,
+ 0x00FF00, 0x00FF0D, 0x00FF1A, 0x00FF26, 0x00FF33,
+ 0x00FF40, 0x00FF4D, 0x00FF59, 0x00FF66, 0x00FF73,
+ 0x00FF80, 0x00FF8C, 0x00FF99, 0x00FFA6, 0x00FFB3,
+ 0x00FFBF, 0x00FFCC, 0x00FFD9, 0x00FFE6, 0x00FFF2,
+ 0x00FFFF, 0x00F2FF, 0x00E6FF, 0x00D9FF, 0x00CCFF,
+ 0x00BFFF, 0x00B3FF, 0x00A6FF, 0x0099FF, 0x008CFF,
+ 0x0080FF, 0x0073FF, 0x0066FF, 0x0059FF, 0x004DFF,
+ 0x0040FF, 0x0033FF, 0x0026FF, 0x001AFF, 0x000DFF,
+ 0x0000FF, 0x0D00FF, 0x1A00FF, 0x2600FF, 0x3300FF,
+ 0x4000FF, 0x4D00FF, 0x5900FF, 0x6600FF, 0x7300FF,
+ 0x8000FF, 0x8C00FF, 0x9900FF, 0xA600FF, 0xB300FF,
+ 0xBF00FF, 0xCC00FF, 0xD900FF, 0xE600FF, 0xF200FF,
+ 0xFF00FF, 0xFF00E6, 0xFF00CC, 0xFF00B3, 0xFF0099,
+ 0xFF0080, 0xFF0066, 0xFF004D, 0xFF0033, 0xFF001A)
+
+
+class TurtleGraphics:
+ ''' A class for the Turtle graphics canvas '''
+
+ def __init__(self, tw, width, height):
+ ''' Create a sprite to hold the canvas. '''
+ self.tw = tw
+ self.width = width
+ self.height = height
+
+ # Build a cairo.Context from a cairo.XlibSurface
+ self.canvas = cairo.Context(self.tw.turtle_canvas)
+ cr = gtk.gdk.CairoContext(self.canvas)
+ cr.set_line_cap(1) # Set the line cap to be round
+ self.cr_svg = None # Surface used for saving to SVG
+ self.cx = 0
+ self.cy = 0
+ self.fgrgb = [255, 0, 0]
+ self.bgrgb = [255, 255, 255]
+ self.textsize = 48 # deprecated
+ self.shade = 0
+ self.pendown = False
+ self.xcor = 0
+ self.ycor = 0
+ self.heading = 0
+ self.pensize = 5
+ self.color = 0
+ self.gray = 100
+ self.fill = False
+ self.poly_points = []
+
+ def setup_svg_surface(self):
+ ''' Set up a surface for saving to SVG '''
+ if self.tw.running_sugar:
+ svg_surface = cairo.SVGSurface(
+ os.path.join(get_path(self.tw.activity, 'instance'),
+ 'output.svg'), self.width, self.height)
+ else:
+ svg_surface = cairo.SVGSurface(
+ os.path.join(os.getcwd(), 'output.svg'),
+ self.width, self.height)
+ self.cr_svg = cairo.Context(svg_surface)
+ self.cr_svg.set_line_cap(1) # Set the line cap to be round
+
+ def start_fill(self):
+ ''' Start accumulating points of a polygon to fill. '''
+ self.fill = True
+ self.poly_points = []
+
+ def stop_fill(self):
+ ''' Fill the polygon. '''
+ self.fill = False
+ if len(self.poly_points) == 0:
+ return
+ self.fill_polygon(self.poly_points)
+ if self.tw.sharing():
+ shared_poly_points = []
+ for p in self.poly_points:
+ shared_poly_points.append((self.screen_to_turtle_coordinates(
+ p[0], p[1])))
+ event = 'F|%s' % (data_to_string([self._get_my_nick(),
+ shared_poly_points]))
+ self.tw.send_event(event)
+ self.poly_points = []
+
+ def fill_polygon(self, poly_points):
+ ''' Draw the polygon... '''
+ def _fill_polygon(cr, poly_points):
+ cr.new_path()
+ for i, p in enumerate(poly_points):
+ if p[0] == 'move':
+ cr.move_to(p[1], p[2])
+ elif p[0] == 'rarc':
+ cr.arc(p[1], p[2], p[3], p[4], p[5])
+ elif p[0] == 'larc':
+ cr.arc_negative(p[1], p[2], p[3], p[4], p[5])
+ else: # line
+ cr.line_to(p[1], p[2])
+ cr.close_path()
+ cr.fill()
+
+ _fill_polygon(self.canvas, poly_points)
+ self.inval()
+ if self.cr_svg is not None:
+ _fill_polygon(self.cr_svg, poly_points)
+
+ def clearscreen(self, share=True):
+ '''Clear the canvas and reset most graphics attributes to defaults.'''
+
+ def _clearscreen(cr):
+ cr.move_to(0, 0)
+ self.bgrgb = [255, 255, 255]
+ cr.set_source_rgb(self.bgrgb[0] / 255.,
+ self.bgrgb[1] / 255.,
+ self.bgrgb[2] / 255.)
+ cr.rectangle(0, 0, self.width * 2, self.height * 2)
+ cr.fill()
+
+ _clearscreen(self.canvas)
+ self.inval()
+ if self.cr_svg is not None:
+ _clearscreen(self.cr_svg)
+
+ self.setpensize(5, share)
+ self.setgray(100, share)
+ self.setcolor(0, share)
+ self.setshade(50, share)
+ self.fill = False
+ self.poly_points = []
+ for turtle_key in iter(self.tw.turtles.dict):
+ # Don't reset remote turtles
+ if not self.tw.remote_turtle(turtle_key):
+ self.set_turtle(turtle_key)
+ self.tw.active_turtle.set_color(0)
+ self.tw.active_turtle.set_shade(50)
+ self.tw.active_turtle.set_gray(100)
+ self.tw.active_turtle.set_pen_size(5)
+ self.tw.active_turtle.reset_shapes()
+ self.seth(0, share)
+ self.setpen(False, share)
+ self.setxy(0, 0, share)
+ self.setpen(True, share)
+ self.tw.active_turtle.hide()
+ self.set_turtle(self.tw.default_turtle_name)
+
+ def forward(self, n, share=True):
+ ''' Move the turtle forward.'''
+ nn = n * self.tw.coord_scale
+ self.canvas.set_source_rgb(self.fgrgb[0] / 255., self.fgrgb[1] / 255.,
+ self.fgrgb[2] / 255.)
+ if self.cr_svg is not None:
+ debug_output('in forward', True)
+ self.cr_svg.set_source_rgb(self.fgrgb[0] / 255.,
+ self.fgrgb[1] / 255., self.fgrgb[2] / 255.)
+ oldx, oldy = self.xcor, self.ycor
+ try:
+ self.xcor += nn * sin(self.heading * DEGTOR)
+ self.ycor += nn * cos(self.heading * DEGTOR)
+ except TypeError, ValueError:
+ debug_output('bad value sent to %s' % (__name__),
+ self.tw.running_sugar)
+ return
+ if self.pendown:
+ self.draw_line(oldx, oldy, self.xcor, self.ycor)
+
+ self.move_turtle()
+
+ if self.tw.sharing() and share:
+ event = 'f|%s' % (data_to_string([self._get_my_nick(), int(n)]))
+ self.tw.send_event(event)
+ self.inval()
+
+ def seth(self, n, share=True):
+ ''' Set the turtle heading. '''
+ try:
+ self.heading = n
+ except TypeError, ValueError:
+ debug_output('bad value sent to %s' % (__name__),
+ self.tw.running_sugar)
+ return
+ self.heading %= 360
+ self.turn_turtle()
+ if self.tw.sharing() and share:
+ event = 'r|%s' % (data_to_string([self._get_my_nick(),
+ round_int(self.heading)]))
+ self.tw.send_event(event)
+
+ def right(self, n, share=True):
+ ''' Rotate turtle clockwise '''
+ try:
+ self.heading += n
+ except TypeError, ValueError:
+ debug_output('bad value sent to %s' % (__name__),
+ self.tw.running_sugar)
+ return
+ self.heading %= 360
+ self.turn_turtle()
+ if self.tw.sharing() and share:
+ event = 'r|%s' % (data_to_string([self._get_my_nick(),
+ round_int(self.heading)]))
+ self.tw.send_event(event)
+
+ def arc(self, a, r, share=True):
+ ''' Draw an arc '''
+ self.canvas.set_source_rgb(self.fgrgb[0] / 255., self.fgrgb[1] / 255.,
+ self.fgrgb[2] / 255.)
+ if self.cr_svg is not None:
+ self.cr_svg.set_source_rgb(self.fgrgb[0] / 255.,
+ self.fgrgb[1] / 255., self.fgrgb[2] / 255.)
+ try:
+ if a < 0:
+ self.larc(-a, r)
+ else:
+ self.rarc(a, r)
+ except TypeError, ValueError:
+ debug_output('bad value sent to %s' % (__name__),
+ self.tw.running_sugar)
+ return
+ self.move_turtle()
+ if self.tw.sharing() and share:
+ event = 'a|%s' % (data_to_string([self._get_my_nick(),
+ [round_int(a), round_int(r)]]))
+ self.tw.send_event(event)
+
+ def rarc(self, a, r):
+ ''' draw a clockwise arc '''
+ r *= self.tw.coord_scale
+ if r < 0:
+ r = -r
+ a = -a
+ s = 0
+ else:
+ s = 1
+ oldx, oldy = self.xcor, self.ycor
+ cx = self.xcor + r * cos(self.heading * DEGTOR)
+ cy = self.ycor - r * sin(self.heading * DEGTOR)
+ if self.pendown:
+ x, y = self.turtle_to_screen_coordinates(cx, cy)
+
+ def _rarc(cr, x, y, r, a, h):
+ cr.arc(x, y, r, (h - 180) * DEGTOR, (h - 180 + a) * DEGTOR)
+ cr.stroke()
+
+ _rarc(self.canvas, x, y, r, a, self.heading)
+ self.inval()
+ if self.cr_svg is not None:
+ _rarc(self.cr_svg, x, y, r, a, self.heading)
+
+ if self.fill:
+ if self.poly_points == []:
+ self.poly_points.append(('move', x, y))
+ self.poly_points.append(('rarc', x, y, r,
+ (self.heading - 180) * DEGTOR,
+ (self.heading - 180 + a) * DEGTOR))
+
+ self.right(a, False)
+ self.xcor = cx - r * cos(self.heading * DEGTOR)
+ self.ycor = cy + r * sin(self.heading * DEGTOR)
+
+ def larc(self, a, r):
+ ''' draw a counter-clockwise arc '''
+ r *= self.tw.coord_scale
+ if r < 0:
+ r = -r
+ a = -a
+ s = 1
+ else:
+ s = 0
+ oldx, oldy = self.xcor, self.ycor
+ cx = self.xcor - r * cos(self.heading * DEGTOR)
+ cy = self.ycor + r * sin(self.heading * DEGTOR)
+ if self.pendown:
+ x, y = self.turtle_to_screen_coordinates(cx, cy)
+
+ def _larc(cr, x, y, r, a, h):
+ cr.arc_negative(x, y, r, h * DEGTOR, (h - a) * DEGTOR)
+ cr.stroke()
+
+ _larc(self.canvas, x, y, r, a, self.heading)
+ self.inval()
+ if self.cr_svg is not None:
+ _larc(self.cr_svg, x, y, r, a, self.heading)
+
+ if self.fill:
+ if self.poly_points == []:
+ self.poly_points.append(('move', x, y))
+ self.poly_points.append(('larc', x, y, r,
+ (self.heading) * DEGTOR,
+ (self.heading - a) * DEGTOR))
+
+ self.right(-a, False)
+ self.xcor = cx + r * cos(self.heading * DEGTOR)
+ self.ycor = cy - r * sin(self.heading * DEGTOR)
+
+ def setxy(self, x, y, share=True, pendown=True):
+ ''' Move turtle to position x,y '''
+ oldx, oldy = self.xcor, self.ycor
+ x *= self.tw.coord_scale
+ y *= self.tw.coord_scale
+ try:
+ self.xcor, self.ycor = x, y
+ except TypeError, ValueError:
+ debug_output('bad value sent to %s' % (__name__),
+ self.tw.running_sugar)
+ return
+
+ if self.pendown and pendown:
+ self.canvas.set_source_rgb(self.fgrgb[0] / 255.,
+ self.fgrgb[1] / 255.,
+ self.fgrgb[2] / 255.)
+ if self.cr_svg is not None:
+ self.cr_svg.set_source_rgb(self.fgrgb[0] / 255.,
+ self.fgrgb[1] / 255.,
+ self.fgrgb[2] / 255.)
+ self.draw_line(oldx, oldy, self.xcor, self.ycor)
+ self.inval()
+ self.move_turtle()
+
+ if self.tw.sharing() and share:
+ event = 'x|%s' % (data_to_string([self._get_my_nick(),
+ [round_int(x), round_int(y)]]))
+ self.tw.send_event(event)
+
+ def setpensize(self, ps, share=True):
+ ''' Set the pen size '''
+ try:
+ if ps < 0:
+ ps = 0
+ self.pensize = ps
+ except TypeError, ValueError:
+ debug_output('bad value sent to %s' % (__name__),
+ self.tw.running_sugar)
+ return
+ self.tw.active_turtle.set_pen_size(ps)
+ self.canvas.set_line_width(ps)
+ if self.cr_svg is not None:
+ self.cr_svg.set_line_width(ps)
+ if self.tw.sharing() and share:
+ event = 'w|%s' % (data_to_string([self._get_my_nick(),
+ round_int(ps)]))
+ self.tw.send_event(event)
+
+ def setcolor(self, c, share=True):
+ ''' Set the pen color '''
+ try:
+ self.color = c
+ except TypeError, ValueError:
+ debug_output('bad value sent to %s' % (__name__),
+ self.tw.running_sugar)
+ return
+ self.tw.active_turtle.set_color(c)
+ self.set_fgcolor()
+ if self.tw.sharing() and share:
+ event = 'c|%s' % (data_to_string([self._get_my_nick(),
+ round_int(c)]))
+ self.tw.send_event(event)
+
+ def setgray(self, g, share=True):
+ ''' Set the gray level '''
+ try:
+ self.gray = g
+ except TypeError, ValueError:
+ debug_output('bad value sent to %s' % (__name__),
+ self.tw.running_sugar)
+ return
+ if self.gray < 0:
+ self.gray = 0
+ if self.gray > 100:
+ self.gray = 100
+ self.set_fgcolor()
+ self.tw.active_turtle.set_gray(self.gray)
+ if self.tw.sharing() and share:
+ event = 'g|%s' % (data_to_string([self._get_my_nick(),
+ round_int(self.gray)]))
+ self.tw.send_event(event)
+
+ def set_textcolor(self):
+ ''' Deprecated: Set the text color to foreground color. '''
+ return
+
+ def settextcolor(self, c): # deprecated
+ ''' Set the text color '''
+ return
+
+ def settextsize(self, c): # deprecated
+ ''' Set the text size '''
+ try:
+ self.tw.textsize = c
+ except TypeError, ValueError:
+ debug_output('bad value sent to %s' % (__name__),
+ self.tw.running_sugar)
+
+ def setshade(self, s, share=True):
+ ''' Set the color shade '''
+ try:
+ self.shade = s
+ except TypeError, ValueError:
+ debug_output('bad value sent to %s' % (__name__),
+ self.tw.running_sugar)
+ return
+ self.tw.active_turtle.set_shade(s)
+ self.set_fgcolor()
+ if self.tw.sharing() and share:
+ event = 's|%s' % (data_to_string([self._get_my_nick(),
+ round_int(s)]))
+ self.tw.send_event(event)
+
+ def fillscreen(self, c, s):
+ ''' Fill screen with color/shade and reset to defaults '''
+ oldc, olds = self.color, self.shade
+ self.setcolor(c, False)
+ self.setshade(s, False)
+ self.bgrgb = self.fgrgb[:]
+
+ def _fillscreen(cr, rgb, w, h):
+ cr.set_source_rgb(rgb[0] / 255., rgb[1] / 255., rgb[2] / 255.)
+ cr.rectangle(0, 0, w * 2, h * 2)
+ cr.fill()
+
+ _fillscreen(self.canvas, self.fgrgb, self.width, self.height)
+ self.inval()
+ if self.cr_svg is not None:
+ _fillscreen(self.cr_svg, self.fgrgb, self.width, self.height)
+ self.setcolor(oldc, False)
+ self.setshade(olds, False)
+ self.fill = False
+ self.poly_points = []
+
+ def set_fgcolor(self):
+ ''' Set the foreground color '''
+ if self.color == WHITE or self.shade == WHITE:
+ r = 0xFF00
+ g = 0xFF00
+ b = 0xFF00
+ elif self.color == BLACK or self.shade == BLACK:
+ r = 0x0000
+ g = 0x0000
+ b = 0x0000
+ else:
+ sh = (wrap100(self.shade) - 50) / 50.0
+ rgb = COLOR_TABLE[wrap100(self.color)]
+ r = (rgb >> 8) & 0xff00
+ r = calc_gray(r, self.gray)
+ r = calc_shade(r, sh)
+ g = rgb & 0xff00
+ g = calc_gray(g, self.gray)
+ g = calc_shade(g, sh)
+ b = (rgb << 8) & 0xff00
+ b = calc_gray(b, self.gray)
+ b = calc_shade(b, sh)
+ self.fgrgb = [r >> 8, g >> 8, b >> 8]
+
+ def setpen(self, bool, share=True):
+ ''' Lower or raise the pen '''
+ self.pendown = bool
+ self.tw.active_turtle.set_pen_state(bool)
+ if self.tw.sharing() and share:
+ event = 'p|%s' % (data_to_string([self._get_my_nick(), bool]))
+ self.tw.send_event(event)
+
+ def draw_surface(self, surface, x, y, w, h):
+ ''' Draw a surface '''
+
+ def _draw_surface(cr, surface, x, y, w, h):
+ cc = gtk.gdk.CairoContext(cr)
+ cc.set_source_surface(surface, x, y)
+ cc.rectangle(x, y, w, h)
+ cc.fill()
+
+ _draw_surface(self.canvas, surface, x, y, w, h)
+ self.inval()
+ if self.cr_svg is not None:
+ _draw_surface(self.cr_svg, surface, x, y, w, h)
+
+ def draw_pixbuf(self, pixbuf, a, b, x, y, w, h, path, share=True):
+ ''' Draw a pixbuf '''
+
+ def _draw_pixbuf(cr, pixbuf, a, b, x, y, w, h, heading):
+ # Build a gtk.gdk.CairoContext from a cairo.Context to access
+ # the set_source_pixbuf attribute.
+ cc = gtk.gdk.CairoContext(cr)
+ cc.save()
+ # center the rotation on the center of the image
+ cc.translate(x + w / 2., y + h / 2.)
+ cc.rotate(heading * DEGTOR)
+ cc.translate(-x - w / 2., -y - h / 2.)
+ cc.set_source_pixbuf(pixbuf, x, y)
+ cc.rectangle(x, y, w, h)
+ cc.fill()
+ cc.restore()
+
+ _draw_pixbuf(self.canvas, pixbuf, a, b, x, y, w, h, self.heading)
+ self.inval()
+ if self.cr_svg is not None:
+ _draw_pixbuf(self.cr_svg, pixbuf, a, b, x, y, w, h, self.heading)
+ if self.tw.sharing() and share:
+ if self.tw.running_sugar:
+ tmp_path = get_path(self.tw.activity, 'instance')
+ else:
+ tmp_path = '/tmp'
+ tmp_file = os.path.join(get_path(self.tw.activity, 'instance'),
+ 'tmpfile.png')
+ pixbuf.save(tmp_file, 'png', {'quality': '100'})
+ data = image_to_base64(tmp_file, tmp_path)
+ height = pixbuf.get_height()
+ width = pixbuf.get_width()
+ x, y = self.screen_to_turtle_coordinates(x, y)
+ event = 'P|%s' % (data_to_string([self._get_my_nick(),
+ [round_int(a), round_int(b),
+ round_int(x), round_int(y),
+ round_int(w), round_int(h),
+ round_int(width),
+ round_int(height),
+ data]]))
+ self.tw.send_event(event)
+ os.remove(tmp_file)
+
+ def draw_text(self, label, x, y, size, w, share=True):
+ ''' Draw text '''
+ w *= self.tw.coord_scale
+
+ def _draw_text(cr, label, x, y, size, w, scale, heading, rgb):
+ cc = pangocairo.CairoContext(cr)
+ pl = cc.create_layout()
+ fd = pango.FontDescription('Sans')
+ fd.set_size(int(size * scale) * pango.SCALE)
+ pl.set_font_description(fd)
+ if type(label) == str or type(label) == unicode:
+ pl.set_text(label.replace('\0', ' '))
+ elif type(label) == float or type(label) == int:
+ pl.set_text(str(label))
+ else:
+ pl.set_text(str(label))
+ pl.set_width(int(w) * pango.SCALE)
+ cc.save()
+ cc.translate(x, y)
+ cc.rotate(heading * DEGTOR)
+ cr.set_source_rgb(rgb[0] / 255., rgb[1] / 255., rgb[2] / 255.)
+ cc.update_layout(pl)
+ cc.show_layout(pl)
+ cc.restore()
+
+ _draw_text(self.canvas, label, x, y, size, w, self.tw.coord_scale,
+ self.heading, self.fgrgb)
+ self.inval()
+ if self.cr_svg is not None: # and self.pendown:
+ _draw_text(self.cr_svg, label, x, y, size, w, self.tw.coord_scale,
+ self.heading, self.fgrgb)
+ if self.tw.sharing() and share:
+ event = 'W|%s' % (data_to_string([self._get_my_nick(),
+ [label, round_int(x),
+ round_int(y), round_int(size),
+ round_int(w)]]))
+ self.tw.send_event(event)
+
+ def turtle_to_screen_coordinates(self, x, y):
+ ''' The origin of turtle coordinates is the center of the screen '''
+ return self.width / 2. + x, self.invert_y_coordinate(y)
+
+ def screen_to_turtle_coordinates(self, x, y):
+ ''' The origin of the screen coordinates is the upper left corner '''
+ return x - self.width / 2., self.invert_y_coordinate(y)
+
+ def invert_y_coordinate(self, y):
+ ''' Positive y goes up in turtle coordinates, down in sceeen
+ coordinates '''
+ return self.height / 2. - y
+
+ def draw_line(self, x1, y1, x2, y2):
+ ''' Draw a line '''
+ x1, y1 = self.turtle_to_screen_coordinates(x1, y1)
+ x2, y2 = self.turtle_to_screen_coordinates(x2, y2)
+
+ def _draw_line(cr, x1, y1, x2, y2):
+ cr.move_to(x1, y1)
+ cr.line_to(x2, y2)
+ cr.stroke()
+
+ _draw_line(self.canvas, x1, y1, x2, y2)
+ if self.cr_svg is not None:
+ _draw_line(self.cr_svg, x1, y1, x2, y2)
+ if self.fill:
+ if self.poly_points == []:
+ self.poly_points.append(('move', x1, y1))
+ self.poly_points.append(('line', x2, y2))
+
+ def turn_turtle(self):
+ ''' Change the orientation of the turtle '''
+ self.tw.active_turtle.set_heading(self.heading)
+
+ def move_turtle(self):
+ ''' Move the turtle '''
+ x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor)
+ if self.tw.interactive_mode:
+ self.tw.active_turtle.move(
+ (int(self.cx + x - self.tw.active_turtle.spr.rect.width / 2.),
+ int(self.cy + y - self.tw.active_turtle.spr.rect.height / 2.))
+ )
+ else:
+ self.tw.active_turtle.move((int(self.cx + x), int(self.cy + y)))
+
+ def get_color_index(self, r, g, b, a=0):
+ ''' Find the closest palette entry to the rgb triplet '''
+ if self.shade != 50 or self.gray != 100:
+ r <<= 8
+ g <<= 8
+ b <<= 8
+ if self.shade != 50:
+ sh = (wrap100(self.shade) - 50) / 50.
+ r = calc_shade(r, sh, True)
+ g = calc_shade(g, sh, True)
+ b = calc_shade(b, sh, True)
+ if self.gray != 100:
+ r = calc_gray(r, self.gray, True)
+ g = calc_gray(g, self.gray, True)
+ b = calc_gray(b, self.gray, True)
+ r >>= 8
+ g >>= 8
+ b >>= 8
+ min_distance = 1000000
+ closest_color = -1
+ for i, c in enumerate(COLOR_TABLE):
+ cr = int((c & 0xff0000) >> 16)
+ cg = int((c & 0x00ff00) >> 8)
+ cb = int((c & 0x0000ff))
+ distance_squared = \
+ ((cr - r) ** 2) + ((cg - g) ** 2) + ((cb - b) ** 2)
+ if distance_squared == 0:
+ return i
+ if distance_squared < min_distance:
+ min_distance = distance_squared
+ closest_color = i
+ return closest_color
+
+ def get_pixel(self):
+ ''' Read the pixel at x, y '''
+ if self.tw.interactive_mode:
+ x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor)
+ x = int(x)
+ y = int(y)
+ w = self.tw.turtle_canvas.get_width()
+ h = self.tw.turtle_canvas.get_height()
+ if x < 0 or x > (w - 1) or y < 0 or y > (h - 1):
+ return(-1, -1, -1, -1)
+ # create a new 1x1 cairo surface
+ cs = cairo.ImageSurface(cairo.FORMAT_RGB24, 1, 1)
+ cr = cairo.Context(cs)
+ cr.set_source_surface(self.tw.turtle_canvas, -x, -y)
+ cr.rectangle(0, 0, 1, 1)
+ cr.set_operator(cairo.OPERATOR_SOURCE)
+ cr.fill()
+ cs.flush() # ensure all writing is done
+ pixels = cs.get_data() # Read the pixel
+ return (ord(pixels[2]), ord(pixels[1]), ord(pixels[0]), 0)
+ else:
+ return(-1, -1, -1, -1)
+
+ def set_turtle(self, k, colors=None):
+ ''' Select the current turtle and associated pen status '''
+ if k not in self.tw.turtles.dict:
+ # if it is a new turtle, start it in the center of the screen
+ self.tw.active_turtle = self.tw.turtles.get_turtle(k, True, colors)
+ self.seth(0, False)
+ self.setxy(0, 0, False, pendown=False)
+ self.tw.active_turtle.set_pen_state(True)
+ elif colors is not None:
+ self.tw.active_turtle = self.tw.turtles.get_turtle(k, False)
+ self.tw.active_turtle.set_turtle_colors(colors)
+ else:
+ self.tw.active_turtle = self.tw.turtles.get_turtle(k, False)
+ self.tw.active_turtle.show()
+ tx, ty = self.tw.active_turtle.get_xy()
+ self.xcor, self.ycor = self.screen_to_turtle_coordinates(tx, ty)
+ if self.tw.interactive_mode:
+ self.xcor += self.tw.active_turtle.spr.rect.width / 2.
+ self.ycor -= self.tw.active_turtle.spr.rect.height / 2.
+ self.heading = self.tw.active_turtle.get_heading()
+ self.setcolor(self.tw.active_turtle.get_color(), False)
+ self.setgray(self.tw.active_turtle.get_gray(), False)
+ self.setshade(self.tw.active_turtle.get_shade(), False)
+ self.setpensize(self.tw.active_turtle.get_pen_size(), False)
+ self.setpen(self.tw.active_turtle.get_pen_state(), False)
+
+ def svg_close(self):
+ ''' Close current SVG graphic '''
+ self.cr_svg.show_page()
+
+ def svg_reset(self):
+ ''' Reset svg flags '''
+ self.cr_svg = None
+
+ def _get_my_nick(self):
+ return self.tw.nick
+
+ def inval(self):
+ ''' Invalidate a region for gtk '''
+ self.tw.inval_all()
diff --git a/TurtleArt/tacollaboration.py b/TurtleArt/tacollaboration.py
new file mode 100644
index 0000000..8316c7a
--- /dev/null
+++ b/TurtleArt/tacollaboration.py
@@ -0,0 +1,419 @@
+#Copyright (c) 2011, Walter Bender
+#Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+
+#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.
+
+from dbus.service import signal
+from dbus.gobject_service import ExportedGObject
+import telepathy
+
+import gtk
+import base64
+
+from TurtleArt.tautils import data_to_string, data_from_string, get_path, \
+ base64_to_image, debug_output, error_output
+from TurtleArt.taconstants import DEFAULT_TURTLE_COLORS
+
+try:
+ from sugar import profile
+ from sugar.presence import presenceservice
+ from sugar.presence.tubeconn import TubeConnection
+except:
+ profile = None
+ from collaboration import presenceservice
+ from collaboration.tubeconn import TubeConnection
+
+SERVICE = 'org.laptop.TurtleArtActivity'
+IFACE = SERVICE
+PATH = '/org/laptop/TurtleArtActivity'
+
+
+class Collaboration():
+ def __init__(self, tw, activity):
+ """ A simplistic sharing model: the sharer is the master """
+ self._tw = tw
+ self._tw.send_event = self.send_event
+ self._tw.remote_turtle_dictionary = {}
+ self._activity = activity
+ self._setup_dispatch_table()
+
+ def setup(self):
+ # TODO: hand off role of master is sharer leaves
+ self.pservice = presenceservice.get_instance()
+ self.initiating = None # sharing (True) or joining (False)
+
+ # Add my buddy object to the list
+ owner = self.pservice.get_owner()
+ self.owner = owner
+ self._tw.buddies.append(self.owner)
+ self._share = ''
+ self._activity.connect('shared', self._shared_cb)
+ self._activity.connect('joined', self._joined_cb)
+
+ def _setup_dispatch_table(self):
+ self._processing_methods = {
+ 't': self._turtle_request,
+ 'T': self._receive_turtle_dict,
+ 'f': self._move_forward,
+ 'a': self._move_in_arc,
+ 'r': self._rotate_turtle,
+ 'x': self._setxy,
+ 'W': self._draw_text,
+ 'c': self._set_pen_color,
+ 'g': self._set_pen_gray_level,
+ 's': self._set_pen_shade,
+ 'w': self._set_pen_width,
+ 'p': self._set_pen_state,
+ 'F': self._fill_polygon,
+ 'P': self._draw_pixbuf
+ }
+
+ def _shared_cb(self, activity):
+ self._shared_activity = self._activity._shared_activity
+ if self._shared_activity is None:
+ debug_output('Failed to share or join activity ... \
+ _shared_activity is null in _shared_cb()',
+ self._tw.running_sugar)
+ return
+
+ self._tw.set_sharing(True)
+
+ self.initiating = True
+ self.waiting_for_turtles = False
+ self._tw.remote_turtle_dictionary = self._get_dictionary()
+
+ debug_output('I am sharing...', self._tw.running_sugar)
+
+ self.conn = self._shared_activity.telepathy_conn
+ self.tubes_chan = self._shared_activity.telepathy_tubes_chan
+ self.text_chan = self._shared_activity.telepathy_text_chan
+
+ self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
+ 'NewTube', self._new_tube_cb)
+
+ debug_output('This is my activity: making a tube...',
+ self._tw.running_sugar)
+
+ id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
+ SERVICE, {})
+
+ def _joined_cb(self, activity):
+ self._shared_activity = self._activity._shared_activity
+ if self._shared_activity is None:
+ debug_output('Failed to share or join activity ... \
+ _shared_activity is null in _shared_cb()',
+ self._tw.running_sugar)
+ return
+
+ self._tw.set_sharing(True)
+
+ self.initiating = False
+ self.conn = self._shared_activity.telepathy_conn
+ self.tubes_chan = self._shared_activity.telepathy_tubes_chan
+ self.text_chan = self._shared_activity.telepathy_text_chan
+
+ # call back for "NewTube" signal
+ self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
+ 'NewTube', self._new_tube_cb)
+
+ debug_output('I am joining an activity: waiting for a tube...',
+ self._tw.running_sugar)
+ self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+ reply_handler=self._list_tubes_reply_cb,
+ error_handler=self._list_tubes_error_cb)
+
+ # Joiner should request current state from sharer.
+ self.waiting_for_turtles = True
+
+ def _list_tubes_reply_cb(self, tubes):
+ for tube_info in tubes:
+ self._new_tube_cb(*tube_info)
+
+ def _list_tubes_error_cb(self, e):
+ error_output('ListTubes() failed: %s' % (e), self._tw.running_sugar)
+
+ def _new_tube_cb(self, id, initiator, type, service, params, state):
+ """ Create a new tube. """
+ debug_output('New tube: ID=%d initator=%d type=%d service=%s \
+ params=%r state=%d' % (id, initiator, type, service,
+ params, state), self._tw.running_sugar)
+
+ if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
+ if state == telepathy.TUBE_STATE_LOCAL_PENDING:
+ self.tubes_chan[ \
+ telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
+
+ tube_conn = TubeConnection(self.conn,
+ self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES], id, \
+ group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
+
+ # We'll use a chat tube to send serialized stacks back and forth.
+ self.chattube = ChatTube(tube_conn, self.initiating, \
+ self.event_received_cb)
+
+ # Now that we have the tube, we can ask for the turtle dictionary.
+ if self.waiting_for_turtles: # A joiner must wait for turtles.
+ debug_output('Sending a request for the turtle dictionary',
+ self._tw.running_sugar)
+ # We need to send our own nick, colors, and turtle position
+ colors = self._get_colors()
+ event = 't|' + data_to_string([self._get_nick(), colors])
+ debug_output(event, self._tw.running_sugar)
+ self.send_event(event)
+
+ def event_received_cb(self, event_message):
+ """
+ Events are sent as a tuple, nick|cmd, where nick is a turle name
+ and cmd is a turtle event. Everyone gets the turtle dictionary from
+ the sharer and watches for 't' events, which indicate that a new
+ turtle has joined.
+ """
+ if len(event_message) == 0:
+ return
+
+ # Save active Turtle
+ save_active_turtle = self._tw.active_turtle
+
+ try:
+ command, payload = event_message.split('|', 2)
+ self._processing_methods[command](payload)
+ except ValueError:
+ debug_output('Could not split event message.',
+ self._tw.running_sugar)
+
+ # Restore active Turtle
+ self._tw.canvas.set_turtle(self._tw.turtles.get_turtle_key(
+ save_active_turtle))
+
+ def send_event(self, entry):
+ """ Send event through the tube. """
+ if hasattr(self, 'chattube') and self.chattube is not None:
+ self.chattube.SendText(entry)
+
+ def _turtle_request(self, payload):
+ ''' incoming turtle from a joiner '''
+ if payload > 0:
+ [nick, colors] = data_from_string(payload)
+ if nick != self._tw.nick: # It is not me.
+ # There may not be a turtle dictionary.
+ if hasattr(self._tw, 'remote_turtle_dictionary'):
+ # Make sure it is not a "rejoin".
+ if not nick in self._tw.remote_turtle_dictionary:
+ # Add new turtle for the joiner.
+ self._tw.canvas.set_turtle(nick, colors)
+ self._tw.label_remote_turtle(nick, colors)
+ self._tw.remote_turtle_dictionary[nick] = colors
+ else:
+ self._tw.remote_turtle_dictionary = self._get_dictionary()
+ # Add new turtle for the joiner.
+ self._tw.canvas.set_turtle(nick, colors)
+ self._tw.label_remote_turtle(nick, colors)
+
+ # Sharer should send the updated remote turtle dictionary to everyone.
+ if self.initiating:
+ if not self._tw.nick in self._tw.remote_turtle_dictionary:
+ self._tw.remote_turtle_dictionary[self._tw.nick] = \
+ self._get_colors()
+ event_payload = data_to_string(self._tw.remote_turtle_dictionary)
+ self.send_event('T|' + event_payload)
+ self.send_my_xy() # And the sender should report her xy position.
+
+ def _receive_turtle_dict(self, payload):
+ ''' Any time there is a new joiner, an updated turtle dictionary is
+ circulated. Everyone must report their turtle positions so that we
+ are in sync. '''
+ if self.waiting_for_turtles:
+ if len(payload) > 0:
+ # Grab the new remote turtles dictionary.
+ remote_turtle_dictionary = data_from_string(payload)
+ # Add see what is new.
+ for nick in remote_turtle_dictionary:
+ if nick == self._tw.nick:
+ debug_output('skipping my nick %s' \
+ % (nick), self._tw.running_sugar)
+ elif nick != self._tw.remote_turtle_dictionary:
+ # Add new the turtle.
+ colors = remote_turtle_dictionary[nick]
+ self._tw.remote_turtle_dictionary[nick] = colors
+ self._tw.canvas.set_turtle(nick, colors)
+ # Label the remote turtle.
+ self._tw.label_remote_turtle(nick, colors)
+ debug_output('adding %s to remote turtle dictionary' \
+ % (nick), self._tw.running_sugar)
+ else:
+ debug_output('%s already in remote turtle dictionary' \
+ % (nick), self._tw.running_sugar)
+ self.waiting_for_turtles = False
+ self.send_my_xy()
+
+ def send_my_xy(self):
+ ''' Set xy location so joiner can sync turtle positions. Should be
+ used to sync positions after turtle drag. '''
+ self._tw.canvas.set_turtle(self._get_nick())
+ if self._tw.canvas.pendown:
+ self.send_event('p|%s' % (data_to_string([self._get_nick(),
+ False])))
+ put_pen_back_down = True
+ else:
+ put_pen_back_down = False
+ self.send_event('x|%s' % (data_to_string([self._get_nick(),
+ [int(self._tw.canvas.xcor), int(self._tw.canvas.ycor)]])))
+ if put_pen_back_down:
+ self.send_event('p|%s' % (data_to_string([self._get_nick(),
+ True])))
+ self.send_event('r|%s' % (data_to_string([self._get_nick(),
+ int(self._tw.canvas.heading)])))
+
+ def _draw_pixbuf(self, payload):
+ if len(payload) > 0:
+ [nick, [a, b, x, y, w, h, width, height, data]] =\
+ data_from_string(payload)
+ if nick != self._tw.nick:
+ if self._tw.running_sugar:
+ tmp_path = get_path(self._tw.activity, 'instance')
+ else:
+ tmp_path = '/tmp'
+ file_name = base64_to_image(data, tmp_path)
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(file_name,
+ width, height)
+ x, y = self._tw.canvas.turtle_to_screen_coordinates(x, y)
+ self._tw.canvas.draw_pixbuf(pixbuf, a, b, x, y, w, h,
+ file_name, False)
+
+ def _move_forward(self, payload):
+ if len(payload) > 0:
+ [nick, x] = data_from_string(payload)
+ if nick != self._tw.nick:
+ self._tw.canvas.set_turtle(nick)
+ self._tw.canvas.forward(x, False)
+
+ def _move_in_arc(self, payload):
+ if len(payload) > 0:
+ [nick, [a, r]] = data_from_string(payload)
+ if nick != self._tw.nick:
+ self._tw.canvas.set_turtle(nick)
+ self._tw.canvas.arc(a, r, False)
+
+ def _rotate_turtle(self, payload):
+ if len(payload) > 0:
+ [nick, h] = data_from_string(payload)
+ if nick != self._tw.nick:
+ self._tw.canvas.set_turtle(nick)
+ self._tw.canvas.seth(h, False)
+
+ def _setxy(self, payload):
+ if len(payload) > 0:
+ [nick, [x, y]] = data_from_string(payload)
+ if nick != self._tw.nick:
+ self._tw.canvas.set_turtle(nick)
+ self._tw.canvas.setxy(x, y, False)
+
+ def _draw_text(self, payload):
+ if len(payload) > 0:
+ [nick, [label, x, y, size, w]] = data_from_string(payload)
+ if nick != self._tw.nick:
+ self._tw.canvas.draw_text(label, x, y, size, w, False)
+
+ def _set_pen_color(self, payload):
+ if len(payload) > 0:
+ [nick, x] = data_from_string(payload)
+ if nick != self._tw.nick:
+ self._tw.canvas.set_turtle(nick)
+ self._tw.canvas.setcolor(x, False)
+
+ def _set_pen_gray_level(self, payload):
+ if len(payload) > 0:
+ [nick, x] = data_from_string(payload)
+ if nick != self._tw.nick:
+ self._tw.canvas.set_turtle(nick)
+ self._tw.canvas.setgray(x, False)
+
+ def _set_pen_shade(self, payload):
+ if len(payload) > 0:
+ [nick, x] = data_from_string(payload)
+ if nick != self._tw.nick:
+ self._tw.canvas.set_turtle(nick)
+ self._tw.canvas.setshade(x, False)
+
+ def _set_pen_width(self, payload):
+ if len(payload) > 0:
+ [nick, x] = data_from_string(payload)
+ if nick != self._tw.nick:
+ self._tw.canvas.set_turtle(nick)
+ self._tw.canvas.setpensize(x, False)
+
+ def _set_pen_state(self, payload):
+ if len(payload) > 0:
+ [nick, x] = data_from_string(payload)
+ if nick != self._tw.nick:
+ self._tw.canvas.set_turtle(nick)
+ self._tw.canvas.setpen(x, False)
+
+ def _fill_polygon(self, payload):
+ # Check to make sure that the poly_point array is passed properly
+ if len(payload) > 0:
+ [nick, poly_points] = data_from_string(payload)
+ shared_poly_points = []
+ for i in range(len(poly_points)):
+ shared_poly_points.append((
+ self._tw.canvas.turtle_to_screen_coordinates(
+ poly_points[i][0], poly_points[i][1])))
+ self._tw.canvas.fill_polygon(shared_poly_points)
+
+ def _get_dictionary(self):
+ return {self._get_nick(): self._get_colors()}
+
+ def _get_nick(self):
+ return self._tw.nick
+
+ def _get_colors(self):
+ colors = None
+ if self._tw.running_sugar:
+ if profile.get_color() is not None:
+ colors = profile.get_color().to_string()
+ else:
+ colors = self._activity.get_colors()
+ if colors is None:
+ colors = '%s,%s' % (DEFAULT_TURTLE_COLORS[0],
+ DEFAULT_TURTLE_COLORS[1])
+ return colors.split(',')
+
+
+class ChatTube(ExportedGObject):
+
+ def __init__(self, tube, is_initiator, stack_received_cb):
+ """Class for setting up tube for sharing."""
+ super(ChatTube, self).__init__(tube, PATH)
+ self.tube = tube
+ self.is_initiator = is_initiator # Are we sharing or joining activity?
+ self.stack_received_cb = stack_received_cb
+ self.stack = ''
+
+ self.tube.add_signal_receiver(self.send_stack_cb, 'SendText', IFACE, \
+ path=PATH, sender_keyword='sender')
+
+ def send_stack_cb(self, text, sender=None):
+ if sender == self.tube.get_unique_name():
+ return
+ self.stack = text
+ self.stack_received_cb(text)
+
+ @signal(dbus_interface=IFACE, signature='s')
+ def SendText(self, text):
+ self.stack = text
diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py
new file mode 100644
index 0000000..7a82a42
--- /dev/null
+++ b/TurtleArt/taconstants.py
@@ -0,0 +1,428 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2010-12 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.
+
+from gettext import gettext as _
+
+#
+# Sprite layers
+#
+
+OVERLAY_LAYER = 400
+TURTLE_LAYER = 500
+BLOCK_LAYER = 600
+CATEGORY_LAYER = 700
+TAB_LAYER = 800
+PROTO_LAYER = 801
+STATUS_LAYER = 900
+TOP_LAYER = 1000
+
+# Special-case some block colors
+BOX_COLORS = {'red': ["#FF0000", "#A00000"],
+ 'orange': ["#FFD000", "#AA8000"],
+ 'yellow': ["#FFFF00", "#A0A000"],
+ 'blue': ["#0000FF", "#000080"],
+ 'cyan': ["#00FFFF", "#00A0A0"],
+ 'green': ["#00FF00", "#008000"],
+ 'purple': ["#FF00FF", "#A000A0"],
+ 'white': ["#FFFFFF", "#A0A0A0"],
+ 'black': ["#000000", "#000000"]}
+
+#
+# Misc. parameters
+#
+PALETTE_HEIGHT = 120
+PALETTE_WIDTH = 175
+SELECTOR_WIDTH = 55
+ICON_SIZE = 55
+GRADIENT_COLOR = "#FFFFFF"
+STANDARD_STROKE_WIDTH = 1.0
+BLOCK_SCALE = [0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0]
+PALETTE_SCALE = 1.5
+DEFAULT_TURTLE = 'Yertle'
+DEFAULT_TURTLE_COLORS = ['#008000', '#00A000']
+PALETTE_COLOR = "#FFD000"
+TOOLBAR_COLOR = "#282828"
+HORIZONTAL_PALETTE = 0
+VERTICAL_PALETTE = 1
+BLACK = -9999
+WHITE = -9998
+HIT_HIDE = 240
+HIT_SHOW = 224
+HIT_RED = "#F00000"
+HIT_GREEN = "#00E000"
+HIDE_WHITE = "#F0F0F0"
+SHOW_WHITE = "#E0E0E0"
+DEFAULT_SCALE = 33
+XO1 = 'xo1'
+XO15 = 'xo1.5'
+XO175 = 'xo1.75'
+XO30 = 'xo3.0'
+UNKNOWN = 'unknown'
+
+CONSTANTS = {'leftpos': None, 'toppos': None, 'rightpos': None,
+ 'bottompos': None, 'width': None, 'height': None, 'red': 0,
+ 'orange': 10, 'yellow': 20, 'green': 40, 'cyan': 50, 'blue': 70,
+ 'purple': 90, 'titlex': None, 'titley': None, 'leftx': None,
+ 'topy': None, 'rightx': None, 'bottomy': None}
+
+#
+# Blocks that are expandable
+#
+EXPANDABLE_STYLE = ['boolean-style', 'compare-porch-style', 'compare-style',
+ 'number-style-porch', 'number-style', 'basic-style-2arg',
+ 'number-style-block', 'box-style-media']
+
+EXPANDABLE = ['vspace', 'hspace', 'identity2']
+
+EXPANDABLE_ARGS = ['list', 'myfunc1arg', 'myfunc2arg', 'myfunc3arg',
+ 'userdefined', 'userdefined2args', 'userdefined3args']
+#
+# Blocks that are 'collapsible'
+#
+COLLAPSIBLE = ['sandwichbottom', 'sandwichcollapsed']
+
+#
+# Deprecated block styles that need dock adjustments
+#
+OLD_DOCK = ['and', 'or', 'plus', 'minus', 'division', 'product', 'remainder']
+
+CONTENT_ARGS = ['show', 'showaligned', 'push', 'storein', 'storeinbox1',
+ 'storeinbox2']
+
+PREFIX_DICTIONARY = {}
+
+#
+# These blocks get a special skin
+#
+BLOCKS_WITH_SKIN = []
+
+PYTHON_SKIN = []
+
+SKIN_PATHS = ['images']
+
+MEDIA_SHAPES = []
+
+NO_IMPORT = []
+
+EXPAND_SKIN = {}
+
+#
+# Status blocks
+#
+OVERLAY_SHAPES = ['Cartesian', 'Cartesian_labeled', 'polar', 'metric']
+
+STATUS_SHAPES = ['status', 'info', 'nostack', 'dupstack', 'noinput',
+ 'emptyheap', 'emptybox', 'nomedia', 'nocode', 'overflowerror',
+ 'negroot', 'syntaxerror', 'nofile', 'nojournal', 'zerodivide',
+ 'notanumber', 'incompatible', 'help', 'print']
+
+#
+# Emulate Sugar toolbar when running from outside of Sugar
+#
+TOOLBAR_SHAPES = ['hideshowoff', 'eraseron', 'run-fastoff',
+ 'run-slowoff', 'debugoff', 'stopiton']
+
+#
+# Legacy names
+#
+OLD_NAMES = {'product': 'product2', 'storeinbox': 'storein', 'minus': 'minus2',
+ 'division': 'division2', 'plus': 'plus2', 'and': 'and2',
+ 'or': 'or2', 'less': 'less2', 'greater': 'greater2',
+ 'equal': 'equal2', 'remainder': 'remainder2',
+ 'identity': 'identity2', 'division': 'division2',
+ 'audiooff': 'audio', 'endfill': 'stopfill',
+ 'descriptionoff': 'description', 'template3': 'templatelist',
+ 'template1': 'template1x1', 'template2': 'template2x1',
+ 'template6': 'template1x2', 'template7': 'template2x2',
+ 'template4': 'template1x1a', 'hres': 'width', 'vres': 'height',
+ 'sandwichtop2': 'sandwichtop', 'image': 'show',
+ 'container': 'indentity2', 'insertimage': 'show'}
+
+#
+# Define the relative size and postion of media objects
+# (w, h, x, y, dx, dy)
+#
+TITLEXY = (0.9375, 0.875)
+
+#
+# Relative placement of portfolio objects (used by deprecated blocks)
+#
+TEMPLATES = {'t1x1': (0.5, 0.5, 0.0625, 0.125, 1.05, 0),
+ 't2z1': (0.5, 0.5, 0.0625, 0.125, 1.05, 1.05),
+ 't1x2': (0.45, 0.45, 0.0625, 0.125, 1.05, 1.05),
+ 't2x2': (0.45, 0.45, 0.0625, 0.125, 1.05, 1.05),
+ 't1x1a': (0.9, 0.9, 0.0625, 0.125, 0, 0),
+ 'bullet': (1, 1, 0.0625, 0.125, 0, 0.1),
+ 'insertimage': (0.333, 0.333)}
+
+#
+# 'dead key' Unicode dictionaries
+#
+
+DEAD_KEYS = ['grave', 'acute', 'circumflex', 'tilde', 'diaeresis', 'abovering']
+DEAD_DICTS = [{'A': 192, 'E': 200, 'I': 204, 'O': 210, 'U': 217, 'a': 224,
+ 'e': 232, 'i': 236, 'o': 242, 'u': 249},
+ {'A': 193, 'E': 201, 'I': 205, 'O': 211, 'U': 218, 'a': 225,
+ 'e': 233, 'i': 237, 'o': 243, 'u': 250},
+ {'A': 194, 'E': 202, 'I': 206, 'O': 212, 'U': 219, 'a': 226,
+ 'e': 234, 'i': 238, 'o': 244, 'u': 251},
+ {'A': 195, 'O': 211, 'N': 209, 'U': 360, 'a': 227, 'o': 245,
+ 'n': 241, 'u': 361},
+ {'A': 196, 'E': 203, 'I': 207, 'O': 211, 'U': 218, 'a': 228,
+ 'e': 235, 'i': 239, 'o': 245, 'u': 252},
+ {'A': 197, 'a': 229}]
+NOISE_KEYS = ['Shift_L', 'Shift_R', 'Control_L', 'Caps_Lock', 'Pause',
+ 'Alt_L', 'Alt_R', 'KP_Enter', 'ISO_Level3_Shift', 'KP_Divide',
+ 'Escape', 'Return', 'KP_Page_Up', 'Up', 'Down', 'Menu',
+ 'Left', 'Right', 'KP_Home', 'KP_End', 'KP_Up', 'Super_L',
+ 'KP_Down', 'KP_Left', 'KP_Right', 'KP_Page_Down', 'Scroll_Lock',
+ 'Page_Down', 'Page_Up']
+WHITE_SPACE = ['space', 'Tab']
+
+CURSOR = '█'
+RETURN = '⏎'
+
+VOICES = {'af': 'afrikaans', 'cy': 'welsh-test', 'el': 'greek',
+ 'es': 'spanish', 'hi': 'hindi-test', 'hy': 'armenian',
+ 'ku': 'kurdish', 'mk': 'macedonian-test', 'pt': 'brazil',
+ 'sk': 'slovak', 'sw': 'swahili', 'bs': 'bosnian',
+ 'da': 'danish', 'en': 'english', 'fi': 'finnish',
+ 'hr': 'croatian', 'id': 'indonesian-test', 'la': 'latin',
+ 'nl': 'dutch-test', 'sq': 'albanian', 'ta': 'tamil',
+ 'vi': 'vietnam-test', 'ca': 'catalan', 'de': 'german',
+ 'eo': 'esperanto', 'fr': 'french', 'hu': 'hungarian',
+ 'is': 'icelandic-test', 'lv': 'latvian', 'no': 'norwegian',
+ 'ro': 'romanian', 'sr': 'serbian', 'zh': 'Mandarin',
+ 'cs': 'czech', 'it': 'italian', 'pl': 'polish',
+ 'ru': 'russian_test', 'sv': 'swedish', 'tr': 'turkish'}
+
+#
+# Macros (groups of blocks)
+#
+MACROS = {
+ 'kbinput':
+ [[0, 'until', 0, 0, [None, 1, 4, None]],
+ [1, 'greater2', 0, 0, [0, 2, 3, None]],
+ [2, 'keyboard', 0, 0, [1, None]],
+ [3, ['number', '0'], 0, 0, [1, None]],
+ [4, 'wait', 0, 0, [0, 5, 6]],
+ [5, ['number', '0.1'], 0, 0, [4, None]],
+ [6, 'kbinput', 0, 0, [4, None]]],
+ 'picturelist':
+ [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [1, 'penup', 0, 0, [0, 2]],
+ [2, 'setxy2', 0, 0, [1, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'pendown', 0, 0, [2, 6]],
+ [6, 'setscale', 0, 0, [5, 7, 8]],
+ [7, ['number', '100'], 0, 0, [6, None]],
+ [8, 'show', 0, 0, [6, 9, 10]],
+ [9, ['string', _('Title')], 0, 0, [8, None]],
+ [10, 'penup', 0, 0, [8, 11]],
+ [11, 'setxy2', 0, 0, [10, 12, 13, 14]],
+ [12, 'leftx', 0, 0, [11, None]],
+ [13, 'topy', 0, 0, [11, None]],
+ [14, 'pendown', 0, 0, [11, 15]],
+ [15, 'setscale', 0, 0, [14, 16, 17]],
+ [16, ['number', '67'], 0, 0, [15, None]],
+ [17, 'list', 0, 0, [15, 18, 19, 20]],
+ [18, ['string', '∙ '], 0, 0, [17, None]],
+ [19, ['string', '∙ '], 0, 0, [17, None]],
+ [20, 'sandwichbottom', 0, 0, [17, None]]],
+ 'picture1x1a':
+ [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [1, 'penup', 0, 0, [0, 2]],
+ [2, 'setxy2', 0, 0, [1, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'pendown', 0, 0, [2, 6]],
+ [6, 'setscale', 0, 0, [5, 7, 8]],
+ [7, ['number', '100'], 0, 0, [6, None]],
+ [8, 'show', 0, 0, [6, 9, 10]],
+ [9, ['string', _('Title')], 0, 0, [8, None]],
+ [10, 'penup', 0, 0, [8, 11]],
+ [11, 'setxy2', 0, 0, [10, 12, 13, 14]],
+ [12, 'leftx', 0, 0, [11, None]],
+ [13, 'topy', 0, 0, [11, None]],
+ [14, 'pendown', 0, 0, [11, 15]],
+ [15, 'setscale', 0, 0, [14, 16, 17]],
+ [16, ['number', '90'], 0, 0, [15, None]],
+ [17, 'showaligned', 0, 0, [15, 18, 19]],
+ [18, 'journal', 0, 0, [17, None]],
+ [19, 'sandwichbottom', 0, 0, [17, None]]],
+ 'picture2x2':
+ [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [1, 'penup', 0, 0, [0, 2]],
+ [2, 'setxy2', 0, 0, [1, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'pendown', 0, 0, [2, 6]],
+ [6, 'setscale', 0, 0, [5, 7, 8]],
+ [7, ['number', '100'], 0, 0, [6, None]],
+ [8, 'show', 0, 0, [6, 9, 10]],
+ [9, ['string', _('Title')], 0, 0, [8, None]],
+ [10, 'setscale', 0, 0, [8, 11, 12]],
+ [11, ['number', '35'], 0, 0, [10, None]],
+ [12, 'penup', 0, 0, [10, 13]],
+ [13, 'setxy2', 0, 0, [12, 14, 15, 16]],
+ [14, 'leftx', 0, 0, [13, None]],
+ [15, 'topy', 0, 0, [13, None]],
+ [16, 'pendown', 0, 0, [13, 17]],
+ [17, 'showaligned', 0, 0, [16, 18, 19]],
+ [18, 'journal', 0, 0, [17, None]],
+ [19, 'penup', 0, 0, [17, 20]],
+ [20, 'setxy2', 0, 0, [19, 21, 22, 23]],
+ [21, 'rightx', 0, 0, [20, None]],
+ [22, 'topy', 0, 0, [20, None]],
+ [23, 'pendown', 0, 0, [20, 24]],
+ [24, 'showaligned', 0, 0, [23, 25, 26]],
+ [25, 'journal', 0, 0, [24, None]],
+ [26, 'penup', 0, 0, [24, 27]],
+ [27, 'setxy2', 0, 0, [26, 28, 29, 30]],
+ [28, 'leftx', 0, 0, [27, None]],
+ [29, 'bottomy', 0, 0, [27, None]],
+ [30, 'pendown', 0, 0, [27, 31]],
+ [31, 'showaligned', 0, 0, [30, 32, 33]],
+ [32, 'journal', 0, 0, [31, None]],
+ [33, 'penup', 0, 0, [31, 34]],
+ [34, 'setxy2', 0, 0, [33, 35, 36, 37]],
+ [35, 'rightx', 0, 0, [34, None]],
+ [36, 'bottomy', 0, 0, [34, None]],
+ [37, 'pendown', 0, 0, [34, 38]],
+ [38, 'showaligned', 0, 0, [37, 39, 40]],
+ [39, 'journal', 0, 0, [38, None]],
+ [40, 'sandwichbottom', 0, 0, [38, None]]],
+ 'picture1x2':
+ [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [1, 'penup', 0, 0, [0, 2]],
+ [2, 'setxy2', 0, 0, [1, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'pendown', 0, 0, [2, 6]],
+ [6, 'setscale', 0, 0, [5, 7, 8]],
+ [7, ['number', '100'], 0, 0, [6, None]],
+ [8, 'show', 0, 0, [6, 9, 10]],
+ [9, ['string', _('Title')], 0, 0, [8, None]],
+ [10, 'setscale', 0, 0, [8, 11, 12]],
+ [11, ['number', '35'], 0, 0, [10, None]],
+ [12, 'penup', 0, 0, [10, 13]],
+ [13, 'setxy2', 0, 0, [12, 14, 15, 16]],
+ [14, 'leftx', 0, 0, [13, None]],
+ [15, 'topy', 0, 0, [13, None]],
+ [16, 'pendown', 0, 0, [13, 17]],
+ [17, 'showaligned', 0, 0, [16, 18, 19]],
+ [18, 'journal', 0, 0, [17, None]],
+ [19, 'penup', 0, 0, [17, 20]],
+ [20, 'setxy2', 0, 0, [19, 21, 22, 23]],
+ [21, 'rightx', 0, 0, [20, None]],
+ [22, 'topy', 0, 0, [20, None]],
+ [23, 'pendown', 0, 0, [20, 24]],
+ [24, 'showaligned', 0, 0, [23, 25, 26]],
+ [25, 'description', 0, 0, [24, None]],
+ [26, 'penup', 0, 0, [24, 27]],
+ [27, 'setxy2', 0, 0, [26, 28, 29, 30]],
+ [28, 'leftx', 0, 0, [27, None]],
+ [29, 'bottomy', 0, 0, [27, None]],
+ [30, 'pendown', 0, 0, [27, 31]],
+ [31, 'showaligned', 0, 0, [30, 32, 33]],
+ [32, 'journal', 0, 0, [31, None]],
+ [33, 'penup', 0, 0, [31, 34]],
+ [34, 'setxy2', 0, 0, [33, 35, 36, 37]],
+ [35, 'rightx', 0, 0, [34, None]],
+ [36, 'bottomy', 0, 0, [34, None]],
+ [37, 'pendown', 0, 0, [34, 38]],
+ [38, 'showaligned', 0, 0, [37, 39, 40]],
+ [39, 'description', 0, 0, [38, None]],
+ [40, 'sandwichbottom', 0, 0, [38, None]]],
+ 'picture2x1':
+ [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [1, 'penup', 0, 0, [0, 2]],
+ [2, 'setxy2', 0, 0, [1, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'pendown', 0, 0, [2, 6]],
+ [6, 'setscale', 0, 0, [5, 7, 8]],
+ [7, ['number', '100'], 0, 0, [6, None]],
+ [8, 'show', 0, 0, [6, 9, 10]],
+ [9, ['string', _('Title')], 0, 0, [8, None]],
+ [10, 'setscale', 0, 0, [8, 11, 12]],
+ [11, ['number', '35'], 0, 0, [10, None]],
+ [12, 'penup', 0, 0, [10, 13]],
+ [13, 'setxy2', 0, 0, [12, 14, 15, 16]],
+ [14, 'leftx', 0, 0, [13, None]],
+ [15, 'topy', 0, 0, [13, None]],
+ [16, 'pendown', 0, 0, [13, 17]],
+ [17, 'showaligned', 0, 0, [16, 18, 19]],
+ [18, 'journal', 0, 0, [17, None]],
+ [19, 'penup', 0, 0, [17, 20]],
+ [20, 'setxy2', 0, 0, [19, 21, 22, 23]],
+ [21, 'rightx', 0, 0, [20, None]],
+ [22, 'topy', 0, 0, [20, None]],
+ [23, 'pendown', 0, 0, [20, 24]],
+ [24, 'showaligned', 0, 0, [23, 25, 26]],
+ [25, 'journal', 0, 0, [24, None]],
+ [26, 'penup', 0, 0, [24, 27]],
+ [27, 'setxy2', 0, 0, [26, 28, 29, 30]],
+ [28, 'leftx', 0, 0, [27, None]],
+ [29, 'bottomy', 0, 0, [27, None]],
+ [30, 'pendown', 0, 0, [27, 31]],
+ [31, 'showaligned', 0, 0, [30, 32, 33]],
+ [32, 'description', 0, 0, [31, None]],
+ [33, 'penup', 0, 0, [31, 34]],
+ [34, 'setxy2', 0, 0, [33, 35, 36, 37]],
+ [35, 'rightx', 0, 0, [34, None]],
+ [36, 'bottomy', 0, 0, [34, None]],
+ [37, 'pendown', 0, 0, [34, 38]],
+ [38, 'showaligned', 0, 0, [37, 39, 40]],
+ [39, 'description', 0, 0, [38, None]],
+ [40, 'sandwichbottom', 0, 0, [38, None]]],
+ 'picture1x1':
+ [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [1, 'penup', 0, 0, [0, 2]],
+ [2, 'setxy2', 0, 0, [1, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'pendown', 0, 0, [2, 6]],
+ [6, 'setscale', 0, 0, [5, 7, 8]],
+ [7, ['number', '100'], 0, 0, [6, None]],
+ [8, 'show', 0, 0, [6, 9, 10]],
+ [9, ['string', _('Title')], 0, 0, [8, None]],
+ [10, 'setscale', 0, 0, [8, 11, 12]],
+ [11, ['number', '35'], 0, 0, [10, None]],
+ [12, 'penup', 0, 0, [10, 13]],
+ [13, 'setxy2', 0, 0, [12, 14, 15, 16]],
+ [14, 'leftx', 0, 0, [13, None]],
+ [15, 'topy', 0, 0, [13, None]],
+ [16, 'pendown', 0, 0, [13, 17]],
+ [17, 'showaligned', 0, 0, [16, 18, 19]],
+ [18, 'journal', 0, 0, [17, None]],
+ [19, 'penup', 0, 0, [17, 20]],
+ [20, 'setxy2', 0, 0, [19, 21, 22, 23]],
+ [21, 'rightx', 0, 0, [20, None]],
+ [22, 'topy', 0, 0, [20, None]],
+ [23, 'pendown', 0, 0, [20, 24]],
+ [24, 'showaligned', 0, 0, [23, 25, 26]],
+ [25, 'description', 0, 0, [24, None]],
+ [26, 'sandwichbottom', 0, 0, [24, None]]],
+ 'reskin':
+ [[0, 'skin', 0, 0, [None, 1, None]],
+ [1, 'journal', 0, 0, [0, None]]]}
diff --git a/TurtleArt/taexporthtml.py b/TurtleArt/taexporthtml.py
new file mode 100644
index 0000000..0e6a2f1
--- /dev/null
+++ b/TurtleArt/taexporthtml.py
@@ -0,0 +1,161 @@
+#Copyright (c) 2008-9, 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.
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import os.path
+from cgi import escape
+from gettext import gettext as _
+
+from tautils import data_to_string, save_picture, image_to_base64, get_path
+
+# A dictionary to define the HTML wrappers around template elements
+HTML_GLUE = {
+ 'doctype': '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 ' + \
+ 'Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n',
+ 'html': ('<html>\n', '</html>\n'),
+ 'html_svg': ('<html xmlns="http://www.w3.org/1999/xhtml">\n',
+ '</html>\n'),
+ 'head': ('<head>\n<!-- Created by Turtle Art -->\n', '</head>\n'),
+ 'meta': '<meta http-equiv="content-type" content="text/html; ' + \
+ 'charset=UTF-8"/>\n',
+ 'title': ('<title>', '</title>\n'),
+ 'style': ('<style type="text/css">\n<!--\n', '-->\n</style>\n'),
+ 'body': ('<body>\n', '\n</body>\n'),
+ 'div': ('<div>\n', '</div>\n'),
+ 'slide': ('\n<a name="slide', '"></a>\n'),
+ 'h1': ('<h1>', '</h1>\n'),
+ 'table': ('<table cellpadding="10\'>\n', '</table>\n'),
+ 'tr': ('<tr>\n', '</tr>\n'),
+ 'td': ('<td valign="top" width="400" height="300">\n',
+ '\n</td>\n'),
+ 'img': ('<img width="400" height="300" alt="Image" ' + \
+ 'src="file://"', '".png" />\n'),
+ 'img2': ('<img alt="Image" src="image"', '".png" />\n'),
+ 'img3': ('<img alt="Image" src="file://"', '"" />\n'),
+ 'ul': ('<table>\n', '</table>\n'),
+ 'li': ('<tr><td>', '</td></tr>\n')}
+
+COMMENT = '<!--\n\<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"' + \
+ ' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\n\
+ <!ENTITY ns_svg "http://www.w3.org/2000/svg">\n\
+ <!ENTITY ns_xlink "http://www.w3.org/1999/xlink">\n\
+]>\n\
+-->\n'
+
+
+def save_html(self, tw, embed_flag=True):
+ """ Either save the canvas and code or pictures to HTML """
+
+ if embed_flag:
+ HTML_GLUE['img'] = ('<img width="400" height="300" alt=' + \
+ '"Image" src="data:image/png;base64,\n',
+ '"/>\n')
+ HTML_GLUE['img2'] = ('<img alt="Image" src="data:image/png;' + \
+ 'base64,\n', '"/>\n')
+
+ """
+ If there are saved_pictures, put them into a .html; otherwise,
+ save a screendump and the turtle project code.
+ """
+ htmlcode = ''
+ if len(tw.saved_pictures) > 0:
+ # saved_picture list is a collection of tuples of either the
+ # image_file or the containing dsobject and an SVG flag
+ for i, (image, svg_flag) in enumerate(tw.saved_pictures):
+ htmlcode += HTML_GLUE['slide'][0] + str(i)
+ htmlcode += HTML_GLUE['slide'][1] + \
+ HTML_GLUE['div'][0] + \
+ HTML_GLUE['h1'][0]
+ if tw.running_sugar:
+ from sugar.datastore import datastore
+ dobject = datastore.get(image) # dsobject.object_id
+ image_file = dobject.file_path
+ else:
+ image_file = image
+ if embed_flag:
+ f = open(image_file, 'r')
+ imgdata = f.read()
+ f.close()
+ if svg_flag:
+ tmp = imgdata
+ else:
+ imgdata = image_to_base64(
+ image_file, get_path(tw.activity, 'instance'))
+ tmp = HTML_GLUE['img2'][0]
+ tmp += imgdata
+ tmp += HTML_GLUE['img2'][1]
+ else:
+ if svg_flag:
+ f = open(image_file, 'r')
+ imgdata = f.read()
+ f.close()
+ tmp = imgdata
+ else:
+ tmp = HTML_GLUE['img3'][0]
+ tmp += image_file
+ tmp += HTML_GLUE['img3'][1]
+ htmlcode += tmp + \
+ HTML_GLUE['h1'][1] + \
+ HTML_GLUE['div'][1]
+ else:
+ if embed_flag:
+ tmp_file = os.path.join(get_path(tw.activity, 'instance'),
+ 'tmpfile.png')
+ save_picture(self.tw.canvas, tmp_file)
+ imgdata = image_to_base64(tmp_file,
+ get_path(tw.activity, 'instance'))
+ else:
+ imgdata = os.path.join(self.tw.load_save_folder, 'image')
+ self.tw.save_as_image(imgdata)
+ htmlcode += (HTML_GLUE['img'][0] + imgdata + \
+ HTML_GLUE['img'][1])
+ htmlcode += HTML_GLUE['div'][0]
+ htmlcode += escape(data_to_string(
+ tw.assemble_data_to_save(False, True)))
+ htmlcode += HTML_GLUE['div'][1]
+
+ if tw.running_sugar:
+ title = _('Turtle Art') + ' ' + tw.activity.metadata['title']
+ else:
+ title = _('Turtle Art')
+
+ header = HTML_GLUE['doctype'] + \
+ HTML_GLUE['html'][0]
+ style = HTML_GLUE['style'][0] + \
+ HTML_GLUE['style'][1]
+ if len(tw.saved_pictures) > 0:
+ if tw.saved_pictures[0][1]:
+ header = HTML_GLUE['html_svg'][0]
+ style = COMMENT
+
+ return header + \
+ HTML_GLUE['head'][0] + \
+ HTML_GLUE['meta'] + \
+ HTML_GLUE['title'][0] + \
+ title + \
+ HTML_GLUE['title'][1] + \
+ style + \
+ HTML_GLUE['head'][1] + \
+ HTML_GLUE['body'][0] + \
+ htmlcode + \
+ HTML_GLUE['body'][1] + \
+ HTML_GLUE['html'][1]
diff --git a/TurtleArt/taexportlogo.py b/TurtleArt/taexportlogo.py
new file mode 100644
index 0000000..0d0660e
--- /dev/null
+++ b/TurtleArt/taexportlogo.py
@@ -0,0 +1,272 @@
+#Copyright (c) 2008-11, 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.
+
+from gettext import gettext as _
+
+try:
+ from sugar.datastore import datastore
+ HAS_DATASTORE = True
+except:
+ HAS_DATASTORE = False
+
+from TurtleArt.tapalette import logo_commands, logo_functions
+from TurtleArt.taconstants import TITLEXY, CONSTANTS
+
+def save_logo(tw):
+ """ Set up the Turtle Art color palette and color processing. """
+
+ # We need to catch several special cases: stacks, boxes, labels, etc.
+ dispatch_table = {
+ 'label': _add_label,
+ 'to action': _add_named_stack,
+ 'action': _add_reference_to_stack,
+ 'storeinbox': _add_named_box,
+ 'box': _add_reference_to_box
+ }
+ constants_table = {
+ 'lpos': _lpos,
+ 'tpos': _tpos,
+ 'rpos': _rpos,
+ 'bpos': _bpos,
+ 'red': _red,
+ 'orange': _orange,
+ 'yellow': _yellow,
+ 'green': _green,
+ 'cyan': _cyan,
+ 'blue': _blue,
+ 'purple': _purple,
+ 'white': _white,
+ 'black': _black,
+ 'titlex': _titlex,
+ 'titley': _titley,
+ 'leftx': _leftx,
+ 'topy': _topy,
+ 'rightx': _rightx,
+ 'bottomy': _bottomy,
+ 'width': _width,
+ 'height': _height
+ }
+
+ stacks_of_blocks = tw.just_blocks()
+ stack_count = 0
+
+ logocode = ''
+
+ """
+ Walk through the code, substituting UCB Logo for Turtle Art primitives.
+ """
+ for stack in stacks_of_blocks:
+ this_stack = ''
+ psuedocode = _walk_stack(tw, stack)
+ if psuedocode == []:
+ continue
+
+ skip = False
+ for i in range(len(psuedocode)):
+ if skip:
+ skip = False
+ continue
+ blk = psuedocode[i]
+ if type(blk) == type((1, 2)):
+ (blk, _blk_no) = blk
+ if blk in logo_commands:
+ logo_command = logo_commands[blk]
+ else:
+ logo_command = None
+ if i == 0 and not logo_command in ['to stack1\r', 'to stack2\r',
+ 'to action', 'to start\r']:
+ this_stack = 'to turtleblocks_%d\r' % (stack_count)
+ stack_count += 1
+ if logo_command in dispatch_table:
+ if i + 1 < len(psuedocode):
+ this_stack += dispatch_table[logo_command](
+ psuedocode[i + 1])
+ skip = True
+ else:
+ print 'missing arg to %s' % (logo_command)
+ elif logo_command in constants_table:
+ this_stack += str(constants_table[logo_command](tw))
+ elif logo_command is not None:
+ this_stack += logo_command
+ else: # assume it is an argument
+ if not blk in ['nop', 'nop1', 'nop2', 'nop3']:
+ if type(blk) == str and blk[0:2] == '#s':
+ this_stack += str(blk[2:]).replace(' ', '_')
+ else:
+ this_stack += str(blk).replace(' ', '_')
+ this_stack += ' '
+
+ logocode += this_stack
+ logocode += '\rend\r'
+
+ # We may need to prepend some additional procedures.
+ for key in logo_functions.iterkeys():
+ if key in logocode:
+ logocode = logo_functions[key] + logocode
+
+ if 'tasetshade' in logocode or 'tasetpencolor' in logocode or \
+ 'tasetbackground' in logocode:
+ logocode = logo_functions['tacolor'] + logocode
+
+ logocode = 'window\r' + logocode
+ return logocode
+
+
+def _add_label(string):
+ if type(string) == str and string[0:8] in ['#smedia_', '#saudio_',
+ '#svideo_', '#sdescr_']:
+ string = string[8:]
+ if HAS_DATASTORE:
+ dsobject = datastore.get(string[8:])
+ if 'title' in dsobject.metadata:
+ string = dsobject.metadata['title']
+ else:
+ string = str(string)
+ if string[0:2] == '#s':
+ string = string[2:]
+ string = '"' + string
+ if string.count(' ') > 0:
+ return 'label sentence %s\r' % (string.replace(' ', ' "'))
+ else:
+ return 'label %s' % (string.replace(' ', '_'))
+
+
+def _add_named_stack(action):
+ if type(action) == str and action[0:2] == '#s':
+ return 'to %s\r' % (str(action[2:]).replace(' ', '_'))
+ else:
+ return 'to %s\r' % (str(action).replace(' ', '_'))
+
+
+def _add_reference_to_stack(action):
+ if type(action) == str and action[0:2] == '#s':
+ return '%s' % (str(action[2:]).replace(' ', '_'))
+ else:
+ return '%s' % (str(action).replace(' ', '_'))
+
+
+def _add_named_box(box_name):
+ if type(box_name) == str and box_name[0:2] == '#s':
+ return 'make "%s' % (str(box_name[2:]).replace(' ', '_'))
+ else:
+ return 'make "%s' % (str(box_name).replace(' ', '_'))
+
+
+def _add_reference_to_box(box_name):
+ if type(box_name) == str and box_name[0:2] == '#s':
+ return ':%s' % (str(box_name[2:]).replace(' ', '_'))
+ else:
+ return ':%s' % (str(box_name).replace(' ', '_'))
+
+
+def _lpos(tw):
+ return int(-tw.canvas.width / (tw.coord_scale * 2))
+
+
+def _tpos(tw):
+ return int(tw.canvas.height / (tw.coord_scale * 2))
+
+
+def _rpos(tw):
+ return int(tw.canvas.width / (tw.coord_scale * 2))
+
+
+def _bpos(tw):
+ return int(-tw.canvas.height / (tw.coord_scale * 2))
+
+
+def _width(tw):
+ return int(tw.canvas.width / tw.coord_scale)
+
+
+def _height(tw):
+ int(tw.canvas.height / tw.coord_scale)
+
+
+def _titlex(tw):
+ return int(-(tw.canvas.width * TITLEXY[0]) / (tw.coord_scale * 2))
+
+
+def _titley(tw):
+ return int((tw.canvas.height * TITLEXY[1]) / (tw.coord_scale * 2))
+
+
+def _leftx(tw):
+ return int(-(tw.canvas.width * TITLEXY[0]) / (tw.coord_scale * 2))
+
+
+def _topy(tw):
+ return int((tw.canvas.height * (TITLEXY[1] - 0.125)) / (tw.coord_scale * 2))
+
+
+def _rightx(tw):
+ return 0
+
+
+def _bottomy(tw):
+ return 0
+
+
+def _red(tw):
+ return CONSTANTS['red']
+
+
+def _orange(tw):
+ return CONSTANTS['orange']
+
+
+def _yellow(tw):
+ return CONSTANTS['yellow']
+
+
+def _green(tw):
+ return CONSTANTS['green']
+
+
+def _cyan(tw):
+ return CONSTANTS['cyan']
+
+
+def _blue(tw):
+ return CONSTANTS['blue']
+
+
+def _purple(tw):
+ return CONSTANTS['purple']
+
+
+def _white(tw):
+ return 1
+
+
+def _black(tw):
+ return 0
+
+
+def _walk_stack(tw, blk_in_stack):
+ """ Convert blocks to logo psuedocode. """
+ from tautils import find_top_block
+
+ top = find_top_block(blk_in_stack)
+ if blk_in_stack == top:
+ psuedocode = tw.lc.run_blocks(top, tw.just_blocks(), False)
+ return psuedocode
+ else:
+ return []
diff --git a/TurtleArt/tagettext.py b/TurtleArt/tagettext.py
new file mode 100644
index 0000000..ebeebb3
--- /dev/null
+++ b/TurtleArt/tagettext.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2010-11 Walter Bender, Martin Langhoff
+# License: GPLv2
+
+# Defines the magic global _() with the right params so all modules
+# can use it.
+#
+# Plugins that want to override MUST use a different technique. See
+# the developer notes in the TA wikipage.
+#
+import gettext
+import os
+
+# In a git checkout, locale is in the root of the project
+# so one dir "up" from tagettext.py
+localedir = os.path.join(os.path.dirname(os.path.dirname(__file__)),
+ 'locale' )
+
+if os.path.exists(localedir):
+ # works from a git checkout
+ gettext.install('org.laptop.TurtleArtActivity', localedir)
+else:
+ # fallback for packaged TA (rpm, xo)
+ gettext.install('org.laptop.TurtleArtActivity')
diff --git a/TurtleArt/tagplay.py b/TurtleArt/tagplay.py
new file mode 100644
index 0000000..72379c6
--- /dev/null
+++ b/TurtleArt/tagplay.py
@@ -0,0 +1,300 @@
+"""
+ tagplay.py
+ refactored based on Jukebox Activity
+ Copyright (C) 2007 Andy Wingo <wingo@pobox.com>
+ Copyright (C) 2007 Red Hat, Inc.
+ Copyright (C) 2008-2010 Kushal Das <kushal@fedoraproject.org>
+ Copyright (C) 2010 Walter Bender
+"""
+
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# 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 logging
+import os
+
+import pygtk
+pygtk.require('2.0')
+
+import gobject
+gobject.threads_init()
+
+import pygst
+import gst
+import gst.interfaces
+import gtk
+
+import urllib
+
+
+def play_audio_from_file(lc, file_path):
+ """ Called from Show block of audio media """
+ if lc.gplay is not None and lc.gplay.player is not None:
+ if lc.gplay.player.playing:
+ lc.gplay.player.stop()
+ if lc.gplay.bin is not None:
+ lc.gplay.bin.destroy()
+
+ lc.gplay = Gplay(lc, lc.tw.canvas.width, lc.tw.canvas.height, 4, 3)
+ lc.gplay.start(file_path)
+
+
+def play_movie_from_file(lc, filepath, x, y, w, h):
+ """ Called from Show block of video media """
+ if lc.gplay is not None and lc.gplay.player is not None:
+ if lc.gplay.player.playing:
+ lc.gplay.player.stop()
+ if lc.gplay.bin is not None:
+ lc.gplay.bin.destroy()
+
+ lc.gplay = Gplay(lc, x, y, w, h)
+ lc.gplay.start(filepath)
+
+
+def stop_media(lc):
+ """ Called from Clean block and toolbar Stop button """
+ if lc.gplay == None:
+ return False
+
+ if lc.gplay.player is not None:
+ lc.gplay.player.stop()
+ if lc.gplay.bin != None:
+ lc.gplay.bin.destroy()
+
+ lc.gplay = None
+
+
+def media_playing(lc):
+ if lc.gplay == None:
+ return False
+ return lc.gplay.player.is_playing()
+
+
+class Gplay():
+ UPDATE_INTERVAL = 500
+
+ def __init__(self, lc, x, y, w, h):
+
+ self.player = None
+ self.uri = None
+ self.playlist = []
+ self.jobjectlist = []
+ self.playpath = None
+ self.only_audio = False
+ self.got_stream_info = False
+ self.currentplaying = 0
+
+ self.bin = gtk.Window()
+
+ self.videowidget = VideoWidget()
+ self.bin.add(self.videowidget)
+ self.bin.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NORMAL)
+ self.bin.set_decorated(False)
+ if lc.tw.running_sugar:
+ self.bin.set_transient_for(lc.tw.activity)
+
+ self.bin.move(x, y + 108)
+ self.bin.resize(w, h)
+ self.bin.show_all()
+
+ self._want_document = True
+
+ def _player_eos_cb(self, widget):
+ logging.debug('end of stream')
+
+ def _player_error_cb(self, widget, message, detail):
+ self.player.stop()
+ self.player.set_uri(None)
+ logging.debug('Error: %s - %s' % (message, detail))
+
+ def _player_stream_info_cb(self, widget, stream_info):
+ if not len(stream_info) or self.got_stream_info:
+ return
+
+ GST_STREAM_TYPE_VIDEO = 2
+
+ only_audio = True
+ for item in stream_info:
+ if item.props.type == GST_STREAM_TYPE_VIDEO:
+ only_audio = False
+ self.only_audio = only_audio
+ self.got_stream_info = True
+
+ def start(self, uri=None):
+ self._want_document = False
+ self.playpath = os.path.dirname(uri)
+ if not uri:
+ return False
+ self.playlist.append('file://' + urllib.quote(os.path.abspath(uri)))
+ if not self.player:
+ # lazy init the player so that videowidget is realized
+ # and has a valid widget allocation
+ self.player = GstPlayer(self.videowidget)
+ self.player.connect('eos', self._player_eos_cb)
+ self.player.connect('error', self._player_error_cb)
+ self.player.connect('stream-info', self._player_stream_info_cb)
+
+ try:
+ if not self.currentplaying:
+ logging.info('Playing: ' + self.playlist[0])
+ self.player.set_uri(self.playlist[0])
+ self.currentplaying = 0
+ self.play_toggled()
+ self.show_all()
+ else:
+ pass
+ except:
+ pass
+ return False
+
+ def play_toggled(self):
+ if self.player.is_playing():
+ self.player.pause()
+ else:
+ if self.player.error:
+ pass
+ else:
+ self.player.play()
+
+
+class GstPlayer(gobject.GObject):
+ __gsignals__ = {
+ 'error': (gobject.SIGNAL_RUN_FIRST, None, [str, str]),
+ 'eos': (gobject.SIGNAL_RUN_FIRST, None, []),
+ 'stream-info': (gobject.SIGNAL_RUN_FIRST, None, [object])}
+
+ def __init__(self, videowidget):
+ gobject.GObject.__init__(self)
+
+ self.playing = False
+ self.error = False
+
+ self.player = gst.element_factory_make('playbin', 'player')
+
+ self.videowidget = videowidget
+ self._init_video_sink()
+
+ bus = self.player.get_bus()
+ bus.enable_sync_message_emission()
+ bus.add_signal_watch()
+ bus.connect('sync-message::element', self.on_sync_message)
+ bus.connect('message', self.on_message)
+
+ def set_uri(self, uri):
+ self.player.set_property('uri', uri)
+
+ def on_sync_message(self, bus, message):
+ if message.structure is None:
+ return
+ if message.structure.get_name() == 'prepare-xwindow-id':
+ self.videowidget.set_sink(message.src)
+ message.src.set_property('force-aspect-ratio', True)
+
+ def on_message(self, bus, message):
+ t = message.type
+ if t == gst.MESSAGE_ERROR:
+ err, debug = message.parse_error()
+ logging.debug('Error: %s - %s' % (err, debug))
+ self.error = True
+ self.emit('eos')
+ self.playing = False
+ self.emit('error', str(err), str(debug))
+ elif t == gst.MESSAGE_EOS:
+ self.emit('eos')
+ self.playing = False
+ elif t == gst.MESSAGE_STATE_CHANGED:
+ old, new, pen = message.parse_state_changed()
+ if old == gst.STATE_READY and new == gst.STATE_PAUSED:
+ self.emit('stream-info',
+ self.player.props.stream_info_value_array)
+
+ def _init_video_sink(self):
+ self.bin = gst.Bin()
+ videoscale = gst.element_factory_make('videoscale')
+ self.bin.add(videoscale)
+ pad = videoscale.get_pad('sink')
+ ghostpad = gst.GhostPad('sink', pad)
+ self.bin.add_pad(ghostpad)
+ videoscale.set_property('method', 0)
+
+ caps_string = 'video/x-raw-yuv, '
+ r = self.videowidget.get_allocation()
+ if r.width > 500 and r.height > 500:
+ # Sigh... xvimagesink on the XOs will scale the video to fit
+ # but ximagesink in Xephyr does not. So we live with unscaled
+ # video in Xephyr so that the XO can work right.
+ w = 480
+ h = float(w) / float(float(r.width) / float(r.height))
+ caps_string += 'width=%d, height=%d' % (w, h)
+ else:
+ caps_string += 'width=480, height=360'
+
+ caps = gst.Caps(caps_string)
+ self.filter = gst.element_factory_make('capsfilter', 'filter')
+ self.bin.add(self.filter)
+ self.filter.set_property('caps', caps)
+
+ conv = gst.element_factory_make('ffmpegcolorspace', 'conv')
+ self.bin.add(conv)
+ videosink = gst.element_factory_make('autovideosink')
+ self.bin.add(videosink)
+ gst.element_link_many(videoscale, self.filter, conv, videosink)
+ self.player.set_property('video-sink', self.bin)
+
+ def pause(self):
+ self.player.set_state(gst.STATE_PAUSED)
+ self.playing = False
+ logging.debug('pausing player')
+
+ def play(self):
+ self.player.set_state(gst.STATE_PLAYING)
+ self.playing = True
+ self.error = False
+ logging.debug('playing player')
+
+ def stop(self):
+ self.player.set_state(gst.STATE_NULL)
+ self.playing = False
+ logging.debug('stopped player')
+ return False
+
+ def get_state(self, timeout=1):
+ return self.player.get_state(timeout=timeout)
+
+ def is_playing(self):
+ return self.playing
+
+
+class VideoWidget(gtk.DrawingArea):
+
+ def __init__(self):
+ gtk.DrawingArea.__init__(self)
+ self.set_events(gtk.gdk.EXPOSURE_MASK)
+ self.imagesink = None
+ self.unset_flags(gtk.DOUBLE_BUFFERED)
+ self.set_flags(gtk.APP_PAINTABLE)
+
+ def do_expose_event(self, event):
+ if self.imagesink:
+ self.imagesink.expose()
+ return False
+ else:
+ return True
+
+ def set_sink(self, sink):
+ assert self.window.xid
+ self.imagesink = sink
+ self.imagesink.set_xwindow_id(self.window.xid)
diff --git a/TurtleArt/tajail.py b/TurtleArt/tajail.py
new file mode 100644
index 0000000..0444dc7
--- /dev/null
+++ b/TurtleArt/tajail.py
@@ -0,0 +1,60 @@
+#Copyright (c) 2009-10, Walter Bender (on behalf of Sugar Labs)
+
+#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.
+
+# A naive approach to running myfunc in a jail
+import traceback
+from time import *
+from math import *
+
+from gettext import gettext as _
+
+def myfunc(f, args):
+ ''' Run inline Python code '''
+ # check to make sure no import calls are made
+ if len(args) == 1:
+ myf = 'def f(x): return ' + f.replace('import', '')
+ userdefined = {}
+ exec myf in globals(), userdefined
+ return userdefined.values()[0](args[0])
+ elif len(args) == 2:
+ myf = 'def f(x, y): return ' + f.replace('import', '')
+ userdefined = {}
+ exec myf in globals(), userdefined
+ return userdefined.values()[0](args[0], args[1])
+ elif len(args) == 3:
+ myf = 'def f(x, y, z): return ' + f.replace('import', '')
+ userdefined = {}
+ exec myf in globals(), userdefined
+ return userdefined.values()[0](args[0], args[1], args[2])
+
+
+def myfunc_import(parent, f, x):
+ ''' Run Python code imported from Journal '''
+ if 'def myblock(lc,' in f:
+ base_class = parent.tw.lc # pre-v107, we passed lc
+ else:
+ base_class = parent.tw # as of v107, we pass tw
+ userdefined = {}
+ try:
+ exec f in globals(), userdefined
+ return userdefined['myblock'](base_class, x)
+ except:
+ traceback.print_exc()
+ return None
diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py
new file mode 100644
index 0000000..1e5bf07
--- /dev/null
+++ b/TurtleArt/talogo.py
@@ -0,0 +1,908 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2007-8, Playful Invention Company.
+#Copyright (c) 2008-12, Walter Bender
+#Copyright (c) 2008-10, Raúl Gutiérrez Segalés
+
+#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.
+
+import gtk
+from time import time
+
+from operator import isNumberType
+from UserDict import UserDict
+
+from taconstants import TAB_LAYER, DEFAULT_SCALE, PREFIX_DICTIONARY
+from tapalette import block_names, value_blocks
+from tautils import get_pixbuf_from_journal, convert, data_from_file, \
+ text_media_type, round_int, debug_output
+
+from util.RtfParser import RtfTextOnly
+
+from gettext import gettext as _
+
+media_blocks_dictionary = {} # new media blocks get added here
+primitive_dictionary = {} # new block primitives get added here
+
+
+class noKeyError(UserDict):
+
+ __missing__ = lambda x, y: 0
+
+
+class symbol:
+
+ def __init__(self, name):
+ self.name = name
+ self.nargs = None
+ self.fcn = None
+
+ def __str__(self):
+ return self.name
+
+ def __repr__(self):
+ return '#' + self.name
+
+
+class logoerror(Exception):
+
+ def __init__(self, value):
+ print value
+ self.value = value
+
+ def __str__(self):
+ # return repr(self.value)
+ return str(self.value)
+
+
+class HiddenBlock:
+
+ def __init__(self, name, value=None):
+ self.name = name
+ self.values = []
+ if value is not None:
+ self.values.append(value)
+ self.primitive = None
+ else:
+ self.primitive = name
+ self.connections = []
+ self.docks = []
+
+# Utility functions
+
+
+def _just_stop():
+ """ yield False to stop stack """
+ yield False
+
+
+def _millisecond():
+ """ Current time in milliseconds """
+ return time() * 1000
+
+
+class LogoCode:
+ """ A class for parsing Logo code """
+
+ def __init__(self, tw):
+
+ self.tw = tw
+ self.oblist = {}
+
+ DEFPRIM = {'(': [1, lambda self, x: self._prim_opar(x)],
+ 'define': [2, self._prim_define],
+ 'nop': [0, lambda self: None]}
+
+ for p in iter(DEFPRIM):
+ if len(DEFPRIM[p]) == 2:
+ self.def_prim(p, DEFPRIM[p][0], DEFPRIM[p][1])
+ else:
+ self.def_prim(p, DEFPRIM[p][0], DEFPRIM[p][1], DEFPRIM[p][2])
+
+ self.symtype = type(self._intern('print'))
+ self.listtype = type([])
+ self.symnothing = self._intern('%nothing%')
+ self.symopar = self._intern('(')
+ self.iline = None
+ self.cfun = None
+ self.arglist = None
+ self.ufun = None
+ self.procstop = False
+ self.running = False
+ self.istack = []
+ self.stacks = {}
+ self.boxes = {'box1': 0, 'box2': 0}
+ self.heap = []
+ self.iresults = None
+ self.step = None
+ self.bindex = None
+
+ self.hidden_turtle = None
+
+ self.keyboard = 0
+ self.trace = 0
+ self.update_values = False
+ self.gplay = None
+ self.filepath = None
+ self.pixbuf = None
+ self.dsobject = None
+ self.start_time = None
+
+ self.body_height = int((self.tw.canvas.height / 40) * self.tw.scale)
+
+ self.scale = DEFAULT_SCALE
+
+ def stop_logo(self):
+ """ Stop logo is called from the Stop button on the toolbar """
+ self.tw.step_time = 0
+ self.step = _just_stop()
+ for plugin in self.tw._plugins:
+ plugin.stop()
+ if self.tw.gst_available:
+ from tagplay import stop_media
+ stop_media(self)
+ self.tw.active_turtle.show()
+
+ def def_prim(self, name, args, fcn, rprim=False):
+ """ Define the primitives associated with the blocks """
+ sym = self._intern(name)
+ sym.nargs, sym.fcn = args, fcn
+ sym.rprim = rprim
+
+ def _intern(self, string):
+ """ Add any new objects to the symbol list. """
+ if string in self.oblist:
+ return self.oblist[string]
+ sym = symbol(string)
+ self.oblist[string] = sym
+ return sym
+
+ def run_blocks(self, blk, blocks, run_flag):
+ """ Given a block to run... """
+ for k in self.stacks.keys():
+ self.stacks[k] = None
+ self.stacks['stack1'] = None
+ self.stacks['stack2'] = None
+
+ # Save state in case there is a hidden macro expansion
+ self.save_blocks = None
+ self.save_blk = blk
+ self.save_while_blks = []
+
+ if self.trace > 0:
+ self.update_values = True
+ else:
+ self.update_values = False
+
+ for b in blocks:
+ b.unhighlight()
+
+ for b in blocks:
+ # Hidden macro expansions
+ if b.name in ['while', 'until', 'forever']:
+ action_blk, new_blocks = self._expand_forever(b, blk, blocks)
+ blocks = new_blocks[:]
+ if b == blk:
+ blk = action_blk
+
+ for b in blocks:
+ if b.name == 'hat1':
+ code = self._blocks_to_code(b)
+ self.stacks['stack1'] = self._readline(code)
+ elif b.name == 'hat2':
+ code = self._blocks_to_code(b)
+ self.stacks['stack2'] = self._readline(code)
+ elif b.name == 'hat':
+ if b.connections is not None and len(b.connections) > 1 and \
+ b.connections[1] is not None:
+ code = self._blocks_to_code(b)
+ try:
+ x = b.connections[1].values[0]
+ except IndexError:
+ self.tw.showlabel('#nostack')
+ self.tw.showblocks()
+ return None
+ if type(convert(x, float, False)) == float:
+ if int(float(x)) == x:
+ x = int(x)
+ self.stacks['stack3' + str(x)] = self._readline(code)
+
+ code = self._blocks_to_code(blk)
+
+ if self.save_blocks is not None:
+ # Undo any hidden macro expansion
+ blocks = self.save_blocks[:]
+ blk = self.save_blk
+ for b in self.save_while_blks:
+ if b[1] is not None:
+ b[0].connections[0].connections[b[1]] = b[0]
+ if b[2] is not None:
+ b[0].connections[-1].connections[b[2]] = b[0]
+ if b[3] is not None:
+ b[0].connections[-2].connections[b[3]] = b[0]
+
+ if run_flag:
+ # debug_output("running code: %s" % (code), self.tw.running_sugar)
+ self.start_time = time()
+ self._setup_cmd(code)
+ else:
+ return code
+
+ def _blocks_to_code(self, blk):
+ """ Convert a stack of blocks to pseudocode. """
+ if blk is None:
+ return ['%nothing%', '%nothing%']
+ code = []
+ dock = blk.docks[0]
+ if len(dock) > 4: # There could be a '(', ')', '[' or ']'.
+ code.append(dock[4])
+ if blk.primitive is not None: # make a tuple (prim, blk)
+ # special case: expand 'while' and 'until' primitives
+ try:
+ code.append((blk.primitive,
+ self.tw.block_list.list.index(blk)))
+ except ValueError:
+ code.append(blk.primitive) # Hidden block
+ elif len(blk.values) > 0: # Extract the value from content blocks.
+ if blk.name == 'number':
+ try:
+ code.append(float(blk.values[0]))
+ except ValueError:
+ code.append(float(ord(blk.values[0][0])))
+ elif blk.name == 'string' or \
+ blk.name == 'title': # deprecated block
+ if type(blk.values[0]) == float or \
+ type(blk.values[0]) == int:
+ if int(blk.values[0]) == blk.values[0]:
+ blk.values[0] = int(blk.values[0])
+ code.append('#s' + str(blk.values[0]))
+ else:
+ code.append('#s' + blk.values[0])
+ elif blk.name in PREFIX_DICTIONARY:
+ if blk.values[0] is not None:
+ code.append(PREFIX_DICTIONARY[blk.name] + \
+ str(blk.values[0]))
+ else:
+ code.append(PREFIX_DICTIONARY[blk.name] + 'None')
+ elif blk.name in media_blocks_dictionary:
+ code.append('#smedia_' + blk.name.upper())
+ else:
+ return ['%nothing%']
+ else:
+ return ['%nothing%']
+ if blk.connections is not None and len(blk.connections) > 0:
+ for i in range(1, len(blk.connections)):
+ b = blk.connections[i]
+ dock = blk.docks[i]
+ if len(dock) > 4: # There could be a '(', ')', '[' or ']'.
+ for c in dock[4]:
+ code.append(c)
+ if b is not None:
+ code.extend(self._blocks_to_code(b))
+ elif blk.docks[i][0] not in ['flow', 'unavailable']:
+ code.append('%nothing%')
+ return code
+
+ def _setup_cmd(self, string):
+ """ Execute the psuedocode. """
+ self.hidden_turtle = self.tw.active_turtle
+ self.hidden_turtle.hide() # Hide the turtle while we are running.
+ self.procstop = False
+ blklist = self._readline(string)
+ self.step = self._start_eval(blklist)
+
+ def _readline(self, line):
+ """
+ Convert the pseudocode into a list of commands.
+ The block associated with the command is stored as the second element
+ in a tuple, e.g., (#forward, 16)
+ """
+ # debug_output(line, self.tw.running_sugar)
+ res = []
+ while line:
+ token = line.pop(0)
+ bindex = None
+ if type(token) == tuple:
+ (token, bindex) = token
+ if isNumberType(token):
+ res.append(token)
+ elif token.isdigit():
+ res.append(float(token))
+ elif token[0] == '-' and token[1:].isdigit():
+ res.append(-float(token[1:]))
+ elif token[0] == '"':
+ res.append(token[1:])
+ elif token[0:2] == "#s":
+ res.append(token[2:])
+ elif token == '[':
+ res.append(self._readline(line))
+ elif token == ']':
+ return res
+ elif bindex is None or type(bindex) is not int:
+ res.append(self._intern(token))
+ else:
+ res.append((self._intern(token), bindex))
+ return res
+
+ def _start_eval(self, blklist):
+ """ Step through the list. """
+ if self.tw.running_sugar:
+ self.tw.activity.stop_turtle_button.set_icon("stopiton")
+ self.tw.activity.stop_turtle_button.set_tooltip(
+ _('Stop turtle'))
+ elif self.tw.interactive_mode:
+ self.tw.toolbar_shapes['stopiton'].set_layer(TAB_LAYER)
+ self.running = True
+ self.icall(self.evline, blklist)
+ yield True
+ if self.tw.running_sugar:
+ # self.tw.activity.stop_turtle_button.set_icon("stopitoff")
+ if self.tw.step_time == 0 and self.tw.selected_blk is None:
+ self.tw.activity.stop_turtle_button.set_icon("hideshowoff")
+ self.tw.activity.stop_turtle_button.set_tooltip(
+ _('Show blocks'))
+ else:
+ self.tw.activity.stop_turtle_button.set_icon("stopitoff")
+ self.tw.activity.stop_turtle_button.set_tooltip(
+ _('Stop turtle'))
+ elif self.tw.interactive_mode:
+ self.tw.toolbar_shapes['stopiton'].hide()
+ yield False
+ self.running = False
+
+ def icall(self, fcn, *args):
+ """ Add a function and its arguments to the program stack. """
+ self.istack.append(self.step)
+ self.step = fcn(*(args))
+
+ def evline(self, blklist):
+ """ Evaluate a line of code from the list. """
+ oldiline = self.iline
+ self.iline = blklist[:]
+ self.arglist = None
+ while self.iline:
+ token = self.iline[0]
+ self.bindex = None
+ if type(token) == tuple:
+ (token, self.bindex) = self.iline[0]
+
+ # If the blocks are visible, highlight the current block.
+ if not self.tw.hide and self.bindex is not None:
+ self.tw.block_list.list[self.bindex].highlight()
+
+ # In debugging modes, we pause between steps and show the turtle.
+ if self.tw.step_time > 0:
+ self.tw.active_turtle.show()
+ endtime = _millisecond() + self.tw.step_time * 100.
+ while _millisecond() < endtime:
+ yield True
+ self.tw.active_turtle.hide()
+
+ # 'Stand-alone' booleans are handled here.
+ if token == self.symopar:
+ token = self.iline[1]
+ if type(token) == tuple:
+ (token, self.bindex) = self.iline[1]
+
+ # Process the token and any arguments.
+ self.icall(self._eval)
+ yield True
+
+ # Time to unhighlight the current block.
+ if not self.tw.hide and self.bindex is not None:
+ self.tw.block_list.list[self.bindex].unhighlight()
+
+ if self.procstop:
+ break
+ if self.iresult == None:
+ continue
+
+ if self.bindex is not None:
+ self.tw.block_list.list[self.bindex].highlight()
+ self.tw.showblocks()
+ raise logoerror(str(self.iresult))
+ self.iline = oldiline
+ self.ireturn()
+ if not self.tw.hide and self.tw.step_time > 0:
+ self.tw.display_coordinates()
+ yield True
+
+ def _eval(self):
+ """ Evaluate the next token on the line of code we are processing. """
+ token = self.iline.pop(0)
+ bindex = None
+ if type(token) == tuple:
+ (token, bindex) = token
+
+ # Either we are processing a symbol or a value.
+ if type(token) == self.symtype:
+ # We highlight blocks here in case an error occurs...
+ if not self.tw.hide and bindex is not None:
+ self.tw.block_list.list[bindex].highlight()
+ self.icall(self._evalsym, token)
+ yield True
+ # and unhighlight if everything was OK.
+ if not self.tw.hide and bindex is not None:
+ self.tw.block_list.list[bindex].unhighlight()
+ res = self.iresult
+ else:
+ res = token
+
+ self.ireturn(res)
+ yield True
+
+ def _evalsym(self, token):
+ """ Process primitive associated with symbol token """
+ self._undefined_check(token)
+ oldcfun, oldarglist = self.cfun, self.arglist
+ self.cfun, self.arglist = token, []
+
+ if token.nargs == None:
+ self.tw.showblocks()
+ raise logoerror("#noinput")
+ for i in range(token.nargs):
+ self._no_args_check()
+ self.icall(self._eval)
+ yield True
+ self.arglist.append(self.iresult)
+ if self.cfun.rprim:
+ if type(self.cfun.fcn) == self.listtype:
+ # debug_output('evalsym rprim list: %s' % (str(token)),
+ # self.tw.running_sugar)
+ self.icall(self._ufuncall, self.cfun.fcn)
+ yield True
+ else:
+ self.icall(self.cfun.fcn, *self.arglist)
+ yield True
+ result = None
+ else:
+ result = self.cfun.fcn(self, *self.arglist)
+ self.cfun, self.arglist = oldcfun, oldarglist
+ if self.arglist is not None and result == None:
+ self.tw.showblocks()
+ raise logoerror("%s %s %s" % \
+ (oldcfun.name, _("did not output to"), self.cfun.name))
+ self.ireturn(result)
+ yield True
+
+ def _ufuncall(self, body):
+ """ ufuncall """
+ self.ijmp(self.evline, body)
+ yield True
+
+ def doevalstep(self):
+ """ evaluate one step """
+ starttime = _millisecond()
+ try:
+ while (_millisecond() - starttime) < 120:
+ try:
+ if self.step is not None:
+ try:
+ self.step.next()
+ except ValueError:
+ debug_output('generator already executing',
+ self.tw.running_sugar)
+ return False
+ else:
+ return False
+ except StopIteration:
+ # self.tw.turtles.show_all()
+ if self.hidden_turtle is not None:
+ self.hidden_turtle.show()
+ self.hidden_turtle = None
+ else:
+ self.tw.active_turtle.show()
+ return False
+ except logoerror, e:
+ self.tw.showblocks()
+ self.tw.showlabel('syntaxerror', str(e))
+ self.tw.turtles.show_all()
+ return False
+ return True
+
+ def ireturn(self, res=None):
+ """ return value """
+ self.step = self.istack.pop()
+ self.iresult = res
+
+ def ijmp(self, fcn, *args):
+ """ ijmp """
+ self.step = fcn(*(args))
+
+ def _undefined_check(self, token):
+ """ Make sure token has a definition """
+ if token.fcn is not None:
+ return False
+ if token.name == '%nothing%':
+ errormsg = ''
+ else:
+ errormsg = "%s %s" % (_("I don't know how to"), _(token.name))
+ self.tw.showblocks()
+ raise logoerror(errormsg)
+
+ def _no_args_check(self):
+ """ Missing argument ? """
+ if self.iline and self.iline[0] is not self.symnothing:
+ return
+ self.tw.showblocks()
+ raise logoerror("#noinput")
+
+ #
+ # Primitives
+ #
+
+ def _prim_opar(self, val):
+ self.iline.pop(0)
+ return val
+
+ def _prim_define(self, name, body):
+ """ Define a primitive """
+ if type(name) is not self.symtype:
+ name = self._intern(name)
+ name.nargs, name.fcn = 0, body
+ name.rprim = True
+
+ def prim_clear(self):
+ """ Clear screen """
+ self.tw.clear_plugins()
+ if self.tw.gst_available:
+ from tagplay import stop_media
+ # stop_media(self) # TODO: gplay variable
+ self.tw.canvas.clearscreen()
+ self.scale = DEFAULT_SCALE
+ self.hidden_turtle = None
+ self.start_time = time()
+ for name in value_blocks:
+ self.update_label_value(name)
+
+ def int(self, n):
+ """ Raise an error if n doesn't convert to int. """
+ if type(n) == int:
+ return n
+ elif type(n) == float:
+ return int(n)
+ elif type(n) == str:
+ return int(ord(n[0]))
+ else:
+ self.tw.showblocks()
+ raise logoerror("%s %s %s %s" \
+ % (self.cfun.name, _("doesn't like"), str(n), _("as input")))
+
+ def find_value_blocks(self):
+ """ Find any value blocks that may need label updates """
+ self.value_blocks_to_update = {}
+ for name in value_blocks:
+ self.value_blocks_to_update[name] = \
+ self.tw.block_list.get_similar_blocks('block', name)
+
+ def update_label_value(self, name, value=None):
+ """ Update the label of value blocks to reflect current value """
+ if not self.tw.interactive_mode:
+ return
+ if self.tw.hide:
+ return
+ self.tw.display_coordinates()
+ if value is None:
+ for block in self.value_blocks_to_update[name]:
+ block.spr.set_label(block_names[name][0])
+ block.resize()
+ elif self.update_values:
+ if type(value) == float:
+ valstring = str(round_int(value)).replace('.',
+ self.tw.decimal_point)
+ else:
+ valstring = str(value)
+ for block in self.value_blocks_to_update[name]:
+ block.spr.set_label(block_names[name][0] + ' = ' + valstring)
+ block.resize()
+
+ def push_file_data_to_heap(self, dsobject):
+ """ push contents of a data store object (assuming json encoding) """
+ data = data_from_file(dsobject.file_path)
+ if data is not None:
+ for val in data:
+ self.heap.append(val)
+ self.update_label_value('pop', self.heap[-1])
+
+ def x2tx(self):
+ """ Convert screen coordinates to turtle coordinates """
+ return int(self.tw.canvas.width / 2) + int(self.tw.canvas.xcor)
+
+ def y2ty(self):
+ """ Convert screen coordinates to turtle coordinates """
+ return int(self.tw.canvas.height / 2) - int(self.tw.canvas.ycor)
+
+ def wpercent(self):
+ """ width as a percentage of screen coordinates """
+ return int((self.tw.canvas.width * self.scale) / 100.)
+
+ def hpercent(self):
+ """ height as a percentage of screen coordinates """
+ return int((self.tw.canvas.height * self.scale) / 100.)
+
+ def insert_image(self, center=False, filepath=None, resize=True,
+ offset=False, pixbuf=False):
+ """ Image only (at current x, y) """
+ if filepath is not None:
+ self.filepath = filepath
+ if not pixbuf:
+ self.pixbuf = None
+ w, h = self.wpercent(), self.hpercent()
+ if w < 1 or h < 1:
+ return
+ if pixbuf: # We may have to rescale the picture
+ if w != self.pixbuf.get_width() or h != self.pixbuf.get_height():
+ self.pixbuf = self.pixbuf.scale_simple(
+ w, h, gtk.gdk.INTERP_BILINEAR)
+ elif self.dsobject is not None:
+ try:
+ self.pixbuf = get_pixbuf_from_journal(self.dsobject, w, h)
+ except:
+ debug_output("Couldn't open dsobject %s" % (self.dsobject),
+ self.tw.running_sugar)
+ if self.pixbuf is None and \
+ self.filepath is not None and \
+ self.filepath != '':
+ try:
+ if not resize:
+ self.pixbuf = gtk.gdk.pixbuf_new_from_file(self.filepath)
+ w = self.pixbuf.get_width()
+ h = self.pixbuf.get_height()
+ else:
+ self.pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
+ self.filepath, w, h)
+ except:
+ self.tw.showlabel('nojournal', self.filepath)
+ debug_output("Couldn't open filepath %s" % (self.filepath),
+ self.tw.running_sugar)
+ if self.pixbuf is not None:
+ x = self.tw.canvas.xcor
+ y = self.tw.canvas.ycor
+ w *= self.tw.coord_scale
+ h *= self.tw.coord_scale
+ if center:
+ self.tw.canvas.draw_pixbuf(self.pixbuf, 0, 0,
+ self.x2tx() - int(w / 2),
+ self.y2ty() - int(h / 2), w, h,
+ self.filepath)
+ elif offset:
+ self.tw.canvas.draw_pixbuf(self.pixbuf, 0, 0,
+ self.x2tx(),
+ self.y2ty() - h,
+ w, h, self.filepath)
+ else:
+ self.tw.canvas.draw_pixbuf(self.pixbuf, 0, 0,
+ self.x2tx(),
+ self.y2ty(),
+ w, h, self.filepath)
+
+ def insert_desc(self, mimetype=None, description=None):
+ """ Description text only (at current x, y) """
+ w = self.wpercent()
+ if w < 1:
+ return
+ text = None
+ if text_media_type(self.filepath):
+ if mimetype == 'application/rtf' or \
+ self.filepath.endswith(('rtf')):
+ text_only = RtfTextOnly()
+ for line in open(self.filepath, 'r'):
+ text_only.feed(line)
+ text = text_only.output
+ else:
+ try:
+ f = open(self.filepath, 'r')
+ text = f.read()
+ f.close()
+ except IOError:
+ self.tw.showlabel('nojournal', self.filepath)
+ debug_output("Couldn't open %s" % (self.filepath),
+ self.tw.running_sugar)
+ else:
+ if description is not None:
+ text = str(description)
+ else:
+ text = self.filepath
+ if text is not None:
+ self.tw.canvas.draw_text(text, self.x2tx(), self.y2ty(),
+ self.body_height, w)
+
+ def media_wait(self):
+ """ Wait for media to stop playing """
+ if self.tw.gst_available:
+ from tagplay import media_playing
+ while(media_playing(self)):
+ yield True
+ self.ireturn()
+ yield True
+
+ def play_sound(self):
+ """ Sound file from Journal """
+ if self.tw.gst_available:
+ from tagplay import play_audio_from_file
+ play_audio_from_file(self, self.filepath)
+
+ def play_video(self):
+ """ Movie file from Journal """
+ w, h = self.wpercent(), self.hpercent()
+ if w < 1 or h < 1:
+ return
+ if self.tw.gst_available:
+ from tagplay import play_movie_from_file
+ play_movie_from_file(self, self.filepath, self.x2tx(), self.y2ty(),
+ w, h)
+
+ def _expand_forever(self, b, blk, blocks):
+ """ Expand a while or until block into: forever, ifelse, stopstack
+ Expand a forever block to run in a separate stack """
+
+ # TODO: create a less brittle way of doing this; having to
+ # manage the connections and flows locally means we may run
+ # into trouble if any of these block types (forever, while,
+ # until. ifelse, stopstack, or stack) is changed in tablock.py
+ if b.name == 'while':
+ while_blk = True
+ else:
+ while_blk = False
+ if b.name == 'until':
+ until_blk = True
+ else:
+ until_blk = False
+
+ # We'll restore the original blocks when we are finished
+ if self.save_blocks is None:
+ self.save_blocks = blocks[:]
+
+ # Create an action block that will jump to the new stack
+ action_name = '#s_forever %d' % (len(self.save_while_blks) + 1)
+ action_blk = HiddenBlock('stack')
+ action_label_blk = HiddenBlock('string', value=action_name)
+
+ # Create the blocks we'll put in the new stack
+ forever_blk = HiddenBlock('forever')
+ if while_blk or until_blk:
+ ifelse_blk = HiddenBlock('ifelse')
+ stopstack_blk = HiddenBlock('stopstack')
+ inflow = None
+ whileflow = None
+ outflow = None
+ boolflow = None
+ if b.connections is not None:
+ inflow = b.connections[0]
+ if while_blk or until_blk:
+ boolflow = b.connections[1]
+ whileflow = b.connections[-2]
+ outflow = b.connections[-1]
+
+ # Create action block(s) to run the code inside the forever loop
+ if until_blk and whileflow is not None: # run until flow at least once
+ action_flow_name = '#s_flow %d' % (len(self.save_while_blks) + 1)
+ action_first = HiddenBlock('stack')
+ first_label_blk = HiddenBlock('string', value=action_flow_name)
+
+ # Assign new connections and build the docks
+ if inflow is not None:
+ i = inflow.connections.index(b)
+ if until_blk and whileflow is not None:
+ inflow.connections[i] = action_first
+ else:
+ inflow.connections[i] = action_blk
+ else:
+ i = None
+ if outflow is not None:
+ j = outflow.connections.index(b)
+ outflow.connections[j] = action_blk
+ else:
+ j = None
+
+ if until_blk and whileflow is not None:
+ action_first.connections.append(inflow)
+ action_first.docks.append(['flow', True, 0, 0])
+ action_first.connections.append(first_label_blk)
+ action_first.docks.append(['number', False, 0, 0])
+ action_first.connections.append(action_blk)
+ action_first.docks.append(['flow', False, 0, 0])
+ first_label_blk.connections.append(action_first)
+ first_label_blk.docks.append(['number', True, 0, 0])
+ action_blk.connections.append(action_first)
+ else:
+ action_blk.connections.append(inflow)
+ action_blk.docks.append(['flow', True, 0, 0])
+ action_blk.connections.append(action_label_blk)
+ action_blk.docks.append(['number', False, 0, 0])
+ action_blk.connections.append(outflow)
+ action_blk.docks.append(['flow', False, 0, 0])
+ action_label_blk.connections.append(action_blk)
+ action_label_blk.docks.append(['number', True, 0, 0])
+
+ forever_blk.connections.append(None)
+ forever_blk.docks.append(['flow', True, 0, 0])
+ if while_blk or until_blk:
+ forever_blk.connections.append(ifelse_blk)
+ else:
+ forever_blk.connections.append(whileflow)
+ forever_blk.docks.append(['flow', False, 0, 0, '['])
+ forever_blk.connections.append(outflow)
+ forever_blk.docks.append(['flow', False, 0, 0, ']'])
+ if while_blk or until_blk:
+ ifelse_blk.connections.append(forever_blk)
+ ifelse_blk.docks.append(['flow', True, 0, 0])
+ ifelse_blk.connections.append(boolflow)
+ ifelse_blk.docks.append(['bool', False, 0, 0])
+ if while_blk:
+ ifelse_blk.connections.append(whileflow)
+ ifelse_blk.connections.append(stopstack_blk)
+ else: # until
+ ifelse_blk.connections.append(stopstack_blk)
+ ifelse_blk.connections.append(whileflow)
+ ifelse_blk.docks.append(['flow', False, 0, 0, '['])
+ ifelse_blk.docks.append(['flow', False, 0, 0, ']['])
+ ifelse_blk.connections.append(None)
+ ifelse_blk.docks.append(['flow', False, 0, 0, ']'])
+ stopstack_blk.connections.append(ifelse_blk)
+ stopstack_blk.docks.append(['flow', False, 0, 0])
+
+ if whileflow is not None:
+ if while_blk or until_blk:
+ whileflow.connections[0] = ifelse_blk
+ else:
+ whileflow.connections[0] = forever_blk
+
+ # Create a separate stacks for the forever loop and the whileflow
+ code = self._blocks_to_code(forever_blk)
+ self.stacks['stack3' + str(action_name)] = self._readline(code)
+ if until_blk and whileflow is not None:
+ # Create a stack from the whileflow to be called from
+ # action_first, but then reconnect it to the ifelse block
+ c = whileflow.connections[0]
+ whileflow.connections[0] = None
+ code = self._blocks_to_code(whileflow)
+ self.stacks['stack3' + str(action_flow_name)] = \
+ self._readline(code)
+ whileflow.connections[0] = c
+
+ # Save the connections so we can restore them later
+ if whileflow is not None:
+ self.save_while_blks.append([b, i, j, 0])
+ else:
+ self.save_while_blks.append([b, i, j, None])
+
+ # Insert the new blocks into the stack
+ i = blocks.index(b)
+ if i == 0:
+ blocks_left = []
+ else:
+ blocks_left = blocks[0:i]
+ if i == len(blocks) - 1:
+ blocks_right = []
+ else:
+ blocks_right = blocks[i + 1:]
+ blocks = blocks_left[:]
+ if until_blk and whileflow is not None:
+ blocks.append(action_first)
+ blocks.append(action_blk)
+ blocks.append(forever_blk)
+ if while_blk or until_blk:
+ blocks.append(ifelse_blk)
+ blocks.append(stopstack_blk)
+ blocks.extend(blocks_right)
+
+ if until_blk and whileflow is not None:
+ return action_first, blocks
+ else:
+ return action_blk, blocks
diff --git a/TurtleArt/tapalette.py b/TurtleArt/tapalette.py
new file mode 100644
index 0000000..ad96103
--- /dev/null
+++ b/TurtleArt/tapalette.py
@@ -0,0 +1,376 @@
+#!/usr/bin/env python
+#Copyright (c) 2011,12 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.
+
+help_palettes = {}
+help_windows = {}
+palette_names = []
+palette_blocks = []
+block_colors = []
+expandable_blocks = []
+block_names = {}
+block_primitives = {}
+default_values = {}
+logo_commands = {}
+logo_functions = {}
+special_names = {} # Names for blocks without names for popup help
+content_blocks = ['number', 'string', 'description', 'audio', 'video',
+ 'journal']
+hidden_proto_blocks = [] # proto blocks that are (at least initially) hidden
+value_blocks = [] # blocks whose labels are updated get added here
+special_block_colors = {}
+string_or_number_args = []
+block_styles = {'basic-style': [],
+ 'blank-style': [],
+ 'basic-style-head': [],
+ 'basic-style-head-1arg': [],
+ 'basic-style-tail': [],
+ 'basic-style-extended': [],
+ 'basic-style-extended-vertical': [],
+ 'basic-style-1arg': [],
+ 'basic-style-2arg': [],
+ 'basic-style-3arg': [],
+ 'basic-style-var-arg': [],
+ 'bullet-style': [],
+ 'invisible': [],
+ 'box-style': [],
+ 'box-style-media': [],
+ 'number-style': [],
+ 'number-style-var-arg': [],
+ 'number-style-block': [],
+ 'number-style-porch': [],
+ 'number-style-1arg': [],
+ 'number-style-1strarg': [],
+ 'compare-style': [],
+ 'compare-porch-style': [],
+ 'boolean-style': [],
+ 'not-style': [],
+ 'flow-style': [],
+ 'flow-style-tail': [],
+ 'flow-style-1arg': [],
+ 'flow-style-boolean': [],
+ 'flow-style-while': [],
+ 'flow-style-else': [],
+ 'collapsible-top': [],
+ 'collapsible-top-no-arm': [],
+ 'collapsible-top-no-label': [],
+ 'collapsible-top-no-arm-no-label': [],
+ 'collapsible-bottom': [],
+ 'portfolio-style-2x2': [],
+ 'portfolio-style-1x1': [],
+ 'portfolio-style-2x1': [],
+ 'portfolio-style-1x2': []}
+
+
+import gtk
+
+from sugar.graphics.icon import Icon
+from sugar.graphics import style
+
+from taconstants import EXPANDABLE_STYLE
+from tautils import debug_output
+
+from util.helpbutton import add_section, add_paragraph
+
+from gettext import gettext as _
+
+help_strings = {
+ 'next': _('displays next palette'),
+ 'orientation': _("changes the orientation of the palette of blocks")
+ }
+
+
+class Palette():
+ """ a class for defining new palettes """
+
+ def __init__(self, name, colors=["#00FF00", "#00A000"], position=None):
+ self._name = name
+ self._special_name = _(name)
+ self._colors = colors
+ self._max_text_width = int(gtk.gdk.screen_width() / 3) - 20
+
+ # Prepare a vbox for the help palette
+ if not self._name in help_palettes:
+ self._help_box = gtk.VBox()
+ self._help_box.set_homogeneous(False)
+ help_palettes[self._name] = self._help_box
+ help_windows[self._name] = gtk.ScrolledWindow()
+ help_windows[self._name].set_size_request(
+ int(gtk.gdk.screen_width() / 3),
+ gtk.gdk.screen_height() - style.GRID_CELL_SIZE * 3)
+ help_windows[self._name].set_policy(
+ gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ help_windows[self._name].add_with_viewport(
+ help_palettes[self._name])
+ help_palettes[self._name].show()
+ self._help = None
+ else:
+ self._help_box = help_palettes[self._name]
+ self._help = 'deja vu'
+
+ def add_palette(self, position=None):
+ if self._name is None:
+ debug_output('You must specify a name for your palette')
+ return
+
+ # Insert new palette just before the trash
+ if 'trash' in palette_names:
+ i = palette_names.index('trash')
+ else:
+ i = len(palette_names)
+
+ if position is not None and type(position) is int and position < i:
+ i = position
+
+ if self._name not in palette_names:
+ palette_names.insert(i, self._name)
+ palette_blocks.insert(i, [])
+ block_colors.insert(i, self._colors)
+ else:
+ return
+
+ # Special name entry is needed for help hover mechanism
+ special_names[self._name] = self._special_name
+ if self._help is not None:
+ help_strings[self._name] = self._help
+ else:
+ help_strings[self._name] = ''
+
+ def set_help(self, help):
+ if self._help is None:
+ self._help = help
+ if hasattr(self, '_help_box'):
+ add_section(self._help_box, self._help, icon=self._name + 'off')
+
+ def set_special_name(self, name):
+ self._special_name = name
+
+ def add_block(self, block_name, style='basic-block', label=None,
+ special_name=None, default=None, prim_name=None,
+ help_string=None, value_block=False, content_block=False,
+ logo_command=None, hidden=False, colors=None,
+ string_or_number=False):
+ """ Add a new block to the palette """
+ block = Block(block_name)
+ block.set_style(style)
+ if label is not None:
+ block.set_label(label)
+ if special_name is not None:
+ block.set_special_name(special_name)
+ if default is not None:
+ if default == 'None':
+ block.set_default(None)
+ else:
+ block.set_default(default)
+ if prim_name is not None:
+ block.set_prim_name(prim_name)
+ if logo_command is not None:
+ block.set_logo_command(logo_command)
+ if help_string is not None:
+ block.set_help(help_string)
+ if not hidden:
+ first_arg = None
+ if special_name is None:
+ if type(label) == list:
+ first_arg = label[0]
+ else:
+ first_arg = label
+ else:
+ first_arg = special_name
+ if first_arg is None or first_arg == '' or first_arg == ' ':
+ add_paragraph(self._help_box, '%s' % (help_string))
+ else:
+ add_paragraph(self._help_box, '%s: %s' % (first_arg,
+ help_string))
+ if colors is not None:
+ block.set_colors(colors)
+ if string_or_number:
+ block.set_string_or_number()
+ block.set_value_block(value_block)
+ block.set_content_block(content_block)
+ block.set_palette(self._name)
+ if hidden:
+ block.set_hidden()
+ block.add_block()
+
+
+def make_palette(palette_name, colors=None, help_string=None, position=None):
+ """ Palette helper function """
+ if colors is None:
+ palette = Palette(palette_name)
+ else:
+ palette = Palette(palette_name, colors)
+ if help_string is not None:
+ palette.set_help(help_string)
+ palette.add_palette(position)
+ return palette
+
+
+def palette_name_to_index(palette_name):
+ ''' Find the index associated with palette_name. '''
+ if palette_name in palette_names:
+ return palette_names.index(palette_name)
+ else:
+ return None
+
+
+def define_logo_function(key, value):
+ ''' Add a logo function to the table (not necessarily associated
+ with a block, e.g., color lookup tables) '''
+ logo_functions[key] = value
+
+
+class Block():
+ """ a class for defining new block primitives """
+
+ def __init__(self, name):
+ self._name = name
+ self._special_name = None
+ self._palette = None
+ self._style = None
+ self._label = None
+ self._default = None
+ self._help = None
+ self._prim_name = None
+ self._logo_command = None
+ self._value_block = False
+ self._content_block = False
+ self._colors = None
+ self._hidden = False
+ self._string_or_number = False
+
+ def add_block(self, position=None):
+ if self._name is None:
+ debug_output('You must specify a name for your block')
+ return
+
+ # FIXME: Does the block already exist? A block can live on
+ # multiple palettes, but it can only have one set of
+ # atttributes. So if this is a redefinition, remove it from
+ # all lists except palettes before regeneration.
+
+ if self._style is None:
+ debug_output('You must specify a style for your block')
+ return
+ else:
+ block_styles[self._style].append(self._name)
+
+ if self._label is not None:
+ block_names[self._name] = self._label
+
+ if self._palette is not None:
+ i = palette_names.index(self._palette)
+ if self._name in palette_blocks[i]:
+ debug_output('%s already in palette %s, skipping...' % (
+ self._name, self._palette))
+ else:
+ if position is not None and type(position) is int and \
+ position < len(palette_blocks[i]):
+ palette_blocks[i].insert(position, self._name)
+ else:
+ palette_blocks[i].append(self._name)
+ if position is not None:
+ debug_output('Ignoring position (%s)' % (str(position)))
+
+ if self._help is not None:
+ help_strings[self._name] = self._help
+ else:
+ help_strings[self._name] = ''
+
+ if self._value_block:
+ value_blocks.append(self._name)
+
+ if self._content_block:
+ content_blocks.append(self._name)
+
+ if self._prim_name is not None:
+ block_primitives[self._name] = self._prim_name
+
+ if self._logo_command is not None and self._prim_name is not None:
+ logo_commands[self._prim_name] = self._logo_command
+
+ if self._default is not None:
+ default_values[self._name] = self._default
+
+ if self._special_name is not None:
+ special_names[self._name] = self._special_name
+
+ if self._style in EXPANDABLE_STYLE:
+ expandable_blocks.append(self._name)
+
+ if self._colors is not None:
+ special_block_colors[self._name] = self._colors
+
+ if self._string_or_number:
+ string_or_number_args.append(self._name)
+
+ if self._hidden:
+ hidden_proto_blocks.append(self._name)
+
+ def set_hidden(self):
+ self._hidden = True
+
+ def set_colors(self, colors=None):
+ self._colors = colors
+
+ def set_string_or_number(self, flag=True):
+ self._string_or_number = flag
+
+ def set_value_block(self, value=True):
+ self._value_block = value
+
+ def set_content_block(self, value=True):
+ self._content_block = value
+
+ def set_palette(self, palette):
+ if not palette in palette_names:
+ debug_output('Could not find palette %s' % (palette))
+ else:
+ self._palette = palette
+
+ def set_help(self, help):
+ self._help = help
+
+ def set_special_name(self, name):
+ self._special_name = name
+
+ def set_label(self, label):
+ if type(label) == type([]):
+ self._label = label[:]
+ else:
+ self._label = [label]
+
+ def set_default(self, default):
+ if type(default) == type([]):
+ self._default = default[:]
+ else:
+ self._default = [default]
+
+ def set_style(self, style):
+ if style not in block_styles:
+ debug_output('Unknown style: %s' % (style))
+ else:
+ self._style = style
+
+ def set_prim_name(self, prim_name):
+ self._prim_name = prim_name
+
+ def set_logo_command(self, logo_command):
+ self._logo_command = logo_command
diff --git a/TurtleArt/tasprite_factory.py b/TurtleArt/tasprite_factory.py
new file mode 100755
index 0000000..2b0e922
--- /dev/null
+++ b/TurtleArt/tasprite_factory.py
@@ -0,0 +1,1233 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#Copyright (c) 2009-12 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.
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import os
+
+from taconstants import HIT_RED, HIT_GREEN, HIDE_WHITE, SHOW_WHITE, \
+ PALETTE_COLOR, TOOLBAR_COLOR
+
+
+class SVG:
+
+ def __init__(self):
+ self._x = 0
+ self._y = 0
+ self._min_x = 10000
+ self._min_y = 10000
+ self._max_x = -10000
+ self._max_y = -10000
+ self._width = 0
+ self._height = 0
+ self.docks = []
+ self._scale = 1
+ self._orientation = 0
+ self._radius = 8
+ self._stroke_width = 1
+ self._innie = [False]
+ self._outie = False
+ self._innie_x1 = (9 - self._stroke_width) / 2
+ self._innie_y1 = 3
+ self._innie_x2 = (9 - self._stroke_width) / 2
+ self._innie_y2 = (9 - self._stroke_width) / 2
+ self._innie_spacer = 9
+ self._slot = True
+ self._cap = False
+ self._tab = True
+ self._bool = False
+ self._slot_x = 10
+ self._slot_y = 2
+ self._tail = False
+ self._porch = False
+ self._porch_x = self._innie_x1 + self._innie_x2 + \
+ 4 * self._stroke_width
+ self._porch_y = self._innie_y2
+ self._expand_x = 0
+ self._expand_y = 0
+ self._arm = True
+ self._else = False
+ self._draw_innies = True
+ self._hide = False
+ self._show = False
+ self._show_x = 0
+ self._show_y = 0
+ self._hide_x = 0
+ self._hide_y = 0
+ self._dot_radius = 8
+ self._fill = "#00FF00"
+ self._stroke = "#00A000"
+ self._gradient_color = "#FFFFFF"
+ self._gradient = False
+ self.margins = [0, 0, 0, 0]
+
+ """
+ The block construction methods typically start on the left side of
+ a block and proceed clockwise around the block, first constructing a
+ left-side connector ("outie"), a corner (1, -1), a slot or hat on along
+ the top, a corner (1, 1), right side connectors ("innie"), possibly a
+ "porch" to suggest an order of arguments, another corner (-1, 1),
+ a tab or tail, and the fourth corner (-1, -1).
+ """
+
+ def basic_block(self):
+ ''' The most common block type: used for 0, 1, 2, or 3
+ argument commands, stacks, et al. '''
+ self.reset_min_max()
+ (x, y) = self._calculate_x_y()
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, y)
+ svg += self._corner(1, -1)
+ svg += self._do_slot()
+ svg += self._rline_to(self._expand_x, 0)
+ xx = self._x
+ svg += self._corner(1, 1)
+ for i in range(len(self._innie)):
+ if self._innie[i] is True:
+ svg += self._do_innie()
+ if i == 0:
+ svg += self._rline_to(0, self._expand_y)
+ if i == 0 and self._porch is True:
+ svg += self._do_porch(False)
+ elif len(self._innie) - 1 > i:
+ svg += self._rline_to(0,
+ 2 * self._innie_y2 + self._innie_spacer)
+ # moved expand_y to just after first innie above
+ # svg += self._rline_to(0, self._expand_y)
+ svg += self._corner(-1, 1)
+ svg += self.line_to(xx, self._y)
+ svg += self._rline_to(-self._expand_x, 0)
+ if self._tab:
+ svg += self._do_tab()
+ else:
+ svg += self._do_tail()
+ svg += self._corner(-1, -1)
+ svg += self._rline_to(0, -self._expand_y)
+ if True in self._innie:
+ svg += self.line_to(x, self._radius + self._innie_y2 + \
+ self._stroke_width / 2.0)
+ svg += self._do_outie()
+ self.calc_w_h()
+ svg += self._close_path()
+ svg += self.style()
+ if self._show is True:
+ svg += self._show_dot()
+ if self._hide is True:
+ svg += self._hide_dot()
+ svg += self.footer()
+ return self.header() + svg
+
+ def invisible(self):
+ ''' A block that is invisible but still has connectors: used
+ when collapsing stacks. '''
+ self.reset_min_max()
+ (x, y) = self._calculate_x_y()
+ self.margins[2] = 0
+ self.margins[3] = 0
+ # calculate shape but don't include it in the svg output
+ self.new_path(x, y)
+ self._corner(1, -1)
+ self._do_slot()
+ xx = self._x
+ self._corner(1, 1)
+ self._corner(-1, 1)
+ self._do_tab()
+ self._corner(-1, -1)
+ self.calc_w_h()
+ self._close_path()
+ self.style()
+ return self.header() + self.footer()
+
+ def basic_flow(self):
+ ''' A flow block includes an arm that holds a branch in the flow '''
+ self.reset_min_max()
+ (x, y) = self._calculate_x_y()
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, y)
+ svg += self._corner(1, -1)
+ svg += self._do_slot()
+ xx = self._x
+ svg += self._rline_to(self._expand_x, 0)
+ if self._bool:
+ svg += self._corner(1, 1, 90, 0, 1, True, False)
+ elif True in self._innie:
+ svg += self._corner(1, 1)
+ for i in range(len(self._innie)):
+ if self._innie[i] is True:
+ svg += self._do_innie()
+ svg += self._rline_to(0, self._innie_spacer)
+ else:
+ self.margins[2] = \
+ int((self._x - self._stroke_width + 0.5) * self._scale)
+ if self._bool is True:
+ svg += self._rline_to(0, self._radius / 2.0)
+ svg += self._do_boolean()
+ svg += self._rline_to(0, self._stroke_width)
+ if self._else:
+ svg += self._rline_to(self._radius * 3 + self._slot_x * 2, 0)
+ else:
+ svg += self._rline_to(self._radius + self._slot_x, 0)
+ save_y = self._y
+ hh = self._x
+ svg += self._corner(1, 1)
+ svg += self._rline_to(-self._radius, 0)
+ if self._else:
+ svg += self._do_tab()
+ svg += self._rline_to(-self._radius * 2, 0)
+ svg += self._do_tab()
+ svg += self._inverse_corner(-1, 1, 90, 0, 0, True, False)
+ svg += self._rline_to(0, self._expand_y)
+ svg += self._corner(-1, 1, 90, 0, 1, False, True)
+ svg += self.line_to(xx, self._y)
+ if self._tab:
+ svg += self._do_tab()
+ else:
+ svg += self._do_tail()
+ svg += self._corner(-1, -1)
+ svg += self._rline_to(0, -self._expand_y)
+ if True in self._innie:
+ svg += self.line_to(x, self._radius + self._innie_y2 + \
+ self._stroke_width)
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ if self._hide is True:
+ svg += self._hide_dot()
+ if self._show is True:
+ svg += self._show_dot()
+ svg += self.footer()
+ if self._bool is True: # move secondary labels to arm
+ self.margins[2] = self._radius * 1.5 * self._scale
+ self.margins[3] = (self._max_y - save_y - self._radius + \
+ self._stroke_width) * self._scale
+ return self.header() + svg
+
+ def portfolio(self):
+ ''' Deprecated block '''
+ self.reset_min_max()
+ (x, y) = self._calculate_x_y()
+ self.margins[0] = int(x + 2 * self._stroke_width + 0.5)
+ self.margins[1] = int(y + self._stroke_width + 0.5 + self._slot_y)
+ self.margins[2] = 0
+ self.margins[3] = 0
+ x += self._innie_x1 + self._innie_x2
+ svg = self.new_path(x, y)
+ svg += self._corner(1, -1)
+ svg += self._do_slot()
+ xx = self._x
+ svg += self._rline_to(self._expand_x, 0)
+ svg += self._corner(1, 1)
+ svg += self._rline_to(0, self._expand_y)
+ for i in range(len(self._innie)):
+ if self._innie[i] is True and i > 0 and self._draw_innies:
+ svg += self._do_innie()
+ svg += self._rline_to(0, 2 * self._innie_y2 + \
+ self._innie_spacer)
+ else:
+ svg += self._rline_to(0, 2 * self._innie_y2 + \
+ self._innie_spacer)
+ svg += self._corner(-1, 1)
+ svg += self.line_to(xx, self._y)
+ svg += self._do_tab()
+ svg += self._corner(-1, -1)
+ for i in range(len(self._innie)):
+ if self._innie[len(self._innie) - i - 1] is True:
+ svg += self._rline_to(0, -2 * self._innie_y2 - \
+ self._innie_spacer)
+ svg += self._do_reverse_innie()
+ else:
+ svg += self._rline_to(0, -2 * self._innie_y2 - \
+ self._innie_spacer)
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ svg += self.footer()
+ return self.header() + svg
+
+ def basic_box(self):
+ ''' Basic argument style used for numbers, text, media '''
+ self.reset_min_max()
+ self.set_outie(True)
+ x = self._stroke_width / 2.0 + self._innie_x1 + self._innie_x2
+ self.margins[0] = int((x + self._stroke_width + 0.5) * self._scale)
+ self.margins[1] = int((self._stroke_width + 0.5) * self._scale)
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, self._stroke_width / 2.0)
+ svg += self._rline_to(self._expand_x, 0)
+ svg += self._rline_to(0, 2 * self._radius + self._innie_y2 + \
+ self._expand_y)
+ svg += self._rline_to(-self._expand_x, 0)
+ svg += self.line_to(x, self._radius + self._innie_y2 + \
+ self._stroke_width / 2.0)
+ svg += self._do_outie()
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ svg += self.footer()
+ return self.header() + svg
+
+ def boolean_and_or(self):
+ ''' Booleans are in a class of their own '''
+ self.reset_min_max()
+ svg = self._start_boolean(self._stroke_width / 2.0,
+ self._radius * 5.5 + self._stroke_width / \
+ 2.0 + \
+ self._innie_y2 + self._innie_spacer +\
+ self._expand_y)
+ svg += self._rline_to(0, -self._radius * 3.5 - self._innie_y2 - \
+ self._innie_spacer - self._stroke_width)
+
+ self._hide_x = self._x + self._radius / 2 + self._stroke_width
+ self._hide_y = self._y - self._radius / 2 + self._stroke_width
+ self._show_x = self._x + self._radius / 2 + self._stroke_width
+
+ svg += self._rarc_to(1, -1)
+ svg += self._rline_to(self._radius / 2.0 + self._expand_x, 0)
+ xx = self._x
+ svg += self._rline_to(0, self._radius / 2.0)
+ svg += self._do_boolean()
+ svg += self._rline_to(0, self._radius * 1.5 + self._innie_y2 + \
+ self._innie_spacer)
+
+ svg += self._rline_to(0, self._expand_y)
+
+ svg += self._do_boolean()
+ svg += self._rline_to(0, self._radius / 2.0)
+
+ self._show_y = self._y + self._radius / 2
+ self._show_y -= (self._innie_y1 + self._innie_y2 + self._stroke_width)
+
+ svg += self.line_to(xx, self._y)
+ svg += self._rline_to(-self._expand_x, 0)
+ svg += self._end_boolean()
+ self.margins[0] = int((self._radius + self._stroke_width + 0.5) * \
+ self._scale)
+ self.margins[1] = int(self._stroke_width * self._scale)
+ self.margins[2] = int(self._stroke_width * self._scale)
+ self.margins[3] = int(self._stroke_width * self._scale)
+ return self.header() + svg
+
+ def boolean_not(self):
+ ''' Booleans are in a class of their own '''
+ self.reset_min_max()
+ svg = self._start_boolean(self._stroke_width / 2.0, self._radius * \
+ 2.0 + self._stroke_width / 2.0)
+ svg += self._rline_to(0, -self._stroke_width)
+ svg += self._rarc_to(1, -1)
+ svg += self._rline_to(self._radius / 2.0 + self._expand_x, 0)
+ xx = self._x
+ svg += self._rline_to(0, self._radius / 2.0)
+ svg += self._do_boolean()
+ svg += self._rline_to(0, self._radius / 2.0)
+ svg += self.line_to(xx, self._y)
+ svg += self._rline_to(-self._expand_x, 0)
+ svg += self._end_boolean()
+ self.margins[0] = int((self._radius + self._stroke_width + 0.5) * \
+ self._scale)
+ self.margins[1] = int(self._stroke_width * self._scale)
+ self.margins[2] = int((self._radius + self._stroke_width + 0.5) * \
+ self._scale)
+ self.margins[3] = int(self._stroke_width * self._scale)
+ return self.header() + svg
+
+ def boolean_compare(self):
+ ''' Booleans are in a class of their own '''
+ self.reset_min_max()
+ yoffset = self._radius * 2 + 2 * self._innie_y2 + \
+ self._innie_spacer + self._stroke_width / 2.0 + \
+ self._expand_y
+ svg = self._start_boolean(self._stroke_width / 2.0, yoffset)
+ yoffset = -2 * self._innie_y2 - self._innie_spacer - self._stroke_width
+ svg += self._rline_to(0, yoffset)
+
+ self._hide_x = self._x + self._radius / 2 + self._stroke_width
+ self._hide_y = self._y - self._radius / 2 + self._stroke_width
+ self._show_x = self._x + self._radius / 2 + self._stroke_width
+
+ svg += self._rarc_to(1, -1)
+ svg += self._rline_to(self._radius / 2.0 + self._expand_x, 0)
+ svg += self._rline_to(0, self._radius)
+ xx = self._x
+ svg += self._do_innie()
+ svg += self._rline_to(0, self._expand_y)
+ if self._porch is True:
+ svg += self._do_porch(False)
+ else:
+ svg += self._rline_to(0, 2 * self._innie_y2 + self._innie_spacer)
+ svg += self._do_innie()
+ svg += self._rline_to(0, self._radius)
+ svg += self.line_to(xx, self._y)
+
+ svg += self._rline_to(-self._expand_x, 0)
+
+ self._show_y = self._y + self._radius / 2
+ self._show_y -= (self._innie_y1 + self._innie_y2 + self._stroke_width)
+
+ svg += self._end_boolean()
+ self.margins[0] = int((self._radius + self._stroke_width) * \
+ self._scale)
+ self.margins[1] = int(self._stroke_width * self._scale)
+ self.margins[2] = int(self._stroke_width * self._scale)
+ return self.header() + svg
+
+ def triangle_up(self, colors):
+ ''' A triangle that points up '''
+ self.reset_min_max()
+ self._fill, self._stroke = colors[0], colors[1]
+ self._width, self._height = 55 * self._scale, 55 * self._scale
+ svg = self.new_path(5, 50)
+ svg += self._rline_to(22.5, -45)
+ svg += self._rline_to(22.5, 45)
+ svg += self._close_path()
+ svg += self.style()
+ svg += self.footer()
+ return self.header() + svg
+
+ def triangle_down(self, colors):
+ ''' A triangle that points down '''
+ self.reset_min_max()
+ self._fill, self._stroke = colors[0], colors[1]
+ self._width, self._height = 55 * self._scale, 55 * self._scale
+ svg = self.new_path(5, 5)
+ svg += self._rline_to(22.5, 45)
+ svg += self._rline_to(22.5, -45)
+ svg += self._close_path()
+ svg += self.style()
+ svg += self.footer()
+ return self.header() + svg
+
+ def turtle(self, colors):
+ ''' Turtles are just another block '''
+ self.reset_min_max()
+ self._fill, self._stroke = colors[1], colors[0]
+
+ svg = "%s%s%s%s%s%s%s%s" % (" <path d=\"M 27.5 48.3 ",
+ "C 26.9 48.3 26.4 48.2 25.9 48.2 L 27.2 50.5 L 28.6 48.2 ",
+ "C 28.2 48.2 27.9 48.3 27.5 48.3 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke,
+ "\" />\n")
+ svg += "%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 40.2 11.7 ",
+ "C 38.0 11.7 36.2 13.3 35.8 15.3 ",
+ "C 37.7 16.7 39.3 18.4 40.5 20.5 ",
+ "C 42.8 20.4 44.6 18.5 44.6 16.2 ",
+ "C 44.6 13.7 42.6 11.7 40.2 11.7 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg += "%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 40.7 39.9 ",
+ "C 39.5 42.1 37.9 44.0 35.9 45.4 ",
+ "C 36.4 47.3 38.1 48.7 40.2 48.7 ",
+ "C 42.6 48.7 44.6 46.7 44.6 44.3 ",
+ "C 44.6 42.0 42.9 40.2 40.7 39.9 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg += "%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 14.3 39.9 ",
+ "C 12.0 40.1 10.2 42.0 10.2 44.3 ",
+ "C 10.2 46.7 12.2 48.7 14.7 48.7 ",
+ "C 16.7 48.7 18.5 47.3 18.9 45.4 ",
+ "C 17.1 43.9 15.5 42.1 14.3 39.9 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg += "%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 19.0 15.4 ",
+ "C 18.7 13.3 16.9 11.7 14.7 11.7 ",
+ "C 12.2 11.7 10.2 13.7 10.2 16.2 ",
+ "C 10.2 18.5 12.1 20.5 14.5 20.6 ",
+ "C 15.7 18.5 17.2 16.8 19.0 15.4 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg += "%s%s%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 27.5 12.6 ",
+ "C 29.4 12.6 31.2 13.0 32.9 13.7 ",
+ "C 33.7 12.6 34.1 11.3 34.1 9.9 ",
+ "C 34.1 6.2 31.1 3.2 27.4 3.2 ",
+ "C 23.7 3.2 20.7 6.2 20.7 9.9 ",
+ "C 20.7 11.3 21.2 12.7 22.0 13.7 ",
+ "C 23.7 13.0 25.5 12.6 27.5 12.6 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg += "%s%s%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 43.1 30.4 ",
+ "C 43.1 35.2 41.5 39.7 38.5 43.0 ",
+ "C 35.6 46.4 31.6 48.3 27.5 48.3 ",
+ "C 23.4 48.3 19.4 46.4 16.5 43.0 ",
+ "C 13.5 39.7 11.9 35.2 11.9 30.4 ",
+ "C 11.9 20.6 18.9 12.6 27.5 12.6 ",
+ "C 36.1 12.6 43.1 20.6 43.1 30.4 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg += "%s%s%s%s%s" % (" <path d=\"M 25.9 33.8 L 24.3 29.1 ",
+ "L 27.5 26.5 L 31.1 29.2 L 29.6 33.8 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._stroke, ";\" stroke=\"none\" />\n")
+ svg += "%s%s%s%s%s%s" % (" <path d=\"M 27.5 41.6 ",
+ "C 23.5 41.4 22.0 39.5 22.0 39.5 L 25.5 35.4 L 30.0 35.5 ",
+ "L 33.1 39.7 C 33.1 39.7 30.2 41.7 27.5 41.6 Z\" ",
+ "stroke_width=\"3.5\" fill=\"", self._stroke,
+ ";\" stroke=\"none\" />\n")
+ svg += "%s%s%s%s%s%s" % (" <path d=\"M 18.5 33.8 ",
+ "C 17.6 30.9 18.6 27.0 18.6 27.0 L 22.6 29.1 L 24.1 33.8 ",
+ "L 20.5 38.0 C 20.5 38.0 19.1 36.0 18.4 33.8 Z\" ",
+ "stroke_width=\"3.5\" fill=\"", self._stroke,
+ ";\" stroke=\"none\" />\n")
+ svg += "%s%s%s%s%s%s" % (" <path d=\"M 19.5 25.1 ",
+ "C 19.5 25.1 20.0 23.2 22.5 21.3 ",
+ "C 24.7 19.7 27.0 19.6 27.0 19.6 L 26.9 24.6 L 23.4 27.3 ",
+ "L 19.5 25.1 Z\" stroke_width=\"3.5\" fill=\"", self._stroke,
+ ";\" stroke=\"none\" />\n")
+ svg += "%s%s%s%s%s%s" % (" <path d=\"M 32.1 27.8 L 28.6 25.0 ",
+ "L 29 19.8 C 29 19.8 30.8 19.7 33.0 21.4 ",
+ "C 35.2 23.2 36.3 26.4 36.3 26.4 L 32.1 27.8 Z\" ",
+ "stroke_width=\"3.5\" fill=\"", self._stroke,
+ ";\" stroke=\"none\" />\n")
+ svg += "%s%s%s%s%s%s" % (" <path d=\"M 31.3 34.0 L 32.6 29.6 ",
+ "L 36.8 28.0 C 36.8 28.0 37.5 30.7 36.8 33.7 ",
+ "C 36.2 36.0 34.7 38.1 34.7 38.1 L 31.3 34.0 Z\" ",
+ "stroke_width=\"3.5\" fill=\"", self._stroke,
+ ";\" stroke=\"none\" />\n")
+ self._width, self._height = 55, 55
+ svg += self.footer()
+ return self.header() + svg
+
+ def palette(self, width, height):
+ ''' Just a rectangle with a hide button. '''
+ self.reset_min_max()
+ self._width, self._height = width, height
+ self._fill, self._stroke = PALETTE_COLOR, "none"
+ svg = self._rect(width, height, 0, 0)
+ self._hide_x = (width - self._radius * 1.5) / 2
+ self._hide_y = (height - self._radius * 1.5) / 2
+ svg += self._hide_dot(True)
+ svg += self.footer()
+ return self.header() + svg
+
+ def toolbar(self, width, height):
+ ''' Just a rectangle '''
+ self.reset_min_max()
+ self._width, self._height = width, height
+ self._fill, self._stroke = TOOLBAR_COLOR, "none"
+ svg = self._rect(width, height, 0, 0)
+ svg += self.footer()
+ return self.header() + svg
+
+ def sandwich_top(self, innie_flag=True):
+ ''' Special block for the top of a collapsible stack; includes
+ an 'arm" that extends down the left side of a stack '''
+ self.reset_min_max()
+ x = self._stroke_width / 2.0
+ y = self._stroke_width / 2.0 + self._radius
+ self.margins[0] = int((x + self._stroke_width + 0.5) * self._scale)
+ self.margins[1] = int((self._stroke_width + 0.5) * self._scale)
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, y)
+ svg += self._corner(1, -1)
+ svg += self._rline_to(self._radius + self._stroke_width, 0)
+ svg += self._do_slot()
+ svg += self._rline_to(self._expand_x, 0)
+ xx = self._x
+ svg += self._corner(1, 1)
+ if innie_flag:
+ svg += self._do_innie()
+ svg += self._corner(-1, 1)
+ svg += self.line_to(xx, self._y)
+ svg += self._rline_to(-self._expand_x, 0)
+ svg += self._do_tab()
+ if self._arm:
+ svg += self._inverse_corner(-1, 1, 90, 0, 0)
+ svg += self._rline_to(0, self._expand_y)
+ svg += self._rline_to(-self._radius, 0)
+ else:
+ svg += self._rline_to(-self._radius - self._stroke_width, 0)
+ svg += self._corner(-1, -1)
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ if self._show is True:
+ svg += self._show_dot()
+ if self._hide is True:
+ svg += self._hide_dot()
+ svg += self.footer()
+ return self.header() + svg
+
+ def sandwich_bottom(self):
+ ''' Special block for the bottom of a collapsible stack;
+ includes a connection to the 'arm" that extends down the left
+ side of a stack '''
+ self.reset_min_max()
+ x = self._stroke_width / 2.0
+ y = self._stroke_width / 2.0
+ self.margins[0] = int((x + self._stroke_width + 0.5) * self._scale)
+ self.margins[1] = int((self._stroke_width + 0.5) * self._scale)
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, y)
+ svg += self._rline_to(self._radius, 0)
+ svg += self._rline_to(0, self._expand_y)
+ svg += self._inverse_corner(1, 1, 90, 0, 0)
+ svg += self._do_slot()
+ svg += self._rline_to(self._radius, 0)
+ svg += self._corner(-1, 1)
+ svg += self._do_tab()
+ svg += self._rline_to(-self._radius - self._stroke_width, 0)
+ svg += self._corner(-1, -1)
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ self._hide_x = x + self._radius / 2
+ self._hide_y = y + self._radius / 2
+ if self._hide is True:
+ svg += self._hide_dot()
+ if self._show is True:
+ svg += self._show_dot()
+ svg += self.footer()
+ return self.header() + svg
+
+ def status_block(self, graphic=None):
+ ''' Generate a status block '''
+ self.reset_min_max()
+ (x, y) = self._calculate_x_y()
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, y)
+ svg += self._corner(1, -1)
+ svg += self._rline_to(self._expand_x, 0)
+ xx = self._x
+ svg += self._corner(1, 1)
+ svg += self._rline_to(0, self._expand_y)
+ svg += self._corner(-1, 1)
+ svg += self.line_to(xx, self._y)
+ svg += self._rline_to(-self._expand_x, 0)
+ svg += self._corner(-1, -1)
+ svg += self._rline_to(0, -self._expand_y)
+ self.calc_w_h()
+ svg += self._close_path()
+ svg += self.style()
+ if self._hide is True:
+ svg += self._hide_dot()
+ svg += self.footer()
+ return self.header() + svg
+
+ #
+ # Utility methods
+ #
+ def set_tail(self, flag=True):
+ self._tail = flag
+
+ def set_draw_innies(self, flag=True):
+ self._draw_innies = flag
+
+ def set_hide(self, flag=False):
+ self._hide = flag
+
+ def set_show(self, flag=False):
+ self._show = flag
+
+ def get_width(self):
+ return self._width
+
+ def get_height(self):
+ return self._height
+
+ def get_innie_width(self):
+ return (self._innie_x1 + self._innie_x2) * self._scale
+
+ def get_slot_depth(self):
+ return self._slot_y * self._scale
+
+ def clear_docks(self):
+ self.docks = []
+
+ def set_scale(self, scale=1):
+ self._scale = scale
+
+ def set_orientation(self, orientation=0):
+ self._orientation = orientation
+
+ def expand(self, w=0, h=0):
+ self._expand_x = w
+ self._expand_y = h
+
+ def set_stroke_width(self, stroke_width=1.5):
+ self._stroke_width = stroke_width
+ self._calc_porch_params()
+
+ def set_colors(self, colors=["#00FF00", "#00A000"]):
+ self._fill = colors[0]
+ self._stroke = colors[1]
+
+ def set_fill_color(self, color="#00FF00"):
+ self._fill = color
+
+ def set_stroke_color(self, color="#00A000"):
+ self._stroke = color
+
+ def set_gradient(self, flag=False, color='#FFFFFF'):
+ self._gradient = flag
+ self._gradient_color = color
+
+ def set_innie(self, innie_array=[False]):
+ self._innie = innie_array
+
+ def set_outie(self, flag=False):
+ self._outie = flag
+
+ def set_slot(self, flag=True):
+ self._slot = flag
+ if self._slot is True:
+ self._cap = False
+
+ def set_cap(self, flag=False):
+ self._cap = flag
+ if self._cap is True:
+ self._slot = False
+
+ def set_tab(self, flag=True):
+ self._tab = flag
+
+ def set_porch(self, flag=False):
+ self._porch = flag
+
+ def set_boolean(self, flag=False):
+ self._bool = flag
+
+ def set_else(self, flag=False):
+ self._else = flag
+
+ def set_arm(self, flag=True):
+ self._arm = flag
+
+ def reset_min_max(self):
+ self._min_x = 10000
+ self._min_y = 10000
+ self._max_x = -10000
+ self._max_y = -10000
+
+ #
+ # Exotic methods
+ #
+
+ def set_radius(self, radius=8):
+ self._radius = radius
+
+ def set_innie_params(self, x1=4, y1=3, x2=4, y2=4):
+ self._innie_x1 = x1
+ self._innie_y1 = y1
+ self._innie_x2 = x2
+ self._innie_y2 = y2
+ self._calc_porch_params()
+
+ def set_innie_spacer(self, innie_spacer=0):
+ self._innie_spacer = innie_spacer
+
+ def set_slot_params(self, x=12, y=4):
+ self._slot_x = x
+ self._slot_y = y
+
+ def _calc_porch_params(self):
+ self._porch_x = self._innie_x1 + self._innie_x2 + \
+ 4 * self._stroke_width
+ self._porch_y = self._innie_y1 + self._innie_y2 + \
+ 4 * self._stroke_width
+
+ #
+ # SVG helper methods
+ #
+ def header(self, center=False):
+ return "%s%s%s%s%s%s%.1f%s%s%.1f%s%s%s" % (
+ "<svg\n",
+ " xmlns:svg=\"http://www.w3.org/2000/svg\"\n",
+ " xmlns=\"http://www.w3.org/2000/svg\"\n",
+ " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n",
+ " version=\"1.1\"\n",
+ " width=\"", self._width, "\"\n",
+ " height=\"", self._height, "\">\n",
+ self._defs(),
+ self._transform(center))
+
+ def _defs(self):
+ if self._gradient is True:
+ return "%s%s%s%s%s%s%s%s%s%s%s%s%.1f%s%s%.1f%s%s%.1f%s%s" % (
+ " <defs>\n <linearGradient\n id=\"linearGradient1234\">\n",
+ " <stop\n style=\"stop-color:" + \
+ self._gradient_color + ";stop-opacity:1;\"\n",
+ " offset=\"0\" />\n",
+ " <stop\n style=\"stop-color:", self._fill,
+ ";stop-opacity:1;\"\n",
+ " offset=\"1\" />\n",
+ " </linearGradient>\n",
+ " <linearGradient\n xlink:href=\"#linearGradient1234\"\n",
+ " id=\"linearGradient5678\"\n",
+ " x1=\"0\"\n",
+ " y1=\"", self._height / 2.0, "\"\n",
+ " x2=\"", self._width / self._scale, "\"\n",
+ " y2=\"", self._height / 2.0, "\"\n",
+ " gradientUnits=\"userSpaceOnUse\" />\n </defs>\n")
+ else:
+ return ""
+
+ def _transform(self, center):
+ if self._orientation != 0:
+ orientation = "<g\ntransform = \"rotate(%.1f %.1f %.1f)\">\n" % (
+ self._orientation, self._width / 2.0, self._height / 2.0)
+ else:
+ orientation = ""
+ if center:
+ return "<g\ntransform=\"translate(%.1f, %.1f)\">\n" % (
+ -self._min_x, -self._min_y)
+ else:
+ return "<g\ntransform=\"scale(%.1f, %.1f)\">\n%s" % (
+ self._scale, self._scale, orientation)
+
+ def footer(self):
+ if self._orientation != 0:
+ return " </g>\n</g>\n</svg>\n"
+ else:
+ return " </g>\n</svg>\n"
+
+ def style(self):
+ if self._gradient is True:
+ fill = "url(#linearGradient5678)"
+ else:
+ fill = self._fill
+ return "%s%s;%s%s;%s%.1f;%s%s" % (
+ " style=\"fill:", fill,
+ "fill-opacity:1;stroke:", self._stroke,
+ "stroke-width:", self._stroke_width,
+ "stroke-linecap:round;",
+ "stroke-opacity:1;\" />\n")
+
+ def text(self, x, y, size, width, string):
+ self._x = x
+ self._y = y
+ self._check_min_max()
+ self._x = x + width
+ self._y = y - size
+ self._check_min_max()
+ return " %s%.1f%s%s%s%.1f%s%.1f%s%.1f%s%s%s%s%s" % (
+ "<text style=\"font-size:", size, "px;fill:", self._stroke,
+ ";font-family:Sans\">\n <tspan x=\"", x, "\" y=\"", y,
+ "\" style=\"font-size:", size, "px;fill:", self._stroke, "\">",
+ string, "</tspan>\n </text>\n")
+
+ def image(self, x, y, w, h, path, image_data=None):
+ self._x = x
+ self._y = y
+ self._check_min_max()
+ self._x = x + w
+ self._y = y + h
+ self._check_min_max()
+ if image_data == None:
+ return " %s%.1f%s%.1f%s%.1f%s%.1f%s%s%s" % (
+ "<image x=\"", x, "\" y=\"", y,
+ "\" width=\"", w, "\" height=\"", h,
+ "\" xlink:href=\"file://", path, "\"/>\n")
+ else:
+ return " %s%.1f%s%.1f%s%.1f%s%.1f%s%s%s" % (
+ "<image x=\"", x, "\" y=\"", y,
+ "\" width=\"", w, "\" height=\"", h,
+ "\" xlink:href=\"data:image/png;base64,", image_data,
+ "\"/>\n")
+
+ def _circle(self, r, cx, cy):
+ return "%s%s%s%s%s%f%s%f%s%f%s" % ("<circle style=\"fill:",
+ self._fill, ";stroke:", self._stroke, ";\" r=\"", r, "\" cx=\"",
+ cx, "\" cy=\"", cy, "\" />\n")
+
+ def _rect(self, w, h, x, y):
+ return "%s%s%s%s%s%f%s%f%s%f%s%f%s" % ("<rect style=\"fill:",
+ self._fill, ";stroke:", self._stroke, ";\" width=\"", w,
+ "\" height=\"", h, "\" x=\"", x, "\" y=\"", y, "\" />\n")
+
+ def background(self, fill):
+ return "%s%s%s%s%s%f%s%f%s%f%s%f%s" % ("<rect style=\"fill:",
+ fill, ";stroke:", fill, ";\" width=\"",
+ self._max_x - self._min_x,
+ "\" height=\"", self._max_y - self._min_y, "\" x=\"",
+ self._min_x, "\" y=\"", self._min_y, "\" />\n")
+
+ def _check_min_max(self):
+ if self._x < self._min_x:
+ self._min_x = self._x
+ if self._y < self._min_y:
+ self._min_y = self._y
+ if self._x > self._max_x:
+ self._max_x = self._x
+ if self._y > self._max_y:
+ self._max_y = self._y
+
+ def line_to(self, x, y):
+ self._check_min_max()
+ if self._x == x and self._y == y:
+ return ""
+ else:
+ self._x = x
+ self._y = y
+ self._check_min_max()
+ return "L %.1f %.1f " % (x, y)
+
+ def _rline_to(self, dx, dy):
+ if dx == 0 and dy == 0:
+ return ""
+ else:
+ return self.line_to(self._x + dx, self._y + dy)
+
+ def arc_to(self, x, y, r, a=90, l=0, s=1):
+ self._check_min_max()
+ if r == 0:
+ return self.line_to(x, y)
+ else:
+ self._x = x
+ self._y = y
+ self._check_min_max()
+ return "A %.1f %.1f %.1f %d %d %.1f %.1f " % (
+ r, r, a, l, s, x, y)
+
+ def _rarc_to(self, sign_x, sign_y, a=90, l=0, s=1):
+ if self._radius == 0:
+ return ""
+ else:
+ x = self._x + sign_x * self._radius
+ y = self._y + sign_y * self._radius
+ return self.arc_to(x, y, self._radius, a, l, s)
+
+ def _inverse_corner(self, sign_x, sign_y, a=90, l=0, s=1, start=True,
+ end=True):
+ r2 = self._stroke_width + self._radius / 2.0
+ if start:
+ if sign_x * sign_y == -1:
+ svg_str = self._rline_to(sign_x * (r2 - self._stroke_width), 0)
+ else:
+ svg_str = self._rline_to(0, sign_y * (r2 - self._stroke_width))
+ x = self._x + sign_x * r2
+ y = self._y + sign_y * r2
+ svg_str += self.arc_to(x, y, r2, a, l, s)
+ if end:
+ if sign_x * sign_y == -1:
+ svg_str += self._rline_to(0,
+ sign_y * (r2 - self._stroke_width))
+ else:
+ svg_str += self._rline_to(sign_x * (r2 - self._stroke_width),
+ 0)
+ return svg_str
+
+ def _corner(self, sign_x, sign_y, a=90, l=0, s=1, start=True, end=True):
+ svg_str = ""
+ if sign_x == 1 and sign_y == -1:
+ self._hide_x = self._x + self._radius / 2 + self._stroke_width
+ self._hide_y = self._y - self._radius / 2 + self._stroke_width
+ self._show_x = self._x + self._radius / 2 + self._stroke_width
+ if sign_x == -1 and sign_y == 1:
+ self._show_y = self._y + self._radius / 2 - self._stroke_width
+ if self._radius > 0:
+ r2 = self._radius / 2.0
+ if start:
+ if sign_x * sign_y == 1:
+ svg_str += self._rline_to(sign_x * r2, 0)
+ else:
+ svg_str += self._rline_to(0, sign_y * r2)
+ x = self._x + sign_x * r2
+ y = self._y + sign_y * r2
+ svg_str += self.arc_to(x, y, r2, a, l, s)
+ if end:
+ if sign_x * sign_y == 1:
+ svg_str += self._rline_to(0, sign_y * r2)
+ else:
+ svg_str += self._rline_to(sign_x * r2, 0)
+ return svg_str
+
+ def new_path(self, x, y):
+ """
+ self._min_x = x
+ self._min_y = y
+ self._max_x = x
+ self._max_y = y
+ """
+ self._x = x
+ self._y = y
+ return " <path d=\"m%.1f %.1f " % (x, y)
+
+ def _close_path(self):
+ return "z\"\n"
+
+ def _hide_dot(self, noscale=False):
+ _saved_fill, _saved_stroke = self._fill, self._stroke
+ self._fill, self._stroke = HIT_RED, HIT_RED
+ svg = "</g>/n<g>/n"
+ if noscale:
+ scale = 2.0
+ else:
+ scale = self._scale
+ scale2 = scale / 2
+ svg += self._circle(self._dot_radius * scale2, self._hide_x * scale,
+ self._hide_y * scale)
+ self._fill, self._stroke = HIDE_WHITE, HIDE_WHITE
+ svg += self._rect(10 * scale2, 2 * scale2,
+ self._hide_x * scale - 5 * scale2,
+ self._hide_y * scale - scale + scale2)
+ self._fill, self._stroke = _saved_fill, _saved_stroke
+ return svg
+
+ def _show_dot(self, noscale=False):
+ _saved_fill, _saved_stroke = self._fill, self._stroke
+ self._fill, self._stroke = HIT_GREEN, HIT_GREEN
+ svg = "</g>/n<g>/n"
+ if noscale:
+ scale = 2.0
+ else:
+ scale = self._scale
+ scale2 = scale / 2
+ svg += self._circle(self._dot_radius * scale2, self._show_x * scale,
+ self._show_y * scale)
+ self._fill, self._stroke = SHOW_WHITE, SHOW_WHITE
+ svg += self._rect(10 * scale2,
+ 2 * scale2, self._show_x * scale - 5 * scale2,
+ self._show_y * scale - scale + scale2)
+ svg += self._rect(2 * scale2, 10 * scale2,
+ self._show_x * scale - scale + scale2,
+ self._show_y * scale - 5 * scale2)
+ self._fill, self._stroke = _saved_fill, _saved_stroke
+ return svg
+
+ def _do_slot(self):
+ if self._slot is True:
+ self.docks.append((int(self._x * self._scale),
+ int(self._y * self._scale)))
+ return "%s%s%s" % (
+ self._rline_to(0, self._slot_y),
+ self._rline_to(self._slot_x, 0),
+ self._rline_to(0, -self._slot_y))
+ elif self._cap is True:
+ return "%s%s" % (
+ self._rline_to(self._slot_x / 2.0, -self._slot_y * 2.0),
+ self._rline_to(self._slot_x / 2.0, self._slot_y * 2.0))
+ else:
+ return self._rline_to(self._slot_x, 0)
+
+ def _do_tail(self):
+ if self._outie is True:
+ return self._rline_to(-self._slot_x, 0)
+ elif self._tail:
+ return "%s%s" % (
+ self._rline_to(-self._slot_x / 2.0, self._slot_y * 2.0),
+ self._rline_to(-self._slot_x / 2.0, -self._slot_y * 2.0))
+ else:
+ return self._rline_to(-self._slot_x, 0)
+
+ def _do_tab(self):
+ s = "%s%s%s%s%s" % (
+ self._rline_to(-self._stroke_width, 0),
+ self._rline_to(0, self._slot_y),
+ self._rline_to(-self._slot_x + 2 * self._stroke_width, 0),
+ self._rline_to(0, -self._slot_y),
+ self._rline_to(-self._stroke_width, 0))
+ self.docks.append((int(self._x * self._scale),
+ int((self._y + self._stroke_width) * self._scale)))
+ return s
+
+ def _do_innie(self):
+ self.docks.append((int((self._x + self._stroke_width) * self._scale),
+ int((self._y + self._innie_y2) * self._scale)))
+ if self.margins[2] == 0:
+ self.margins[1] = int((self._y - self._innie_y1) * self._scale)
+ self.margins[2] = int((self._x - self._innie_x1 - \
+ self._innie_x2 - self._stroke_width * 2) * self._scale)
+ self.margins[3] =\
+ int((self._y + self._innie_y2 + self._innie_y1) * self._scale)
+ return "%s%s%s%s%s%s%s" % (
+ self._rline_to(-self._innie_x1, 0),
+ self._rline_to(0, -self._innie_y1),
+ self._rline_to(-self._innie_x2, 0),
+ self._rline_to(0, self._innie_y2 + 2 * self._innie_y1),
+ self._rline_to(self._innie_x2, 0),
+ self._rline_to(0, -self._innie_y1),
+ self._rline_to(self._innie_x1, 0))
+
+ def _do_reverse_innie(self):
+ self.docks.append((int((self._x + self._stroke_width) * self._scale),
+ int((self._y) * self._scale)))
+ return "%s%s%s%s%s%s%s" % (
+ self._rline_to(-self._innie_x1, 0),
+ self._rline_to(0, self._innie_y1),
+ self._rline_to(-self._innie_x2, 0),
+ self._rline_to(0, -self._innie_y2 - 2 * self._innie_y1),
+ self._rline_to(self._innie_x2, 0),
+ self._rline_to(0, self._innie_y1),
+ self._rline_to(self._innie_x1, 0))
+
+ def _do_outie(self):
+ if self._outie is not True:
+ return self._rline_to(0, -self._innie_y2)
+ self.docks.append((int(self._x * self._scale),
+ int(self._y * self._scale)))
+ return "%s%s%s%s%s%s%s%s%s" % (
+ self._rline_to(0, -self._stroke_width),
+ self._rline_to(-self._innie_x1 - 2 * self._stroke_width, 0),
+ self._rline_to(0, self._innie_y1),
+ self._rline_to(-self._innie_x2 + 2 * self._stroke_width, 0),
+ self._rline_to(0,
+ -self._innie_y2 - 2 * self._innie_y1 + 2 * self._stroke_width),
+ self._rline_to(self._innie_x2 - 2 * self._stroke_width, 0),
+ self._rline_to(0, self._innie_y1),
+ self._rline_to(self._innie_x1 + 2 * self._stroke_width, 0),
+ self._rline_to(0, -self._stroke_width))
+
+ def _do_porch(self, flag=True):
+ if flag:
+ return "%s%s%s" % (
+ self._rline_to(0, self._porch_y + self._innie_y1),
+ self._rline_to(self._porch_x - self._radius, 0),
+ self._corner(1, 1))
+ else:
+ return "%s%s%s" % (
+ self._rline_to(0, self._porch_y - self._innie_y1 + \
+ self._stroke_width),
+ self._rline_to(self._porch_x - self._radius, 0),
+ self._corner(1, 1))
+
+ def _start_boolean(self, xoffset, yoffset):
+ svg = self.new_path(xoffset, yoffset)
+ self._radius -= self._stroke_width
+ self.docks.append((int(self._x * self._scale),
+ int(self._y * self._scale)))
+ svg += self._rarc_to(1, -1)
+ self._radius += self._stroke_width
+ svg += self._rline_to(self._stroke_width, 0)
+ svg += self._rline_to(0, -self._expand_y)
+ return svg
+
+ def _do_boolean(self):
+ self.docks.append(
+ (int((self._x - self._radius + self._stroke_width) * self._scale),
+ int((self._y + self._radius) * self._scale)))
+ self.margins[2] =\
+ int((self._x - self._radius - self._stroke_width) * self._scale)
+ svg = self._rarc_to(-1, 1, 90, 0, 0) + self._rarc_to(1, 1, 90, 0, 0)
+ return svg
+
+ def _end_boolean(self):
+ svg = self._rline_to(-self._radius * 1.5, 0)
+ svg += self._rline_to(0, -self._stroke_width)
+ svg += self._rline_to(-self._stroke_width, 0)
+ self._radius -= self._stroke_width
+ svg += self._rarc_to(-1, -1)
+ self._radius += self._stroke_width
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ if self._show is True:
+ svg += self._show_dot()
+ if self._hide is True:
+ svg += self._hide_dot()
+ return svg + self.footer()
+
+ def calc_w_h(self, add_stroke_width=True):
+ if add_stroke_width:
+ self._width = (self._max_x - self._min_x + self._stroke_width) * \
+ self._scale
+ else:
+ self._width = (self._max_x - self._min_x) * self._scale
+ if self.margins[2] == 0:
+ self.margins[2] = int((self._stroke_width + 0.5) * self._scale)
+ else:
+ self.margins[2] = int(self._width - self.margins[2])
+ if add_stroke_width:
+ self._height = (self._max_y - self._min_y + self._stroke_width) * \
+ self._scale
+ else:
+ self._height = (self._max_y - self._min_y) * self._scale
+ if self.margins[3] == 0:
+ if self._tab:
+ self.margins[3] = int((self._slot_y + self._stroke_width + \
+ 0.5) * self._scale)
+ else:
+ self.margins[3] = int((self._slot_y * 2 + \
+ self._stroke_width + 0.5) * self._scale)
+ else:
+ self.margins[3] = int(self._height - self.margins[3])
+
+ def _calculate_x_y(self):
+ x = self._stroke_width / 2.0
+ y = self._stroke_width / 2.0 + self._radius
+ self.margins[0] = int(x + self._stroke_width + 0.5)
+ self.margins[1] = int(self._stroke_width + 0.5)
+ if self._outie is True:
+ x += self._innie_x1 + self._innie_x2
+ self.margins[0] += self._innie_x1 + self._innie_x2
+ if self._cap is True:
+ y += self._slot_y * 2.0
+ self.margins[1] += self._slot_y * 2.0
+ elif self._slot is True:
+ self.margins[1] += self._slot_y
+ self.margins[0] *= self._scale
+ self.margins[1] *= self._scale
+ return(x, y)
+
+#
+# Command-line tools for testing
+#
+
+
+def open_file(datapath, filename):
+ return file(os.path.join(datapath, filename), "w")
+
+
+def close_file(f):
+ f.close()
+
+
+def generator(datapath):
+ svg0 = SVG()
+ f = open_file(datapath, "basic.svg")
+ svg0.set_innie([True, True])
+ svg0.set_scale(2)
+ svg0.set_tab(True)
+ svg0.set_slot(True)
+ svg0.set_arm(True)
+ svg_str = svg0.basic_block()
+ f.write(svg_str)
+ close_file(f)
+
+
+def main():
+ return 0
+
+
+if __name__ == "__main__":
+ generator(os.path.abspath('.'))
+ main()
+
+
+def svg_str_to_pixbuf(svg_string):
+ """ Load pixbuf from SVG string """
+ pl = gtk.gdk.PixbufLoader('svg')
+ pl.write(svg_string)
+ pl.close()
+ pixbuf = pl.get_pixbuf()
+ return pixbuf
+
+
+def svg_str_to_pixmap(svg_string):
+ """ Load pixmap from SVG string """
+ (pixmap, mask) = svg_str_to_pixbuf(svg_string).render_pixmap_and_mask()
+ return pixmap
+
+
+def svg_from_file(pathname):
+ """ Read SVG string from a file """
+ f = file(pathname, 'r')
+ svg = f.read()
+ f.close()
+ return(svg)
diff --git a/TurtleArt/taturtle.py b/TurtleArt/taturtle.py
new file mode 100644
index 0000000..c70f379
--- /dev/null
+++ b/TurtleArt/taturtle.py
@@ -0,0 +1,312 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2010,12 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.
+
+from random import uniform
+from math import sin, cos, pi, sqrt
+from gettext import gettext as _
+import gtk
+import cairo
+
+from taconstants import TURTLE_LAYER, DEFAULT_TURTLE_COLORS
+from tasprite_factory import SVG, svg_str_to_pixbuf
+from tacanvas import wrap100, COLOR_TABLE
+from sprites import Sprite
+from tautils import debug_output
+
+SHAPES = 36
+
+
+def generate_turtle_pixbufs(colors):
+ """ Generate pixbufs for generic turtles """
+ shapes = []
+ svg = SVG()
+ svg.set_scale(1.0)
+ for i in range(SHAPES):
+ svg.set_orientation(i * 10)
+ shapes.append(svg_str_to_pixbuf(svg.turtle(colors)))
+ return shapes
+
+
+class Turtles:
+
+ def __init__(self, sprite_list):
+ """ Class to hold turtles """
+ self.dict = dict()
+ self.sprite_list = sprite_list
+ self.default_pixbufs = []
+
+ def get_turtle(self, k, append=False, colors=None):
+ """ Find a turtle """
+ if k in self.dict:
+ return self.dict[k]
+ elif not append:
+ return None
+ else:
+ if colors == None:
+ Turtle(self, k)
+ elif type(colors) in [list, tuple]:
+ Turtle(self, k, colors)
+ else:
+ Turtle(self, k, colors.split(','))
+ return self.dict[k]
+
+ def get_turtle_key(self, turtle):
+ """ Find a turtle's name """
+ for k in iter(self.dict):
+ if self.dict[k] == turtle:
+ return k
+ return None
+
+ def turtle_count(self):
+ """ How many turtles are there? """
+ return(len(self.dict))
+
+ def add_to_dict(self, k, turtle):
+ """ Add a new turtle """
+ self.dict[k] = turtle
+
+ def remove_from_dict(self, k):
+ """ Delete a turtle """
+ if k in self.dict:
+ del(self.dict[k])
+
+ def show_all(self):
+ """ Make all turtles visible """
+ for k in iter(self.dict):
+ self.dict[k].show()
+
+ def spr_to_turtle(self, spr):
+ """ Find the turtle that corresponds to sprite spr. """
+ for k in iter(self.dict):
+ if spr == self.dict[k].spr:
+ return self.dict[k]
+ return None
+
+ def get_pixbufs(self):
+ """ Get the pixbufs for the default turtle shapes. """
+ if self.default_pixbufs == []:
+ self.default_pixbufs = generate_turtle_pixbufs(
+ ["#008000", "#00A000"])
+ return(self.default_pixbufs)
+
+
+class Turtle:
+
+ def __init__(self, turtles, key, turtle_colors=None):
+ """ The turtle is not a block, just a sprite with an orientation """
+ self.x = 0
+ self.y = 0
+ self.hidden = False
+ self.shapes = []
+ self.custom_shapes = False
+ self.type = 'turtle'
+ self.name = key
+ self.heading = 0
+ self.pen_shade = 50
+ self.pen_color = 0
+ self.pen_gray = 100
+ self.pen_size = 5
+ self.pen_state = True
+ self.label_block = None
+
+ self._prep_shapes(key, turtles, turtle_colors)
+
+ # Choose a random angle from which to attach the turtle label.
+ if turtles.sprite_list is not None:
+ self.spr = Sprite(turtles.sprite_list, 0, 0, self.shapes[0])
+ angle = uniform(0, pi * 4 / 3.0) # 240 degrees
+ w = self.shapes[0].get_width()
+ r = w * 0.67
+ # Restrict angle the the sides 30-150; 210-330
+ if angle > pi * 2 / 3.0:
+ angle += pi / 2.0 # + 90
+ self.label_xy = [int(r * sin(angle)),
+ int(r * cos(angle) + w / 2.0)]
+ else:
+ angle += pi / 6.0 # + 30
+ self.label_xy = [int(r * sin(angle) + w / 2.0),
+ int(r * cos(angle) + w / 2.0)]
+ else:
+ self.spr = None
+ turtles.add_to_dict(key, self)
+
+ def _prep_shapes(self, name, turtles=None, turtle_colors=None):
+ # If the turtle name is an int, we'll use a palette color as the
+ # turtle color
+ try:
+ int_key = int(name)
+ use_color_table = True
+ except ValueError:
+ use_color_table = False
+
+ if turtle_colors is not None:
+ self.colors = turtle_colors[:]
+ self.shapes = generate_turtle_pixbufs(self.colors)
+ elif use_color_table:
+ fill = wrap100(int_key)
+ stroke = wrap100(fill + 10)
+ self.colors = ['#%06x' % (COLOR_TABLE[fill]),
+ '#%06x' % (COLOR_TABLE[stroke])]
+ self.shapes = generate_turtle_pixbufs(self.colors)
+ else:
+ if turtles is not None:
+ self.colors = DEFAULT_TURTLE_COLORS
+ self.shapes = turtles.get_pixbufs()
+
+ def set_turtle_colors(self, turtle_colors):
+ ''' reset the colors of a preloaded turtle '''
+ if turtle_colors is not None:
+ self.colors = turtle_colors[:]
+ self.shapes = generate_turtle_pixbufs(self.colors)
+ self.set_heading(self.heading)
+
+ def set_shapes(self, shapes, i=0):
+ """ Reskin the turtle """
+ n = len(shapes)
+ if n == 1 and i > 0: # set shape[i]
+ if i < len(self.shapes):
+ self.shapes[i] = shapes[0]
+ elif n == SHAPES: # all shapes have been precomputed
+ self.shapes = shapes[:]
+ else: # rotate shapes
+ if n != 1:
+ debug_output("%d images passed to set_shapes: ignoring" % (n),
+ self.tw.running_sugar)
+ if self.heading == 0: # rotate the shapes
+ images = []
+ w, h = shapes[0].get_width(), shapes[0].get_height()
+ nw = nh = int(sqrt(w * w + h * h))
+ for i in range(SHAPES):
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, nw, nh)
+ context = cairo.Context(surface)
+ context = gtk.gdk.CairoContext(context)
+ context.translate(nw / 2., nh / 2.)
+ context.rotate(i * 10 * pi / 180.)
+ context.translate(-nw / 2., -nh / 2.)
+ context.set_source_pixbuf(shapes[0], (nw - w) / 2.,
+ (nh - h) / 2.)
+ context.rectangle(0, 0, nw, nh)
+ context.fill()
+ images.append(surface)
+ self.shapes = images[:]
+ else: # associate shape with image at current heading
+ j = int(self.heading + 5) % 360 / (360 / SHAPES)
+ self.shapes[j] = shapes[0]
+ self.custom_shapes = True
+ self.show()
+
+ def reset_shapes(self):
+ """ Reset the shapes to the standard turtle """
+ if self.custom_shapes:
+ self.shapes = generate_turtle_pixbufs(self.colors)
+ self.custom_shapes = False
+
+ def set_heading(self, heading):
+ """ Set the turtle heading (one shape per 360/SHAPES degrees) """
+ self.heading = heading
+ i = (int(self.heading + 5) % 360) / (360 / SHAPES)
+ if not self.hidden and self.spr is not None:
+ try:
+ self.spr.set_shape(self.shapes[i])
+ except IndexError:
+ self.spr.set_shape(self.shapes[0])
+
+ def set_color(self, color):
+ """ Set the pen color for this turtle. """
+ self.pen_color = color
+
+ def set_gray(self, gray):
+ """ Set the pen gray level for this turtle. """
+ self.pen_gray = gray
+
+ def set_shade(self, shade):
+ """ Set the pen shade for this turtle. """
+ self.pen_shade = shade
+
+ def set_pen_size(self, pen_size):
+ """ Set the pen size for this turtle. """
+ self.pen_size = pen_size
+
+ def set_pen_state(self, pen_state):
+ """ Set the pen state (down==True) for this turtle. """
+ self.pen_state = pen_state
+
+ def hide(self):
+ """ Hide the turtle. """
+ if self.spr is not None:
+ self.spr.hide()
+ if self.label_block is not None:
+ self.label_block.spr.hide()
+ self.hidden = True
+
+ def show(self):
+ """ Show the turtle. """
+ if self.spr is not None:
+ self.spr.set_layer(TURTLE_LAYER)
+ self.hidden = False
+ self.move((self.x, self.y))
+ self.set_heading(self.heading)
+ if self.label_block is not None:
+ self.label_block.spr.move((self.x + self.label_xy[0],
+ self.y + self.label_xy[1]))
+ self.label_block.spr.set_layer(TURTLE_LAYER + 1)
+
+ def move(self, pos):
+ """ Move the turtle. """
+ self.x, self.y = int(pos[0]), int(pos[1])
+ if not self.hidden and self.spr is not None:
+ self.spr.move(pos)
+ if self.label_block is not None:
+ self.label_block.spr.move((pos[0] + self.label_xy[0],
+ pos[1] + self.label_xy[1]))
+ return(self.x, self.y)
+
+ def get_name(self):
+ ''' return turtle name (key) '''
+ return self.name
+
+ def get_xy(self):
+ """ Return the turtle's x, y coordinates. """
+ return(self.x, self.y)
+
+ def get_heading(self):
+ """ Return the turtle's heading. """
+ return(self.heading)
+
+ def get_color(self):
+ """ Return the turtle's color. """
+ return(self.pen_color)
+
+ def get_gray(self):
+ """ Return the turtle's gray level. """
+ return(self.pen_gray)
+
+ def get_shade(self):
+ """ Return the turtle's shade. """
+ return(self.pen_shade)
+
+ def get_pen_size(self):
+ """ Return the turtle's pen size. """
+ return(self.pen_size)
+
+ def get_pen_state(self):
+ """ Return the turtle's pen state. """
+ return(self.pen_state)
diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py
new file mode 100644
index 0000000..40c39e9
--- /dev/null
+++ b/TurtleArt/tautils.py
@@ -0,0 +1,832 @@
+#Copyright (c) 2007-8, Playful Invention Company.
+#Copyright (c) 2008-12, 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.
+
+import gtk
+import cairo
+import pickle
+import subprocess
+import os
+from gettext import gettext as _
+
+try:
+ OLD_SUGAR_SYSTEM = False
+ import json
+ json.dumps
+ from json import load as jload
+ from json import dump as jdump
+except (ImportError, AttributeError):
+ try:
+ import simplejson as json
+ from simplejson import load as jload
+ from simplejson import dump as jdump
+ except:
+ OLD_SUGAR_SYSTEM = True
+from StringIO import StringIO
+
+from taconstants import COLLAPSIBLE, HIT_HIDE, HIT_SHOW, XO1, XO15, XO175, \
+ XO30, UNKNOWN
+
+SANDWICHES = ['sandwichtop', 'sandwichtop_no_label', 'sandwichtop_no_arm',
+ 'sandwichtop_no_arm_no_label']
+
+import logging
+_logger = logging.getLogger('turtleart-activity')
+
+
+def debug_output(message_string, running_sugar=False):
+ ''' unified debugging output '''
+ if running_sugar:
+ _logger.debug(message_string)
+ else:
+ print(message_string)
+
+
+def error_output(message_string, running_sugar=False):
+ ''' unified debugging output '''
+ if running_sugar:
+ _logger.error(message_string)
+ else:
+ print(message_string)
+
+
+class pythonerror(Exception):
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+def convert(x, fn, try_ord=True):
+ '''
+ The strategy for mixing numbers and strings is to first try
+ converting the string to a float; then if the string is a single
+ character, try converting it to an ord; finally, just treat it as a
+ string. Numbers appended to strings are first treated as ints, then
+ floats.
+ '''
+ try:
+ return fn(x)
+ except ValueError:
+ if try_ord:
+ xx, flag = chr_to_ord(x)
+ if flag:
+ return fn(xx)
+ return x
+
+
+def chr_to_ord(x):
+ ''' Try to comvert a string to an ord '''
+ if strtype(x) and len(x) == 1:
+ try:
+ return ord(x[0]), True
+ except ValueError:
+ return x, False
+ return x, False
+
+
+def strtype(x):
+ ''' Is x a string type? '''
+ if type(x) == str:
+ return True
+ if type(x) == unicode:
+ return True
+ return False
+
+
+def magnitude(pos):
+ ''' Calculate the magnitude of the distance between to blocks. '''
+ x, y = pos
+ return x * x + y * y
+
+
+def json_load(text):
+ ''' Load JSON data using what ever resources are available. '''
+ if OLD_SUGAR_SYSTEM is True:
+ listdata = json.read(text)
+ else:
+ # Strip out leading and trailing whitespace, nulls, and newlines
+ clean_text = text.lstrip()
+ clean_text = clean_text.replace('\12', '')
+ clean_text = clean_text.replace('\00', '')
+ clean_text = clean_text.rstrip()
+ # Look for missing ']'s
+ left_count = clean_text.count('[')
+ right_count = clean_text.count(']')
+ while left_count > right_count:
+ clean_text += ']'
+ right_count = clean_text.count(']')
+ io = StringIO(clean_text)
+ try:
+ listdata = jload(io)
+ except ValueError:
+ # Assume that text is ascii list
+ listdata = text.split()
+ for i, value in enumerate(listdata):
+ listdata[i] = convert(value, float)
+ # json converts tuples to lists, so we need to convert back,
+ return _tuplify(listdata)
+
+
+def _tuplify(tup):
+ ''' Convert to tuples '''
+ if type(tup) is not list:
+ return tup
+ return tuple(map(_tuplify, tup))
+
+
+def get_id(connection):
+ ''' Get a connection block ID. '''
+ if connection is not None and hasattr(connection, 'id'):
+ return connection.id
+ return None
+
+
+def json_dump(data):
+ ''' Save data using available JSON tools. '''
+ if OLD_SUGAR_SYSTEM is True:
+ return json.write(data)
+ else:
+ io = StringIO()
+ jdump(data, io)
+ return io.getvalue()
+
+
+def get_load_name(suffix, load_save_folder):
+ ''' Open a load file dialog. '''
+ dialog = gtk.FileChooserDialog(
+ _('Load...'), None,
+ gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN, gtk.RESPONSE_OK))
+ dialog.set_default_response(gtk.RESPONSE_OK)
+ return do_dialog(dialog, suffix, load_save_folder)
+
+
+def get_save_name(suffix, load_save_folder, save_file_name):
+ ''' Open a save file dialog. '''
+ dialog = gtk.FileChooserDialog(
+ _('Save...'), None,
+ gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+ dialog.set_default_response(gtk.RESPONSE_OK)
+ if save_file_name is not None:
+ dialog.set_current_name(save_file_name + suffix)
+ return do_dialog(dialog, suffix, load_save_folder)
+
+
+def chooser(parent_window, filter, action):
+ ''' Choose an object from the datastore and take some action '''
+ from sugar.graphics.objectchooser import ObjectChooser
+
+ chooser = None
+ try:
+ chooser = ObjectChooser(parent=parent_window, what_filter=filter)
+ except TypeError:
+ chooser = ObjectChooser(None, parent_window,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
+ if chooser is not None:
+ try:
+ result = chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ dsobject = chooser.get_selected_object()
+ action(dsobject)
+ dsobject.destroy()
+ finally:
+ chooser.destroy()
+ del chooser
+
+
+def data_from_file(ta_file):
+ ''' Open the .ta file, ignoring any .png file that might be present. '''
+ file_handle = open(ta_file, 'r')
+ #
+ # We try to maintain read-compatibility with all versions of Turtle Art.
+ # Try pickle first; then different versions of json.
+ #
+ try:
+ data = pickle.load(file_handle)
+ except:
+ # Rewind necessary because of failed pickle.load attempt
+ file_handle.seek(0)
+ text = file_handle.read()
+ data = data_from_string(text)
+ file_handle.close()
+ return data
+
+
+def data_from_string(text):
+ ''' JSON load data from a string. '''
+ return json_load(text.replace(']],\n', ']], '))
+
+
+def data_to_file(data, ta_file):
+ ''' Write data to a file. '''
+ file_handle = file(ta_file, 'w')
+ file_handle.write(data_to_string(data))
+ file_handle.close()
+
+
+def data_to_string(data):
+ ''' JSON dump a string. '''
+ return json_dump(data).replace(']], ', ']],\n')
+
+
+def do_dialog(dialog, suffix, load_save_folder):
+ ''' Open a file dialog. '''
+ result = None
+ file_filter = gtk.FileFilter()
+ file_filter.add_pattern('*' + suffix)
+ file_filter.set_name('Turtle Art')
+ dialog.add_filter(file_filter)
+ dialog.set_current_folder(load_save_folder)
+ response = dialog.run()
+ if response == gtk.RESPONSE_OK:
+ result = dialog.get_filename()
+ load_save_folder = dialog.get_current_folder()
+ dialog.destroy()
+ return result, load_save_folder
+
+
+def save_picture(canvas, file_name):
+ ''' Save the canvas to a file '''
+ x_surface = canvas.canvas.get_target()
+ img_surface = cairo.ImageSurface(cairo.FORMAT_RGB24,
+ canvas.width, canvas.height)
+ cr = cairo.Context(img_surface)
+ cr.set_source_surface(x_surface)
+ cr.paint()
+ if type(file_name) == unicode:
+ img_surface.write_to_png(str(file_name.encode('ascii', 'replace')))
+ else:
+ img_surface.write_to_png(str(file_name))
+
+
+def get_canvas_data(canvas):
+ ''' Get pixel data from the turtle canvas '''
+ x_surface = canvas.canvas.get_target()
+ img_surface = cairo.ImageSurface(cairo.FORMAT_RGB24,
+ canvas.width, canvas.height)
+ cr = cairo.Context(img_surface)
+ cr.set_source_surface(x_surface)
+ cr.paint()
+ return img_surface.get_data()
+
+
+def get_pixbuf_from_journal(dsobject, w, h):
+ ''' Load a pixbuf from a Journal object. '''
+ try:
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(dsobject.file_path,
+ int(w), int(h))
+ except:
+ try:
+ pixbufloader = \
+ gtk.gdk.pixbuf_loader_new_with_mime_type('image/png')
+ pixbufloader.set_size(min(300, int(w)), min(225, int(h)))
+ pixbufloader.write(dsobject.metadata['preview'])
+ pixbufloader.close()
+ pixbuf = pixbufloader.get_pixbuf()
+ except:
+ pixbuf = None
+ return pixbuf
+
+
+def get_path(activity, subpath):
+ ''' Find a Rainbow-approved place for temporary files. '''
+ try:
+ return(os.path.join(activity.get_activity_root(), subpath))
+ except:
+ # Early versions of Sugar didn't support get_activity_root()
+ return(os.path.join(os.environ['HOME'], '.sugar/default',
+ 'org.laptop.TurtleArtActivity', subpath))
+
+
+def image_to_base64(image_path, tmp_path):
+ ''' Convert an image to base64-encoded data '''
+ base64 = os.path.join(tmp_path, 'base64tmp')
+ cmd = 'base64 <' + image_path + ' >' + base64
+ subprocess.check_call(cmd, shell=True)
+ file_handle = open(base64, 'r')
+ data = file_handle.read()
+ file_handle.close()
+ os.remove(base64)
+ return data
+
+
+def base64_to_image(data, path_name):
+ ''' Convert base64-encoded data to an image '''
+ base64 = os.path.join(path_name, 'base64tmp')
+ file_handle = open(base64, 'w')
+ file_handle.write(data)
+ file_handle.close()
+ file_name = os.path.join(path_name, 'imagetmp.png')
+ cmd = 'base64 -d <' + base64 + '>' + file_name
+ subprocess.check_call(cmd, shell=True)
+ return file_name
+
+
+def movie_media_type(name):
+ ''' Is it movie media? '''
+ return name.lower().endswith(('.ogv', '.vob', '.mp4', '.wmv', '.mov',
+ '.mpeg', '.ogg', '.webm'))
+
+
+def audio_media_type(name):
+ ''' Is it audio media? '''
+ return name.lower().endswith(('.oga', '.m4a'))
+
+
+def image_media_type(name):
+ ''' Is it image media? '''
+ return name.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.tiff',
+ '.tif', '.svg'))
+
+
+def text_media_type(name):
+ ''' Is it text media? '''
+ return name.lower().endswith(('.txt', '.py', '.lg', '.rtf'))
+
+
+def round_int(num):
+ ''' Remove trailing decimal places if number is an int '''
+ try:
+ float(num)
+ except TypeError:
+ raise pythonerror('#syntaxerror')
+
+ if int(float(num)) == num:
+ return int(num)
+ else:
+ if float(num) < 0:
+ nn = int((float(num) - 0.005) * 100) / 100.
+ else:
+ nn = int((float(num) + 0.005) * 100) / 100.
+ if int(float(nn)) == nn:
+ return int(nn)
+ return nn
+
+
+def calc_image_size(spr):
+ ''' Calculate the maximum size for placing an image onto a sprite. '''
+ return int(max(spr.label_safe_width(), 1)), \
+ int(max(spr.label_safe_height(), 1))
+
+
+# Collapsible stacks live between 'sandwichtop' and 'sandwichbottom' blocks
+
+
+def reset_stack_arm(top):
+ ''' When we undock, retract the 'arm' that extends from 'sandwichtop'. '''
+ if top is not None and top.name in ['sandwichtop', 'sandwichtop_no_label']:
+ if top.ey > 0:
+ top.reset_y()
+ top.svg.set_hide(False)
+ top.refresh()
+
+
+def grow_stack_arm(top):
+ ''' When we dock, grow an 'arm' from 'sandwichtop'. '''
+ if top is not None and top.name in ['sandwichtop', 'sandwichtop_no_label']:
+ bot = find_sandwich_bottom(top)
+ if bot is None:
+ return
+ if top.ey > 0:
+ top.reset_y()
+ ty = top.spr.get_xy()[1]
+ th = top.spr.get_dimensions()[1]
+ by = bot.spr.get_xy()[1]
+ dy = by - (ty + th)
+ if dy > 0:
+ top.expand_in_y(dy / top.scale)
+ top.svg.set_hide(True)
+ top.refresh()
+
+
+def find_sandwich_top(blk):
+ ''' Find the sandwich top above this block. '''
+ # Always follow the main branch of a flow: the first connection.
+ cblk = blk.connections[0]
+ while cblk is not None:
+ if cblk.name in COLLAPSIBLE:
+ return None
+ if cblk.name in ['repeat', 'if', 'ifelse', 'forever', 'while']:
+ if blk != cblk.connections[len(cblk.connections) - 1]:
+ return None
+ if cblk.name in SANDWICHES:
+ return cblk
+ blk = cblk
+ cblk = cblk.connections[0]
+ return None
+
+
+def find_sandwich_bottom(blk):
+ ''' Find the sandwich bottom below this block. '''
+ # Always follow the main branch of a flow: the last connection.
+ cblk = blk.connections[len(blk.connections) - 1]
+ while cblk is not None:
+ if cblk.name in SANDWICHES:
+ return None
+ if cblk.name in COLLAPSIBLE:
+ return cblk
+ cblk = cblk.connections[len(cblk.connections) - 1]
+ return None
+
+
+def find_sandwich_top_below(blk):
+ ''' Find the sandwich top below this block. '''
+ if blk.name in SANDWICHES:
+ return blk
+ # Always follow the main branch of a flow: the last connection.
+ cblk = blk.connections[len(blk.connections) - 1]
+ while cblk is not None:
+ if cblk.name in SANDWICHES:
+ return cblk
+ cblk = cblk.connections[len(cblk.connections) - 1]
+ return None
+
+
+def restore_stack(top):
+ ''' Restore the blocks between the sandwich top and sandwich bottom. '''
+ group = find_group(top.connections[len(top.connections) - 1])
+ hit_bottom = False
+ bot = find_sandwich_bottom(top)
+ for gblk in group:
+ if not hit_bottom and gblk == bot:
+ hit_bottom = True
+ if len(gblk.values) == 0:
+ gblk.values.append(0)
+ else:
+ gblk.values[0] = 0
+ olddx = gblk.docks[1][2]
+ olddy = gblk.docks[1][3]
+ # Replace 'sandwichcollapsed' shape with 'sandwichbottom' shape
+ gblk.name = 'sandwichbottom'
+ gblk.spr.set_label(' ', 1)
+ gblk.svg.set_show(False)
+ gblk.svg.set_hide(True)
+ gblk.refresh()
+ # Redock to previous block in group
+ you = gblk.connections[0]
+ (yx, yy) = you.spr.get_xy()
+ yd = you.docks[len(you.docks) - 1]
+ (bx, by) = gblk.spr.get_xy()
+ dx = yx + yd[2] - gblk.docks[0][2] - bx
+ dy = yy + yd[3] - gblk.docks[0][3] - by
+ gblk.spr.move_relative((dx, dy))
+ # Since the shapes have changed, the dock positions have too.
+ dx += gblk.docks[1][2] - olddx
+ dy += gblk.docks[1][3] - olddy
+ else:
+ if not hit_bottom:
+ gblk.spr.restore()
+ gblk.status = None
+ else:
+ gblk.spr.move_relative((dx, dy))
+ # Add 'sandwichtop' arm
+ if top.name == 'sandwichtop_no_arm':
+ top.name = 'sandwichtop'
+ else:
+ top.name = 'sandwichtop_no_label'
+ top.spr.set_label(' ', 1)
+ top.resize()
+ top.refresh()
+ grow_stack_arm(top)
+
+
+def uncollapse_forks(top, looping=False):
+ ''' From the top, find and restore any collapsible stacks on forks. '''
+ if top == None:
+ return
+ if looping and top.name in ['sandwichtop_no_arm',
+ 'sandwichtop_no_arm_no_label']:
+ restore_stack(top)
+ return
+ if len(top.connections) == 0:
+ return
+ cblk = top.connections[len(top.connections) - 1]
+ while cblk is not None:
+ if cblk.name in COLLAPSIBLE:
+ return
+ if cblk.name in ['sandwichtop_no_arm', 'sandwichtop_no_arm_no_label']:
+ restore_stack(cblk)
+ return
+ # Follow a fork
+ if cblk.name in ['repeat', 'if', 'ifelse', 'forever', 'while',
+ 'until']:
+ top = find_sandwich_top_below(
+ cblk.connections[len(cblk.connections) - 2])
+ if top is not None:
+ uncollapse_forks(top, True)
+ if cblk.name == 'ifelse':
+ top = find_sandwich_top_below(
+ cblk.connections[len(cblk.connections) - 3])
+ if top is not None:
+ uncollapse_forks(top, True)
+ cblk = cblk.connections[len(cblk.connections) - 1]
+ return
+
+
+def collapse_stack(top):
+ ''' Hide all the blocks between the sandwich top and sandwich bottom. '''
+ # First uncollapse any nested stacks
+ if top == None or top.spr == None:
+ return
+ uncollapse_forks(top)
+ hit_bottom = False
+ bot = find_sandwich_bottom(top)
+ group = find_group(top.connections[len(top.connections) - 1])
+ for gblk in group:
+ if not hit_bottom and gblk == bot:
+ hit_bottom = True
+ # Replace 'sandwichbottom' with invisible 'sandwichcollapsed'
+ if len(gblk.values) == 0:
+ gblk.values.append(1)
+ else:
+ gblk.values[0] = 1
+ olddx = gblk.docks[1][2]
+ olddy = gblk.docks[1][3]
+ gblk.name = 'sandwichcollapsed'
+ gblk.resize()
+ you = find_sandwich_top(gblk)
+ (yx, yy) = you.spr.get_xy()
+ yd = you.docks[len(you.docks) - 1]
+ (bx, by) = gblk.spr.get_xy()
+ dx = yx + yd[2] - gblk.docks[0][2] - bx
+ dy = yy + yd[3] - gblk.docks[0][3] - by
+ gblk.spr.move_relative((dx, dy))
+ # Since the shapes have changed, the dock positions have too.
+ dx += gblk.docks[1][2] - olddx
+ dy += gblk.docks[1][3] - olddy
+ else:
+ if not hit_bottom:
+ gblk.spr.hide()
+ gblk.status = 'collapsed'
+ else:
+ gblk.spr.move_relative((dx, dy))
+ # Remove 'sandwichtop' arm
+ if top.name == 'sandwichtop' or top.name == 'sandwichtop_no_arm':
+ top.name = 'sandwichtop_no_arm'
+ else:
+ top.name = 'sandwichtop_no_arm_no_label'
+ top.spr.set_label(' ')
+ top.spr.set_label(' ', 1)
+ top.resize()
+ top.spr.set_label(_('click to open'), 1)
+ top.resize()
+ top.svg.set_hide(False)
+ top.refresh()
+
+
+def collapsed(blk):
+ ''' Is this stack collapsed? '''
+ if blk is not None and blk.name in COLLAPSIBLE and\
+ len(blk.values) == 1 and blk.values[0] != 0:
+ return True
+ return False
+
+
+def collapsible(blk):
+ ''' Can this stack be collapsed? '''
+ if blk is None or blk.name not in COLLAPSIBLE:
+ return False
+ if find_sandwich_top(blk) is None:
+ return False
+ return True
+
+
+def hide_button_hit(spr, x, y):
+ ''' Did the sprite's hide (contract) button get hit? '''
+ red, green, blue, alpha = spr.get_pixel((x, y))
+ if red == HIT_HIDE:
+ return True
+ else:
+ return False
+
+
+def show_button_hit(spr, x, y):
+ ''' Did the sprite's show (expand) button get hit? '''
+ red, green, blue, alpha = spr.get_pixel((x, y))
+ if green == HIT_SHOW:
+ return True
+ else:
+ return False
+
+
+def numeric_arg(value):
+ ''' Dock test: looking for a numeric value '''
+ if type(convert(value, float)) is float:
+ return True
+ return False
+
+
+def zero_arg(value):
+ ''' Dock test: looking for a zero argument '''
+ if numeric_arg(value):
+ if convert(value, float) == 0:
+ return True
+ return False
+
+
+def neg_arg(value):
+ ''' Dock test: looking for a negative argument '''
+ if numeric_arg(value):
+ if convert(value, float) < 0:
+ return True
+ return False
+
+
+def journal_check(blk1, blk2, dock1, dock2):
+ ''' Dock blocks only if arg is Journal block '''
+ if blk1 == None or blk2 == None:
+ return True
+ if (blk1.name == 'skin' and dock1 == 1) and blk2.name != 'journal':
+ return False
+ if (blk2.name == 'skin' and dock2 == 1) and blk1.name != 'journal':
+ return False
+ return True
+
+
+def arithmetic_check(blk1, blk2, dock1, dock2):
+ ''' Dock strings only if they convert to numbers. Avoid /0 and root(-1)'''
+ if blk1 == None or blk2 == None:
+ return True
+ if blk1.name in ['sqrt', 'number', 'string'] and\
+ blk2.name in ['sqrt', 'number', 'string']:
+ if blk1.name == 'number' or blk1.name == 'string':
+ if not numeric_arg(blk1.values[0]) or neg_arg(blk1.values[0]):
+ return False
+ elif blk2.name == 'number' or blk2.name == 'string':
+ if not numeric_arg(blk2.values[0]) or neg_arg(blk2.values[0]):
+ return False
+ elif blk1.name in ['division2', 'number', 'string'] and\
+ blk2.name in ['division2', 'number', 'string']:
+ if blk1.name == 'number' or blk1.name == 'string':
+ if not numeric_arg(blk1.values[0]):
+ return False
+ if dock2 == 2 and zero_arg(blk1.values[0]):
+ return False
+ elif blk2.name == 'number' or blk2.name == 'string':
+ if not numeric_arg(blk2.values[0]):
+ return False
+ if dock1 == 2 and zero_arg(blk2.values[0]):
+ return False
+ elif blk1.name in ['product2', 'minus2', 'random', 'remainder2',
+ 'string'] and\
+ blk2.name in ['product2', 'minus2', 'random', 'remainder2',
+ 'string']:
+ if blk1.name == 'string':
+ if not numeric_arg(blk1.values[0]):
+ return False
+ elif blk1.name == 'string':
+ if not numeric_arg(blk2.values[0]):
+ return False
+ elif blk1.name in ['greater2', 'less2'] and blk2.name == 'string':
+ # Non-numeric stings are OK only if both args are strings;
+ # Lots of test conditions...
+ if dock1 == 1 and blk1.connections[2] is not None:
+ if blk1.connections[2].name == 'number':
+ if not numeric_arg(blk2.values[0]):
+ return False
+ elif dock1 == 2 and blk1.connections[1] is not None:
+ if blk1.connections[1].name == 'number':
+ if not numeric_arg(blk2.values[0]):
+ return False
+ elif blk2.name in ['greater2', 'less2'] and blk1.name == 'string':
+ if dock2 == 1 and blk2.connections[2] is not None:
+ if blk2.connections[2].name == 'number':
+ if not numeric_arg(blk1.values[0]):
+ return False
+ elif dock2 == 2 and blk2.connections[1] is not None:
+ if blk2.connections[1].name == 'number':
+ if not numeric_arg(blk1.values[0]):
+ return False
+ elif blk1.name in ['greater2', 'less2'] and blk2.name == 'number':
+ if dock1 == 1 and blk1.connections[2] is not None:
+ if blk1.connections[2].name == 'string':
+ if not numeric_arg(blk1.connections[2].values[0]):
+ return False
+ elif dock1 == 2 and blk1.connections[1] is not None:
+ if blk1.connections[1].name == 'string':
+ if not numeric_arg(blk1.connections[1].values[0]):
+ return False
+ elif blk2.name in ['greater2', 'less2'] and blk1.name == 'number':
+ if dock2 == 1 and blk2.connections[2] is not None:
+ if blk2.connections[2].name == 'string':
+ if not numeric_arg(blk2.connections[2].values[0]):
+ return False
+ elif dock2 == 2 and blk2.connections[1] is not None:
+ if blk2.connections[1].name == 'string':
+ if not numeric_arg(blk2.connections[1].values[0]):
+ return False
+ return True
+
+
+def xy(event):
+ ''' Where is the mouse event? '''
+ return map(int, event.get_coords())
+
+
+# Utilities related to finding blocks in stacks.
+
+
+def find_block_to_run(blk):
+ ''' Find a stack to run (any stack without a 'def action'on the top). '''
+ top = find_top_block(blk)
+ if blk == top and blk.name[0:3] is not 'def':
+ return True
+ else:
+ return False
+
+
+def find_top_block(blk):
+ ''' Find the top block in a stack. '''
+ if blk is None:
+ return None
+ if len(blk.connections) == 0:
+ return blk
+ while blk.connections[0] is not None:
+ blk = blk.connections[0]
+ return blk
+
+
+def find_start_stack(blk):
+ ''' Find a stack with a 'start' block on top. '''
+ if blk is None:
+ return False
+ if find_top_block(blk).name == 'start':
+ return True
+ else:
+ return False
+
+
+def find_group(blk):
+ ''' Find the connected group of block in a stack. '''
+ if blk is None:
+ return []
+ group = [blk]
+ if blk.connections is not None:
+ for cblk in blk.connections[1:]:
+ if cblk is not None:
+ group.extend(find_group(cblk))
+ return group
+
+
+def find_blk_below(blk, name):
+ ''' Find a specific block below this block. '''
+ if blk == None or len(blk.connections) == 0:
+ return
+ group = find_group(blk)
+ for gblk in group:
+ if gblk.name == name:
+ return gblk
+ return None
+
+
+def get_hardware():
+ ''' Determine whether we are using XO 1.0, 1.5, or 'unknown' hardware '''
+ product = _get_dmi('product_name')
+ if product is None:
+ if os.path.exists('/sys/devices/platform/lis3lv02d/position'):
+ return XO175 # FIXME: temporary check for XO 1.75 and XO 3.0
+ elif os.path.exists('/etc/olpc-release') or \
+ os.path.exists('/sys/power/olpc-pm'):
+ return XO1
+ else:
+ return UNKNOWN
+ if product != 'XO':
+ return UNKNOWN
+ version = _get_dmi('product_version')
+ if version == '1':
+ return XO1
+ elif version == '1.5':
+ return XO15
+ else:
+ return XO175
+
+
+def _get_dmi(node):
+ ''' The desktop management interface should be a reliable source
+ for product and version information. '''
+ path = os.path.join('/sys/class/dmi/id', node)
+ try:
+ return open(path).readline().strip()
+ except:
+ return None
diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py
new file mode 100644
index 0000000..44e95a6
--- /dev/null
+++ b/TurtleArt/tawindow.py
@@ -0,0 +1,3424 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2007, Playful Invention Company
+#Copyright (c) 2008-12, Walter Bender
+#Copyright (c) 2009-11 Raúl Gutiérrez Segalés
+#Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+
+#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.
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+from gettext import gettext as _
+
+try:
+ import gst
+ GST_AVAILABLE = True
+except ImportError:
+ # Turtle Art should not fail if gst is not available
+ GST_AVAILABLE = False
+
+import os
+import subprocess
+
+from math import atan2, pi
+DEGTOR = 2 * pi / 360
+
+import locale
+
+from taconstants import HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE, \
+ MEDIA_SHAPES, STATUS_SHAPES, OVERLAY_SHAPES, \
+ TOOLBAR_SHAPES, TAB_LAYER, RETURN, OVERLAY_LAYER, CATEGORY_LAYER, \
+ BLOCKS_WITH_SKIN, ICON_SIZE, PALETTE_SCALE, PALETTE_WIDTH, SKIN_PATHS, \
+ MACROS, TOP_LAYER, BLOCK_LAYER, OLD_NAMES, DEFAULT_TURTLE, TURTLE_LAYER, \
+ CURSOR, EXPANDABLE, COLLAPSIBLE, DEAD_DICTS, DEAD_KEYS, NO_IMPORT, \
+ TEMPLATES, PYTHON_SKIN, PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK, \
+ EXPANDABLE_ARGS, XO1, XO15, XO175, XO30, TITLEXY, CONTENT_ARGS, \
+ CONSTANTS, EXPAND_SKIN, PROTO_LAYER
+from tapalette import palette_names, palette_blocks, expandable_blocks, \
+ block_names, content_blocks, default_values, special_names, block_styles, \
+ help_strings, hidden_proto_blocks, string_or_number_args
+from talogo import LogoCode
+from tacanvas import TurtleGraphics
+from tablock import Blocks, Block
+from taturtle import Turtles, Turtle
+from tautils import magnitude, get_load_name, get_save_name, data_from_file, \
+ data_to_file, round_int, get_id, get_pixbuf_from_journal, \
+ movie_media_type, audio_media_type, image_media_type, save_picture, \
+ calc_image_size, get_path, reset_stack_arm, grow_stack_arm, \
+ find_sandwich_top, find_sandwich_bottom, restore_stack, collapse_stack, \
+ collapsed, collapsible, hide_button_hit, show_button_hit, chooser, \
+ arithmetic_check, xy, find_block_to_run, find_top_block, journal_check, \
+ find_group, find_blk_below, data_to_string, find_start_stack, \
+ get_hardware, debug_output, error_output, data_to_string
+from tasprite_factory import SVG, svg_str_to_pixbuf, svg_from_file
+from sprites import Sprites, Sprite
+
+if GST_AVAILABLE:
+ from tagplay import stop_media
+
+MOTION_THRESHOLD = 6
+SNAP_THRESHOLD = 200
+
+
+class TurtleArtWindow():
+ """ TurtleArt Window class abstraction """
+ timeout_tag = [0]
+ _PLUGIN_SUBPATH = 'plugins'
+
+ def __init__(self, canvas_window, path, parent=None,
+ mycolors=None, mynick=None, turtle_canvas=None):
+ self._loaded_project = ''
+ self._sharing = False
+ self.parent = parent
+ self.turtle_canvas = turtle_canvas
+ self.send_event = None # method to send events over the network
+ self.gst_available = GST_AVAILABLE
+ if type(canvas_window) == gtk.DrawingArea:
+ self.interactive_mode = True
+ self.window = canvas_window
+ self.window.set_flags(gtk.CAN_FOCUS)
+ self.window.show_all()
+ if self.parent is not None:
+ self.parent.show_all()
+ self.running_sugar = True
+ else:
+ self.running_sugar = False
+ self._setup_events()
+ else:
+ self.interactive_mode = False
+ self.window = canvas_window
+ self.running_sugar = False
+
+ if self.running_sugar:
+ from sugar import profile
+
+ self.activity = parent
+ self.nick = profile.get_nick_name()
+ else:
+ self.activity = None
+ self.nick = None
+
+ self.path = path
+ self.load_save_folder = os.path.join(path, 'samples')
+ self.py_load_save_folder = os.path.join(path, 'pysamples')
+ self.used_block_list = [] # Which blocks has the user used?
+ self.save_folder = None
+ self.save_file_name = None
+ self.width = gtk.gdk.screen_width()
+ self.height = gtk.gdk.screen_height()
+ self.rect = gtk.gdk.Rectangle(0, 0, 0, 0)
+
+ self.no_help = False
+ self.last_label = None
+
+ self.keypress = ''
+ self.keyvalue = 0
+ self.dead_key = ''
+ self.mouse_flag = 0
+ self.mouse_x = 0
+ self.mouse_y = 0
+
+ try:
+ locale.setlocale(locale.LC_NUMERIC, '')
+ except locale.Error:
+ debug_output('unsupported locale', self.running_sugar)
+ self.decimal_point = locale.localeconv()['decimal_point']
+ if self.decimal_point == '' or self.decimal_point is None:
+ self.decimal_point = '.'
+
+ self.orientation = HORIZONTAL_PALETTE
+
+ self.hw = get_hardware()
+ self.lead = 1.0
+ if self.hw in (XO1, XO15, XO175):
+ self.scale = 1.2 # slight scale-up of fonts on XO
+ if self.hw == XO1:
+ self.color_mode = '565'
+ else:
+ self.color_mode = '888'
+ if self.running_sugar and not self.activity.has_toolbarbox:
+ self.orientation = VERTICAL_PALETTE
+ else:
+ self.scale = 1.0
+ self.color_mode = '888' # TODO: Read visual mode from gtk image
+
+ self.block_scale = BLOCK_SCALE[3]
+ self.trash_scale = 0.5
+ self.myblock = {}
+ self.python_code = None
+ self.nop = 'nop'
+ self.loaded = 0
+ self.step_time = 0
+ self.hide = False
+ self.palette = True
+ self.coord_scale = 1
+ self.buddies = []
+ self.saved_string = ''
+ self.dx = 0
+ self.dy = 0
+ self.media_shapes = {}
+ self.cartesian = False
+ self.polar = False
+ self.metric = False
+ self.overlay_shapes = {}
+ self.toolbar_shapes = {}
+ self.toolbar_offset = 0
+ self.status_spr = None
+ self.status_shapes = {}
+ self.toolbar_spr = None
+ self.palette_sprs = []
+ self.palettes = []
+ self.palette_button = []
+ self.trash_stack = []
+ self.selected_palette = None
+ self.previous_palette = None
+ self.selectors = []
+ self.selected_selector = None
+ self.previous_selector = None
+ self.selector_shapes = []
+ self.selected_blk = None
+ self.selected_spr = None
+ self.selected_turtle = None
+ self.triangle_sprs = []
+ self.drag_group = None
+ self.drag_turtle = 'move', 0, 0
+ self.drag_pos = 0, 0
+ self.dragging_canvas = [False, 0, 0]
+ self.turtle_movement_to_share = None
+ self.paste_offset = 20 # Don't paste on top of where you copied.
+
+ self.block_list = Blocks(font_scale_factor=self.scale,
+ decimal_point=self.decimal_point)
+ if self.interactive_mode:
+ self.sprite_list = Sprites(self.window)
+ else:
+ self.sprite_list = None
+
+ self.canvas = TurtleGraphics(self, self.width, self.height)
+ if self.hw == XO175 and self.canvas.width == 1024:
+ self.hw = XO30 # FIXME: temporary test
+ if self.interactive_mode:
+ self.sprite_list.set_cairo_context(self.canvas.canvas)
+
+ self.turtles = Turtles(self.sprite_list)
+ if self.nick is None:
+ self.default_turtle_name = DEFAULT_TURTLE
+ else:
+ self.default_turtle_name = self.nick
+ if mycolors is None:
+ Turtle(self.turtles, self.default_turtle_name)
+ else:
+ Turtle(self.turtles, self.default_turtle_name, mycolors.split(','))
+ self.active_turtle = self.turtles.get_turtle(self.default_turtle_name)
+ self.active_turtle.show()
+
+ self.canvas.clearscreen(False)
+
+ CONSTANTS['titlex'] = int(-(self.canvas.width * TITLEXY[0]) / \
+ (self.coord_scale * 2))
+ CONSTANTS['leftx'] = int(-(self.canvas.width * TITLEXY[0]) / \
+ (self.coord_scale * 2))
+ CONSTANTS['rightx'] = 0
+ CONSTANTS['titley'] = int((self.canvas.height * TITLEXY[1]) / \
+ (self.coord_scale * 2))
+ CONSTANTS['topy'] = int((self.canvas.height * (TITLEXY[1] - 0.125)) / \
+ (self.coord_scale * 2))
+ CONSTANTS['bottomy'] = 0
+ CONSTANTS['leftpos'] = int(-self.canvas.width / (self.coord_scale * 2))
+ CONSTANTS['toppos'] = int(self.canvas.height / (self.coord_scale * 2))
+ CONSTANTS['rightpos'] = int(self.canvas.width / (self.coord_scale * 2))
+ CONSTANTS['bottompos'] = int(-self.canvas.height / \
+ (self.coord_scale * 2))
+ CONSTANTS['width'] = int(self.canvas.width / self.coord_scale)
+ CONSTANTS['height'] = int(self.canvas.height / self.coord_scale)
+
+ self._icon_paths = [os.path.join(self.path, 'icons')]
+ self._plugins = []
+
+ self._init_plugins()
+ self.lc = LogoCode(self)
+
+ from tabasics import Palettes
+ p = Palettes(self)
+ self._setup_plugins()
+
+ if self.interactive_mode:
+ self._setup_misc()
+ self.show_toolbar_palette(0, False)
+
+ self.saved_pictures = []
+ self.block_operation = ''
+
+ def _get_plugin_home(self):
+ """ Look in the execution directory """
+ path = os.path.join(self.path, self._PLUGIN_SUBPATH)
+ if os.path.exists(path):
+ return path
+ else:
+ return None
+
+ def _get_plugins_from_plugins_dir(self, path):
+ """ Look for plugin files in plugin dir. """
+ plugin_files = []
+ if path is not None:
+ candidates = os.listdir(path)
+ for dirname in candidates:
+ if os.path.exists(
+ os.path.join(path, dirname, dirname + '.py')):
+ plugin_files.append(dirname)
+ return plugin_files
+
+ def _init_plugins(self):
+ """ Try importing plugin files from the plugin dir. """
+ for plugin_dir in self._get_plugins_from_plugins_dir(
+ self._get_plugin_home()):
+ self.init_plugin(plugin_dir)
+
+ def init_plugin(self, plugin_dir):
+ """ Initialize plugin in plugin_dir """
+ plugin_class = plugin_dir.capitalize()
+ f = "def f(self): from plugins.%s.%s import %s; return %s(self)" \
+ % (plugin_dir, plugin_dir, plugin_class, plugin_class)
+ plugins = {}
+ try:
+ exec f in globals(), plugins
+ self._plugins.append(plugins.values()[0](self))
+ debug_output('Successfully importing %s' % (plugin_class),
+ self.running_sugar)
+ # Add the icon dir to the icon_theme search path
+ self._add_plugin_icon_dir(os.path.join(self._get_plugin_home(),
+ plugin_dir))
+ except ImportError, e:
+ debug_output('Failed to import %s: %s' % (plugin_class, str(e)),
+ self.running_sugar)
+
+ def _add_plugin_icon_dir(self, dirname):
+ ''' If there is an icon subdir, add it to the search path. '''
+ icon_theme = gtk.icon_theme_get_default()
+ icon_path = os.path.join(dirname, 'icons')
+ if os.path.exists(icon_path):
+ icon_theme.append_search_path(icon_path)
+ self._icon_paths.append(icon_path)
+
+ def _get_plugin_instance(self, plugin_name):
+ """ Returns the plugin 'plugin_name' instance """
+ list_plugins = self._get_plugins_from_plugins_dir(
+ self._get_plugin_home())
+ if plugin_name in list_plugins:
+ number_plugin = list_plugins.index(plugin_name)
+ return self._plugins[number_plugin]
+ else:
+ return None
+
+ def _setup_plugins(self):
+ """ Initial setup -- called just once. """
+ for plugin in self._plugins:
+ plugin.setup()
+
+ def _start_plugins(self):
+ """ Start is called everytime we execute blocks. """
+ for plugin in self._plugins:
+ plugin.start()
+
+ def _stop_plugins(self):
+ """ Stop is called whenever we stop execution. """
+ for plugin in self._plugins:
+ plugin.stop()
+
+ def clear_plugins(self):
+ """ Clear is called from the clean block and erase button. """
+ for plugin in self._plugins:
+ if hasattr(plugin, 'clear'):
+ plugin.clear()
+
+ def background_plugins(self):
+ """ Background is called when we are pushed to the background. """
+ for plugin in self._plugins:
+ plugin.goto_background()
+
+ def foreground_plugins(self):
+ """ Foreground is called when we are return from the background. """
+ for plugin in self._plugins:
+ plugin.return_to_foreground()
+
+ def quit_plugins(self):
+ """ Quit is called upon program exit. """
+ for plugin in self._plugins:
+ plugin.quit()
+
+ def _setup_events(self):
+ """ Register the events we listen to. """
+ self.window.add_events(gtk.gdk.BUTTON_PRESS_MASK)
+ self.window.add_events(gtk.gdk.BUTTON_RELEASE_MASK)
+ self.window.add_events(gtk.gdk.POINTER_MOTION_MASK)
+ self.window.add_events(gtk.gdk.KEY_PRESS_MASK)
+ # self.window.connect('realize', self.do_realize)
+ self.window.connect("expose-event", self._expose_cb)
+ self.window.connect("button-press-event", self._buttonpress_cb)
+ self.window.connect("button-release-event", self._buttonrelease_cb)
+ self.window.connect("motion-notify-event", self._move_cb)
+ self.window.connect("key-press-event", self._keypress_cb)
+
+ def load_media_shapes(self):
+ """ Media shapes get positioned onto blocks """
+ for name in MEDIA_SHAPES:
+ if name in self.media_shapes:
+ continue
+ if name[0:7] == 'journal' and not self.running_sugar:
+ filename = 'file' + name[7:]
+ else:
+ filename = name
+ # Try both images/ and plugins/*/images/
+ for path in SKIN_PATHS:
+ if os.path.exists(os.path.join(self.path, path,
+ filename + '.svg')):
+ self.media_shapes[name] = svg_str_to_pixbuf(svg_from_file(
+ os.path.join(self.path, path, filename + '.svg')))
+ break
+
+ def _setup_misc(self):
+ """ Misc. sprites for status, overlays, etc. """
+ self.load_media_shapes()
+ for i, name in enumerate(STATUS_SHAPES):
+ # Temporary hack to use wider shapes
+ if name in ['print', 'help', 'status'] and self.width > 1024:
+ self.status_shapes[name] = svg_str_to_pixbuf(svg_from_file(
+ os.path.join(self.path, 'images', name + '1200.svg')))
+ else:
+ self.status_shapes[name] = svg_str_to_pixbuf(svg_from_file(
+ os.path.join(self.path, 'images', name + '.svg')))
+ self.status_spr = Sprite(self.sprite_list, 0, self.height - 200,
+ self.status_shapes['status'])
+ self.status_spr.hide()
+ self.status_spr.type = 'status'
+
+ for name in OVERLAY_SHAPES:
+ self.overlay_shapes[name] = Sprite(self.sprite_list,
+ int(self.width / 2 - 600),
+ int(self.height / 2 - 450),
+ svg_str_to_pixbuf(
+ svg_from_file("%s/images/%s.svg" % (self.path, name))))
+ self.overlay_shapes[name].hide()
+ self.overlay_shapes[name].type = 'overlay'
+
+ if not self.running_sugar:
+ # offset = 2 * self.width - 55 * len(TOOLBAR_SHAPES)
+ offset = 55 * (2 + len(palette_blocks))
+ for i, name in enumerate(TOOLBAR_SHAPES):
+ self.toolbar_shapes[name] = Sprite(
+ self.sprite_list, i * 55 + offset, 0,
+ svg_str_to_pixbuf(svg_from_file(os.path.join(
+ self.path, 'icons', '%s.svg' % (name)))))
+ self.toolbar_shapes[name].set_layer(TAB_LAYER)
+ self.toolbar_shapes[name].name = name
+ self.toolbar_shapes[name].type = 'toolbar'
+ self.toolbar_shapes['stopiton'].hide()
+
+ # Make the triangle buttons for adjusting numeric values
+ svg = SVG()
+ svg.set_scale(self.scale)
+ self.triangle_sprs.append(Sprite(self.sprite_list, 0, 0,
+ svg_str_to_pixbuf(svg.triangle_up(["#FF00FF", "#A000A0"]))))
+ self.triangle_sprs[-1].set_label('+')
+ self.triangle_sprs[-1].type = 'control'
+ self.triangle_sprs[-1].hide()
+ self.triangle_sprs.append(Sprite(self.sprite_list, 0, 0,
+ svg_str_to_pixbuf(svg.triangle_down(["#FF00FF", "#A000A0"]))))
+ self.triangle_sprs[-1].set_label('-')
+ self.triangle_sprs[-1].type = 'control'
+ self.triangle_sprs[-1].hide()
+
+ def set_sharing(self, shared):
+ self._sharing = shared
+
+ def sharing(self):
+ return self._sharing
+
+ def is_project_empty(self):
+ """ Check to see if project has any blocks in use """
+ return len(self.just_blocks()) == 1
+
+ def _expose_cb(self, win=None, event=None):
+ """ Repaint """
+ self.do_expose_event(event)
+ return True
+
+ # Handle the expose-event by drawing
+ def do_expose_event(self, event=None):
+
+ # Create the cairo context
+ cr = self.window.window.cairo_create()
+ # TODO: set global scale
+ # find_sprite needs rescaled coordinates
+ # sw needs new bounds set
+ # cr.scale(self.activity.global_x_scale, self.activity.global_y_scale)
+
+ if event is None:
+ cr.rectangle(self.rect.x, self.rect.y,
+ self.rect.width, self.rect.height)
+ else:
+ # Restrict Cairo to the exposed area; avoid extra work
+ cr.rectangle(event.area.x, event.area.y,
+ event.area.width, event.area.height)
+ cr.clip()
+
+ if self.turtle_canvas is not None:
+ cr.set_source_surface(self.turtle_canvas)
+ cr.paint()
+
+ # Refresh sprite list
+ self.sprite_list.redraw_sprites(cr=cr)
+
+ def eraser_button(self):
+ """ Eraser_button (hide status block when clearing the screen.) """
+ if self.status_spr is not None:
+ self.status_spr.hide()
+ self.lc.find_value_blocks() # Are there blocks to update?
+ self.lc.prim_clear()
+ self.display_coordinates()
+ self.parent.restore_challenge()
+
+ def run_button(self, time, running_from_button_push=False):
+ """ Run turtle! """
+ if self.running_sugar:
+ self.activity.recenter()
+
+ # Look for a 'start' block
+ for blk in self.just_blocks():
+ if find_start_stack(blk):
+ self.step_time = time
+ debug_output("running stack starting from %s" % (blk.name),
+ self.running_sugar)
+ if running_from_button_push:
+ self.selected_blk = None
+ else:
+ self.selected_blk = blk
+ self._run_stack(blk)
+ return
+
+ # If there is no 'start' block, run stacks that aren't 'def action'
+ for blk in self.just_blocks():
+ if find_block_to_run(blk):
+ self.step_time = time
+ debug_output("running stack starting from %s" % (blk.name),
+ self.running_sugar)
+ if running_from_button_push:
+ self.selected_blk = None
+ else:
+ self.selected_blk = blk
+ self._run_stack(blk)
+ return
+
+ def stop_button(self):
+ """ Stop button """
+ self.lc.stop_logo()
+ self._stop_plugins()
+
+ def set_userdefined(self, blk=None):
+ """ Change icon for user-defined blocks after loading Python code. """
+ if blk is not None:
+ if blk.name in PYTHON_SKIN:
+ x, y = self._calc_image_offset('pythonon', blk.spr)
+ blk.set_image(self.media_shapes['pythonon'], x, y)
+ self._resize_skin(blk)
+
+ def set_fullscreen(self):
+ """ Enter fullscreen mode """
+ if self.running_sugar:
+ self.activity.fullscreen()
+ self.activity.recenter()
+
+ def set_cartesian(self, flag):
+ """ Turn on/off Cartesian coordinates """
+ if self.coord_scale == 1:
+ self.draw_overlay('Cartesian_labeled')
+ else:
+ self.draw_overlay('Cartesian')
+ return
+
+ def set_polar(self, flag):
+ """ Turn on/off polar coordinates """
+ self.draw_overlay('polar')
+ return
+
+ def set_metric(self, flag):
+ """ Turn on/off metric coordinates """
+ self.draw_overlay('metric')
+ return
+
+ def draw_overlay(self, overlay):
+ ''' Draw a coordinate grid onto the canvas. '''
+ save_heading = self.canvas.heading
+ self.canvas.heading = 0
+ w = self.overlay_shapes[overlay].rect[2]
+ h = self.overlay_shapes[overlay].rect[3]
+ self.canvas.draw_surface(
+ self.overlay_shapes[overlay].cached_surfaces[0],
+ (self.canvas.width - w) / 2.,
+ (self.canvas.height - h) / 2., w, h)
+ self.canvas.heading = save_heading
+
+ def update_overlay_position(self, widget, event):
+ """ Reposition the overlays when window size changes """
+ self.width = event.width
+ self.height = event.height
+ for name in OVERLAY_SHAPES:
+ shape = self.overlay_shapes[name]
+ showing = False
+ if shape in shape._sprites.list:
+ shape.hide()
+ showing = True
+ self.overlay_shapes[name] = Sprite(self.sprite_list,
+ int(self.width / 2 - 600),
+ int(self.height / 2 - 450),
+ svg_str_to_pixbuf(
+ svg_from_file("%s/images/%s.svg" % (self.path, name))))
+ if showing:
+ self.overlay_shapes[name].set_layer(OVERLAY_LAYER)
+ else:
+ self.overlay_shapes[name].hide()
+ self.overlay_shapes[name].type = 'overlay'
+ self.cartesian = False
+ self.polar = False
+ self.metric = False
+ self.canvas.width = self.width
+ self.canvas.height = self.height
+ self.canvas.move_turtle()
+
+ def hideshow_button(self):
+ """ Hide/show button """
+ if not self.hide:
+ for blk in self.just_blocks():
+ blk.spr.hide()
+ for spr in self.triangle_sprs:
+ spr.hide()
+ self.hide_palette()
+ self.hide = True
+ else:
+ for blk in self.just_blocks():
+ if blk.status != 'collapsed':
+ blk.spr.set_layer(BLOCK_LAYER)
+ self.show_palette()
+ self.hide = False
+ if self.running_sugar:
+ self.activity.recenter()
+ self.inval_all()
+
+ def inval_all(self):
+ """ Force a refresh """
+ if self.interactive_mode:
+ self.window.queue_draw_area(0, 0, self.width, self.height)
+
+ def hideshow_palette(self, state):
+ """ Hide or show palette """
+ if not state:
+ self.palette = False
+ if self.running_sugar:
+ self.activity.do_hidepalette()
+ self.hide_palette()
+ else:
+ self.palette = True
+ if self.running_sugar:
+ self.activity.do_showpalette()
+ self.activity.recenter()
+ self.show_palette()
+
+ def show_palette(self, n=None):
+ """ Show palette. """
+ if n is None:
+ if self.selected_palette is None:
+ n = 0
+ else:
+ n = self.selected_palette
+ self.show_toolbar_palette(n)
+ self.palette_button[self.orientation].set_layer(TAB_LAYER)
+ self.palette_button[2].set_layer(TAB_LAYER)
+ self._display_palette_shift_button(n)
+ if self.activity is None or not self.activity.has_toolbarbox:
+ self.toolbar_spr.set_layer(CATEGORY_LAYER)
+ self.palette = True
+
+ def hide_palette(self):
+ """ Hide the palette. """
+ self._hide_toolbar_palette()
+ self.palette_button[self.orientation].hide()
+ self.palette_button[2].hide()
+ self.palette_button[3].hide()
+ self.palette_button[4].hide()
+ if self.activity is None or not self.activity.has_toolbarbox:
+ self.toolbar_spr.hide()
+ self.palette = False
+
+ def move_palettes(self, x, y):
+ """ Move the palettes. """
+ for p in self.palettes:
+ for blk in p:
+ blk.spr.move((x + blk.spr.save_xy[0], y + blk.spr.save_xy[1]))
+ for spr in self.palette_button:
+ spr.move((x + spr.save_xy[0], y + spr.save_xy[1]))
+ for p in self.palette_sprs:
+ if p[0] is not None:
+ p[0].move((x + p[0].save_xy[0], y + p[0].save_xy[1]))
+ if p[1] is not None:
+ p[1].move((x + p[1].save_xy[0], y + p[1].save_xy[1]))
+
+ self.status_spr.move((x + self.status_spr.save_xy[0],
+ y + self.status_spr.save_xy[1]))
+
+ # To do: set save_xy for blocks in Trash
+ for blk in self.trash_stack:
+ for gblk in find_group(blk):
+ gblk.spr.move((x + gblk.spr.save_xy[0],
+ y + gblk.spr.save_xy[1]))
+
+ def hideblocks(self):
+ """ Callback from 'hide blocks' block """
+ if not self.interactive_mode:
+ return
+ self.hide = False
+ self.hideshow_button()
+ if self.running_sugar:
+ self.activity.do_hide_blocks()
+
+ def showblocks(self):
+ """ Callback from 'show blocks' block """
+ if not self.interactive_mode:
+ return
+ self.hide = True
+ self.hideshow_button()
+ if self.running_sugar:
+ self.activity.do_show_blocks()
+
+ def resize_blocks(self, blocks=None):
+ """ Resize blocks or if blocks is None, all of the blocks """
+ if blocks is None:
+ blocks = self.just_blocks()
+
+ # We need to restore collapsed stacks before resizing.
+ for blk in blocks:
+ if blk.status == 'collapsed':
+ bot = find_sandwich_bottom(blk)
+ if collapsed(bot):
+ dy = bot.values[0]
+ restore_stack(find_sandwich_top(blk))
+ bot.values[0] = dy
+
+ # Do the resizing.
+ for blk in blocks:
+ blk.rescale(self.block_scale)
+ for blk in blocks:
+ self._adjust_dock_positions(blk)
+
+ # Re-collapsed stacks after resizing.
+ for blk in blocks:
+ if collapsed(blk):
+ collapse_stack(find_sandwich_top(blk))
+ for blk in blocks:
+ if blk.name in ['sandwichtop', 'sandwichtop_no_label']:
+ grow_stack_arm(blk)
+
+ # Resize the skins on some blocks: media content and Python
+ for blk in blocks:
+ if blk.name in BLOCKS_WITH_SKIN:
+ self._resize_skin(blk)
+
+ def _shift_toolbar_palette(self, n):
+ ''' Shift blocks on specified palette '''
+ x, y = self.palette_sprs[n][self.orientation].get_xy()
+ w, h = self.palette_sprs[n][self.orientation].get_dimensions()
+ bx, by = self.palettes[n][0].spr.get_xy()
+ if self.orientation == 0:
+ dx = w - self.width
+ dy = 0
+ if bx - x > 0:
+ dx *= -1
+ else:
+ dx = 0
+ dy = h - self.height + ICON_SIZE
+ if by - y > 0:
+ dy *= -1
+ for blk in self.palettes[n]:
+ if blk.get_visibility():
+ blk.spr.move_relative((dx, dy))
+
+ def show_toolbar_palette(self, n, init_only=False, regenerate=False,
+ show=True):
+ """ Show the toolbar palettes, creating them on init_only """
+ # If we are running the 0.86+ toolbar, the selectors are already
+ # created, as toolbar buttons. Otherwise, we need to create them.
+ if (self.activity is None or not self.activity.has_toolbarbox) and \
+ self.selectors == []:
+ # First, create the selector buttons
+ self._create_the_selectors()
+
+ # Create the empty palettes that we'll then populate with prototypes.
+ if self.palette_sprs == []:
+ self._create_the_empty_palettes()
+
+ # At initialization of the program, we don't actually populate
+ # the palettes.
+ if init_only:
+ return
+
+ if show:
+ # Hide the previously displayed palette
+ self._hide_previous_palette()
+ else:
+ save_selected = self.selected_palette
+ save_previous = self.previous_palette
+
+ self.selected_palette = n
+ self.previous_palette = self.selected_palette
+
+ # Make sure all of the selectors are visible. (We don't need to do
+ # this for 0.86+ toolbars since the selectors are toolbar buttons.)
+ if show and \
+ (self.activity is None or not self.activity.has_toolbarbox):
+ self.selected_selector = self.selectors[n]
+ self.selectors[n].set_shape(self.selector_shapes[n][1])
+ for i in range(len(palette_blocks)):
+ self.selectors[i].set_layer(TAB_LAYER)
+
+ # Show the palette with the current orientation.
+ if self.palette_sprs[n][self.orientation] is not None:
+ self.palette_sprs[n][self.orientation].set_layer(
+ CATEGORY_LAYER)
+ self._display_palette_shift_button(n)
+
+ # Create 'proto' blocks for each palette entry
+ self._create_proto_blocks(n)
+
+ if show or save_selected == n:
+ self._layout_palette(n, regenerate=regenerate)
+ else:
+ self._layout_palette(n, regenerate=regenerate, show=False)
+ for blk in self.palettes[n]:
+ if blk.get_visibility():
+ if hasattr(blk.spr, 'set_layer'):
+ blk.spr.set_layer(PROTO_LAYER)
+ else:
+ debug_output('WARNING: block sprite is None' % (blk.name),
+ self.running_sugar)
+ else:
+ blk.spr.hide()
+ if n == palette_names.index('trash'):
+ for blk in self.trash_stack:
+ for gblk in find_group(blk):
+ if gblk.status != 'collapsed':
+ gblk.spr.set_layer(TAB_LAYER)
+
+ if not show:
+ if not save_selected == n:
+ self._hide_previous_palette(palette=n)
+ self.selected_palette = save_selected
+ self.previous_palette = save_previous
+
+ def _display_palette_shift_button(self, n):
+ ''' Palettes too wide (or tall) for the screen get a shift button '''
+ if self.palette_sprs[n][self.orientation].type == \
+ 'category-shift-horizontal':
+ self.palette_button[3].set_layer(CATEGORY_LAYER)
+ self.palette_button[4].hide()
+ elif self.palette_sprs[n][self.orientation].type == \
+ 'category-shift-vertical':
+ self.palette_button[3].hide()
+ self.palette_button[4].set_layer(CATEGORY_LAYER)
+ else:
+ self.palette_button[3].hide()
+ self.palette_button[4].hide()
+
+ def _create_the_selectors(self):
+ ''' Create the palette selector buttons: only when running
+ old-style Sugar toolbars or from GNOME '''
+ svg = SVG()
+ x, y = 50, 0 # positioned at the left, top
+ for i, name in enumerate(palette_names):
+ for path in self._icon_paths:
+ if os.path.exists(os.path.join(path, '%soff.svg' % (name))):
+ icon_pathname = os.path.join(path, '%soff.svg' % (name))
+ break
+ if icon_pathname is not None:
+ off_shape = svg_str_to_pixbuf(svg_from_file(icon_pathname))
+ else:
+ off_shape = svg_str_to_pixbuf(svg_from_file(os.path.join(
+ self._icon_paths[0], 'extrasoff.svg')))
+ error_output('Unable to open %soff.svg' % (name),
+ self.running_sugar)
+ for path in self._icon_paths:
+ if os.path.exists(os.path.join(path, '%son.svg' % (name))):
+ icon_pathname = os.path.join(path, '%son.svg' % (name))
+ break
+ if icon_pathname is not None:
+ on_shape = svg_str_to_pixbuf(svg_from_file(icon_pathname))
+ else:
+ on_shape = svg_str_to_pixbuf(svg_from_file(os.path.join(
+ self._icon_paths[0], 'extrason.svg')))
+ error_output('Unable to open %son.svg' % (name),
+ self.running_sugar)
+
+ self.selector_shapes.append([off_shape, on_shape])
+ self.selectors.append(Sprite(self.sprite_list, x, y, off_shape))
+ self.selectors[i].type = 'selector'
+ self.selectors[i].name = name
+ self.selectors[i].set_layer(TAB_LAYER)
+ w = self.selectors[i].get_dimensions()[0]
+ x += int(w) # running from left to right
+
+ # Create the toolbar background for the selectors
+ self.toolbar_offset = ICON_SIZE
+ self.toolbar_spr = Sprite(self.sprite_list, 0, 0,
+ svg_str_to_pixbuf(svg.toolbar(2 * self.width, ICON_SIZE)))
+ self.toolbar_spr.type = 'toolbar'
+ self.toolbar_spr.set_layer(CATEGORY_LAYER)
+
+ def _create_the_empty_palettes(self):
+ ''' Create the empty palettes to be populated by prototype blocks. '''
+ if len(self.palettes) == 0:
+ for i in range(len(palette_blocks)):
+ self.palettes.append([])
+
+ # Create empty palette backgrounds
+ for i in palette_names:
+ self.palette_sprs.append([None, None])
+
+ # Create the palette orientation button
+ self.palette_button.append(Sprite(self.sprite_list, 0,
+ self.toolbar_offset, svg_str_to_pixbuf(svg_from_file(
+ "%s/images/palettehorizontal.svg" % (self.path)))))
+ self.palette_button.append(Sprite(self.sprite_list, 0,
+ self.toolbar_offset, svg_str_to_pixbuf(svg_from_file(
+ "%s/images/palettevertical.svg" % (self.path)))))
+ self.palette_button[0].name = _('orientation')
+ self.palette_button[1].name = _('orientation')
+ self.palette_button[0].type = 'palette'
+ self.palette_button[1].type = 'palette'
+ self.palette_button[self.orientation].set_layer(TAB_LAYER)
+ self.palette_button[1 - self.orientation].hide()
+
+ # Create the palette next button
+ self.palette_button.append(Sprite(self.sprite_list, 16,
+ self.toolbar_offset, svg_str_to_pixbuf(svg_from_file(
+ "%s/images/palettenext.svg" % (self.path)))))
+ self.palette_button[2].name = _('next')
+ self.palette_button[2].type = 'palette'
+ self.palette_button[2].set_layer(TAB_LAYER)
+
+ # Create the palette shift buttons
+ dims = self.palette_button[0].get_dimensions()
+ self.palette_button.append(Sprite(self.sprite_list, 0,
+ self.toolbar_offset + dims[1], svg_str_to_pixbuf(svg_from_file(
+ "%s/images/palettehshift.svg" % (self.path)))))
+ self.palette_button.append(Sprite(self.sprite_list, dims[0],
+ self.toolbar_offset, svg_str_to_pixbuf(svg_from_file(
+ "%s/images/palettevshift.svg" % (self.path)))))
+ self.palette_button[3].name = _('shift')
+ self.palette_button[4].name = _('shift')
+ self.palette_button[3].type = 'palette'
+ self.palette_button[4].type = 'palette'
+ self.palette_button[3].hide()
+ self.palette_button[4].hide()
+
+ def _create_proto_blocks(self, n):
+ ''' Create the protoblocks that will populate a palette. '''
+ # Reload the palette, but reuse the existing blocks
+ # If a block doesn't exist, add it
+
+ if not n < len(self.palettes):
+ debug_output(
+ '_create_proto_blocks: palette index %d is out of range' % (n),
+ self.running_sugar)
+ return
+
+ for blk in self.palettes[n]:
+ blk.spr.hide()
+ old_blocks = self.palettes[n][:]
+ self.palettes[n] = []
+ for name in palette_blocks[n]:
+ found_block = False
+ for oblk in old_blocks:
+ if oblk.name == name:
+ self.palettes[n].append(oblk)
+ found_block = True
+ break
+ if not found_block:
+ self.palettes[n].append(Block(
+ self.block_list, self.sprite_list, name, 0, 0,
+ 'proto', [], PALETTE_SCALE))
+ if name in hidden_proto_blocks:
+ self.palettes[n][-1].set_visibility(False)
+ else:
+ if hasattr(self.palettes[n][-1].spr, 'set_layer'):
+ self.palettes[n][-1].spr.set_layer(PROTO_LAYER)
+ self.palettes[n][-1].unhighlight()
+ else:
+ debug_output('WARNING: block sprite is None' % (
+ self.palettes[n][-1].name), self.running_sugar)
+
+ # Some proto blocks get a skin.
+ if name in block_styles['box-style-media']:
+ self._proto_skin(name + 'small', n, -1)
+ elif name[:8] == 'template': # Deprecated
+ self._proto_skin(name[8:], n, -1)
+ elif name[:7] == 'picture': # Deprecated
+ self._proto_skin(name[7:], n, -1)
+ elif name in PYTHON_SKIN:
+ self._proto_skin('pythonsmall', n, -1)
+ return
+
+ def _hide_toolbar_palette(self):
+ """ Hide the toolbar palettes """
+ self._hide_previous_palette()
+ if self.activity is None or not self.activity.has_toolbarbox:
+ # Hide the selectors
+ for i in range(len(palette_blocks)):
+ self.selectors[i].hide()
+ elif self.selected_palette is not None and \
+ not self.activity.has_toolbarbox:
+ self.activity.palette_buttons[self.selected_palette].set_icon(
+ palette_names[self.selected_palette] + 'off')
+
+ def _hide_previous_palette(self, palette=None):
+ """ Hide just the previously viewed toolbar palette """
+ if palette is None:
+ palette = self.previous_palette
+ # Hide previously selected palette
+ if palette is not None:
+ if not palette < len(self.palettes):
+ debug_output(
+ '_hide_previous_palette: index %d is out of range' % \
+ (palette), self.running_sugar)
+ return
+ for proto in self.palettes[palette]:
+ proto.spr.hide()
+ if self.palette_sprs[palette][self.orientation] is not None:
+ self.palette_sprs[palette][self.orientation].hide()
+ if self.activity is None or not self.activity.has_toolbarbox:
+ self.selectors[palette].set_shape(
+ self.selector_shapes[palette][0])
+ elif palette is not None and palette != self.selected_palette \
+ and not self.activity.has_toolbarbox:
+ self.activity.palette_buttons[palette].set_icon(
+ palette_names[palette] + 'off')
+ if palette == palette_names.index('trash'):
+ for blk in self.trash_stack:
+ for gblk in find_group(blk):
+ gblk.spr.hide()
+
+ def _horizontal_layout(self, x, y, blocks):
+ """ Position prototypes in a horizontal palette. """
+ max_w = 0
+ for blk in blocks:
+ if not blk.get_visibility():
+ continue
+ w, h = self._width_and_height(blk)
+ if y + h > PALETTE_HEIGHT + self.toolbar_offset:
+ x += int(max_w + 3)
+ y = self.toolbar_offset + 3
+ max_w = 0
+ (bx, by) = blk.spr.get_xy()
+ dx = x - bx
+ dy = y - by
+ for g in find_group(blk):
+ g.spr.move_relative((int(dx), int(dy)))
+ g.spr.save_xy = g.spr.get_xy()
+ if self.running_sugar and not self.hw in [XO1]:
+ g.spr.move_relative((self.activity.hadj_value,
+ self.activity.vadj_value))
+ y += int(h + 3)
+ if w > max_w:
+ max_w = w
+ return x, y, max_w
+
+ def _vertical_layout(self, x, y, blocks):
+ """ Position prototypes in a vertical palette. """
+ row = []
+ row_w = 0
+ max_h = 0
+ for blk in blocks:
+ if not blk.get_visibility():
+ continue
+ w, h = self._width_and_height(blk)
+ if x + w > PALETTE_WIDTH:
+ # Recenter row.
+ dx = int((PALETTE_WIDTH - row_w) / 2)
+ for r in row:
+ for g in find_group(r):
+ g.spr.move_relative((dx, 0))
+ g.spr.save_xy = (g.spr.save_xy[0] + dx,
+ g.spr.save_xy[1])
+ row = []
+ row_w = 0
+ x = 4
+ y += int(max_h + 3)
+ max_h = 0
+ row.append(blk)
+ row_w += (4 + w)
+ (bx, by) = blk.spr.get_xy()
+ dx = int(x - bx)
+ dy = int(y - by)
+ for g in find_group(blk):
+ g.spr.move_relative((dx, dy))
+ g.spr.save_xy = g.spr.get_xy()
+ if self.running_sugar and not self.hw in [XO1]:
+ g.spr.move_relative((self.activity.hadj_value,
+ self.activity.vadj_value))
+ x += int(w + 4)
+ if h > max_h:
+ max_h = h
+ # Recenter last row.
+ dx = int((PALETTE_WIDTH - row_w) / 2)
+ for r in row:
+ for g in find_group(r):
+ g.spr.move_relative((dx, 0))
+ g.spr.save_xy = (g.spr.save_xy[0] + dx, g.spr.save_xy[1])
+ return x, y, max_h
+
+ def _layout_palette(self, n, regenerate=False, show=True):
+ """ Layout prototypes in a palette. """
+ if n is not None:
+ if self.orientation == HORIZONTAL_PALETTE:
+ x, y = 20, self.toolbar_offset + 5
+ x, y, max_w = self._horizontal_layout(x, y, self.palettes[n])
+ if n == palette_names.index('trash'):
+ x, y, max_w = self._horizontal_layout(x + max_w, y,
+ self.trash_stack)
+ w = x + max_w + 25
+ self._make_palette_spr(n, 0, self.toolbar_offset,
+ w, PALETTE_HEIGHT, regenerate)
+ if show:
+ self.palette_button[2].move((w - 20, self.toolbar_offset))
+ else:
+ x, y = 5, self.toolbar_offset + 15
+ x, y, max_h = self._vertical_layout(x, y, self.palettes[n])
+ if n == palette_names.index('trash'):
+ x, y, max_h = self._vertical_layout(x, y + max_h,
+ self.trash_stack)
+ h = y + max_h + 25 - self.toolbar_offset
+ self._make_palette_spr(n, 0, self.toolbar_offset,
+ PALETTE_WIDTH, h, regenerate)
+ if show:
+ self.palette_button[2].move((PALETTE_WIDTH - 20,
+ self.toolbar_offset))
+ if show:
+ self.palette_button[2].save_xy = \
+ self.palette_button[2].get_xy()
+ if self.running_sugar and not self.hw in [XO1]:
+ self.palette_button[2].move_relative(
+ (self.activity.hadj_value, self.activity.vadj_value))
+ self.palette_sprs[n][self.orientation].set_layer(
+ CATEGORY_LAYER)
+ self._display_palette_shift_button(n)
+
+ def _make_palette_spr(self, n, x, y, w, h, regenerate=False):
+ ''' Make the background for the palette. '''
+ if regenerate and not self.palette_sprs[n][self.orientation] is None:
+ self.palette_sprs[n][self.orientation].hide()
+ self.palette_sprs[n][self.orientation] = None
+ if self.palette_sprs[n][self.orientation] is None:
+ svg = SVG()
+ self.palette_sprs[n][self.orientation] = \
+ Sprite(self.sprite_list, x, y, svg_str_to_pixbuf(
+ svg.palette(w, h)))
+ self.palette_sprs[n][self.orientation].save_xy = (x, y)
+ if self.running_sugar and not self.hw in [XO1]:
+ self.palette_sprs[n][self.orientation].move_relative(
+ (self.activity.hadj_value, self.activity.vadj_value))
+ if self.orientation == 0 and w > self.width:
+ self.palette_sprs[n][self.orientation].type = \
+ 'category-shift-horizontal'
+ elif self.orientation == 1 and h > self.height - ICON_SIZE:
+ self.palette_sprs[n][self.orientation].type = \
+ 'category-shift-vertical'
+ else:
+ self.palette_sprs[n][self.orientation].type = 'category'
+ if n == palette_names.index('trash'):
+ svg = SVG()
+ self.palette_sprs[n][self.orientation].set_shape(
+ svg_str_to_pixbuf(svg.palette(w, h)))
+
+ def _buttonpress_cb(self, win, event):
+ """ Button press """
+ self.window.grab_focus()
+ x, y = xy(event)
+ self.mouse_flag = 1
+ self.mouse_x = x
+ self.mouse_y = y
+ self.button_press(event.get_state() & gtk.gdk.CONTROL_MASK, x, y)
+ return True
+
+ def button_press(self, mask, x, y):
+ self.block_operation = 'click'
+
+ # Find out what was clicked
+ spr = self.sprite_list.find_sprite((x, y))
+
+ # Unselect things that may have been selected earlier
+ if self.selected_blk is not None:
+ if self.selected_blk.name == 'number' and \
+ spr in self.triangle_sprs:
+ # increment or decrement a number block
+ nf = float(self.selected_blk.spr.labels[0].replace(CURSOR, ''))
+ ni = int(nf)
+ if ni == nf:
+ n = ni
+ else:
+ n = nf
+ if spr == self.triangle_sprs[0]:
+ n += 1
+ else:
+ n -= 1
+ self.selected_blk.spr.set_label(str(n) + CURSOR)
+ return True
+ self._unselect_block()
+ self.selected_turtle = None
+
+ # Always hide the status layer on a click
+ if self.status_spr is not None:
+ self.status_spr.hide()
+
+ self.dx = 0
+ self.dy = 0
+ self.dragging_canvas[1] = x
+ self.dragging_canvas[2] = y
+ if spr is None:
+ self.dragging_canvas[0] = True
+ return True
+ self.dragging_canvas[0] = False
+ self.selected_spr = spr
+
+ # From the sprite at x, y, look for a corresponding block
+ blk = self.block_list.spr_to_block(spr)
+ if blk is not None:
+ if blk.type == 'block':
+ self.selected_blk = blk
+ self._block_pressed(x, y, blk)
+ elif blk.type == 'trash':
+ self._restore_from_trash(find_top_block(blk))
+ elif blk.type == 'proto':
+ if blk.name == 'restoreall':
+ self._restore_all_from_trash()
+ elif blk.name == 'restore':
+ self._restore_latest_from_trash()
+ elif blk.name == 'empty':
+ self._empty_trash()
+ elif blk.name == 'trashall':
+ for b in self.just_blocks():
+ if b.type != 'trash':
+ self._put_in_trash(find_top_block(b))
+ elif blk.name in MACROS:
+ self._new_macro(blk.name, x + 20, y + 20)
+ else:
+ # You can only have one instance of some blocks
+ if blk.name in ['start', 'hat1', 'hat2']:
+ if len(self.block_list.get_similar_blocks(
+ 'block', blk.name)) > 0:
+ self.showlabel('dupstack')
+ return True
+ # You cannot mix and match sensor blocks
+ elif blk.name in ['sound', 'volume', 'pitch']:
+ if len(self.block_list.get_similar_blocks(
+ 'block', ['resistance', 'voltage',
+ 'resistance2', 'voltage2'])) > 0:
+ self.showlabel('incompatible')
+ return True
+ elif blk.name in ['resistance', 'voltage',
+ 'resistance2', 'voltage2']:
+ if len(self.block_list.get_similar_blocks(
+ 'block', ['sound', 'volume', 'pitch'])) > 0:
+ self.showlabel('incompatible')
+ return True
+ if blk.name in ['resistance', 'resistance2']:
+ if len(self.block_list.get_similar_blocks(
+ 'block', ['voltage', 'voltage2'])) > 0:
+ self.showlabel('incompatible')
+ return True
+ elif blk.name in ['voltage', 'voltage2']:
+ if len(self.block_list.get_similar_blocks(
+ 'block', ['resistance',
+ 'resistance2'])) > 0:
+ self.showlabel('incompatible')
+ return True
+ blk.highlight()
+ self._new_block(blk.name, x, y)
+ blk.unhighlight()
+ return True
+
+ # Next, look for a turtle
+ t = self.turtles.spr_to_turtle(spr)
+ if t is not None:
+ # If turtle is shared, ignore click
+ if self.remote_turtle(t.get_name()):
+ return True
+ self.selected_turtle = t
+ self.canvas.set_turtle(self.turtles.get_turtle_key(t))
+ self._turtle_pressed(x, y)
+ return True
+
+ # Finally, check for anything else
+ if hasattr(spr, 'type'):
+ if spr.type == 'selector':
+ self._select_category(spr)
+ elif spr.type in ['category', 'category-shift-horizontal',
+ 'category-shift-vertical']:
+ if hide_button_hit(spr, x, y):
+ self.hideshow_palette(False)
+ elif spr.type == 'palette':
+ if spr.name == _('next'):
+ i = self.selected_palette + 1
+ if i == len(palette_names):
+ i = 0
+ if self.activity is None or \
+ not self.activity.has_toolbarbox:
+ self._select_category(self.selectors[i])
+ else:
+ if self.selected_palette is not None and \
+ not self.activity.has_toolbarbox:
+ self.activity.palette_buttons[
+ self.selected_palette].set_icon(
+ palette_names[self.selected_palette] + 'off')
+ else:
+ # select radio button associated with this palette
+ self.activity.palette_buttons[i].set_active(True)
+ if not self.activity.has_toolbarbox:
+ self.activity.palette_buttons[i].set_icon(
+ palette_names[i] + 'on')
+ self.show_palette(i)
+ elif spr.name == _('shift'):
+ self._shift_toolbar_palette(self.selected_palette)
+ else:
+ self.orientation = 1 - self.orientation
+ self.palette_button[self.orientation].set_layer(TAB_LAYER)
+ self.palette_button[1 - self.orientation].hide()
+ self.palette_sprs[self.selected_palette][
+ 1 - self.orientation].hide()
+ self._layout_palette(self.selected_palette)
+ self.show_palette(self.selected_palette)
+ elif spr.type == 'toolbar':
+ self._select_toolbar_button(spr)
+ return True
+
+ def _select_category(self, spr):
+ """ Select a category from the toolbar """
+ i = self.selectors.index(spr)
+ spr.set_shape(self.selector_shapes[i][1])
+ if self.selected_selector is not None:
+ j = self.selectors.index(self.selected_selector)
+ if i == j:
+ return
+ self.selected_selector.set_shape(self.selector_shapes[j][0])
+ self.previous_selector = self.selected_selector
+ self.selected_selector = spr
+ self.show_palette(i)
+
+ def _select_toolbar_button(self, spr):
+ """ Select a toolbar button (Used when not running Sugar). """
+ if not hasattr(spr, 'name'):
+ return
+ if spr.name == 'run-fastoff':
+ self.lc.trace = 0
+ self.hideblocks()
+ self.run_button(0)
+ elif spr.name == 'run-slowoff':
+ self.lc.trace = 1
+ self.run_button(3)
+ elif spr.name == 'debugoff':
+ self.lc.trace = 1
+ self.run_button(6)
+ elif spr.name == 'stopiton':
+ self.stop_button()
+ self.toolbar_shapes['stopiton'].hide()
+ elif spr.name == 'eraseron':
+ self.eraser_button()
+ elif spr.name == 'hideshowoff':
+ self.hideshow_button()
+
+ def _put_in_trash(self, blk, x=0, y=0):
+ """ Put a group of blocks into the trash. """
+ self.trash_stack.append(blk)
+ group = find_group(blk)
+ for gblk in group:
+ if gblk.status == 'collapsed':
+ # Collapsed stacks are restored for rescaling
+ # and then recollapsed after they are moved to the trash.
+ bot = find_sandwich_bottom(gblk)
+ if collapsed(bot):
+ dy = bot.values[0]
+ restore_stack(find_sandwich_top(gblk))
+ bot.values[0] = dy
+ gblk.type = 'trash'
+ gblk.rescale(self.trash_scale)
+ blk.spr.move((x, y))
+ for gblk in group:
+ self._adjust_dock_positions(gblk)
+
+ # Re-collapsing any stacks we had restored for scaling
+ for gblk in group:
+ if collapsed(gblk):
+ collapse_stack(find_sandwich_top(gblk))
+
+ # And resize any skins.
+ for gblk in group:
+ if gblk.name in BLOCKS_WITH_SKIN:
+ self._resize_skin(gblk)
+
+ # self.show_palette(palette_names.index('trash'))
+ if self.selected_palette != palette_names.index('trash'):
+ for gblk in group:
+ gblk.spr.hide()
+
+ def _restore_all_from_trash(self):
+ """ Restore all the blocks in the trash can. """
+ for blk in self.block_list.list:
+ if blk.type == 'trash':
+ self._restore_from_trash(blk)
+
+ def _restore_latest_from_trash(self):
+ """ Restore most recent blocks from the trash can. """
+ if len(self.trash_stack) == 0:
+ return
+ self._restore_from_trash(self.trash_stack[len(self.trash_stack) - 1])
+
+ def _restore_from_trash(self, blk):
+ group = find_group(blk)
+ for gblk in group:
+ gblk.rescale(self.block_scale)
+ gblk.spr.set_layer(BLOCK_LAYER)
+ x, y = gblk.spr.get_xy()
+ if self.orientation == 0:
+ gblk.spr.move((x, y + PALETTE_HEIGHT + self.toolbar_offset))
+ else:
+ gblk.spr.move((x + PALETTE_WIDTH, y))
+ gblk.type = 'block'
+ for gblk in group:
+ self._adjust_dock_positions(gblk)
+ # If the stack had been collapsed before going into the trash,
+ # collapse it again now.
+ for gblk in group:
+ if collapsed(gblk):
+ collapse_stack(find_sandwich_top(gblk))
+
+ # And resize any skins.
+ for gblk in group:
+ if gblk.name in BLOCKS_WITH_SKIN:
+ self._resize_skin(gblk)
+
+ self.trash_stack.remove(blk)
+
+ def _empty_trash(self):
+ """ Permanently remove all blocks presently in the trash can. """
+ for blk in self.block_list.list:
+ if blk.type == 'trash':
+ blk.type = 'deleted'
+ blk.spr.hide()
+ self.trash_stack = []
+
+ def _in_the_trash(self, x, y):
+ """ Is x, y over a palette? """
+ if self.selected_palette is not None and \
+ self.palette_sprs[self.selected_palette][self.orientation].hit(
+ (x, y)):
+ return True
+ return False
+
+ def _block_pressed(self, x, y, blk):
+ """ Block pressed """
+ if blk is not None:
+ blk.highlight()
+ self._disconnect(blk)
+ self.drag_group = find_group(blk)
+ (sx, sy) = blk.spr.get_xy()
+ self.drag_pos = x - sx, y - sy
+ for blk in self.drag_group:
+ if blk.status != 'collapsed':
+ blk.spr.set_layer(TOP_LAYER)
+ if len(blk.spr.labels) > 0:
+ self.saved_string = blk.spr.labels[0]
+ else:
+ self.saved_string = ''
+
+ def _unselect_block(self):
+ """ Unselect block """
+ # After unselecting a 'number' block, we need to check its value
+ if self.selected_blk.name == 'number':
+ self._number_check()
+ for spr in self.triangle_sprs:
+ spr.hide()
+ elif self.selected_blk.name == 'string':
+ self._string_check()
+ self.selected_blk.unhighlight()
+ self.selected_blk = None
+
+ def _new_block(self, name, x, y, defaults=None):
+ """ Make a new block. """
+ x_pos = x - 20
+ y_pos = y - 20
+ if name in content_blocks:
+ if defaults == None:
+ defaults = default_values[name]
+ newblk = Block(self.block_list, self.sprite_list, name, x_pos,
+ y_pos, 'block', defaults, self.block_scale)
+ else:
+ newblk = Block(self.block_list, self.sprite_list, name, x_pos,
+ y_pos, 'block', [], self.block_scale)
+
+ # Add a 'skin' to some blocks
+ if name in PYTHON_SKIN:
+ if self.nop == 'pythonloaded':
+ self._block_skin('pythonon', newblk)
+ else:
+ self._block_skin('pythonoff', newblk)
+ elif name in block_styles['box-style-media']:
+ if name in EXPAND_SKIN:
+ if newblk.ex == 0:
+ newblk.expand_in_x(EXPAND_SKIN[name][0])
+ if newblk.ey == 0:
+ newblk.expand_in_y(EXPAND_SKIN[name][1])
+ self._block_skin(name + 'off', newblk)
+
+ newspr = newblk.spr
+ newspr.set_layer(TOP_LAYER)
+ self.drag_pos = 20, 20
+ newblk.connections = [None] * len(newblk.docks)
+ if newblk.name in default_values:
+ if defaults == None:
+ defaults = default_values[newblk.name]
+ for i, argvalue in enumerate(defaults):
+ # skip the first dock position since it is always a connector
+ dock = newblk.docks[i + 1]
+ argname = dock[0]
+ if argname == 'unavailable':
+ continue
+ if argname == 'media':
+ argname = 'journal'
+ elif argname == 'number' and \
+ (type(argvalue) is str or type(argvalue) is unicode):
+ argname = 'string'
+ elif argname == 'bool':
+ argname = argvalue
+ elif argname == 'flow':
+ argname = argvalue
+ (sx, sy) = newspr.get_xy()
+ if argname is not None:
+ if argname in content_blocks:
+ argblk = Block(self.block_list, self.sprite_list,
+ argname, 0, 0, 'block', [argvalue],
+ self.block_scale)
+ else:
+ argblk = Block(self.block_list, self.sprite_list,
+ argname, 0, 0, 'block', [],
+ self.block_scale)
+ argdock = argblk.docks[0]
+ nx = sx + dock[2] - argdock[2]
+ ny = sy + dock[3] - argdock[3]
+ if argname == 'journal':
+ self._block_skin('journaloff', argblk)
+ argblk.spr.move((nx, ny))
+ argblk.spr.set_layer(TOP_LAYER)
+ argblk.connections = [newblk, None]
+ newblk.connections[i + 1] = argblk
+ self.drag_group = find_group(newblk)
+ self.block_operation = 'new'
+ if len(newblk.spr.labels) > 0 and newblk.spr.labels[0] is not None \
+ and newblk.name not in ['', 'number', 'string']:
+ if len(self.used_block_list) > 0:
+ self.used_block_list.append(', ')
+ if newblk.name in special_names:
+ self.used_block_list.append(special_names[newblk.name])
+ elif newblk.spr.labels[0] not in self.used_block_list:
+ self.used_block_list.append(newblk.spr.labels[0])
+
+ def _new_macro(self, name, x, y):
+ """ Create a "macro" (predefined stack of blocks). """
+ macro = MACROS[name]
+ macro[0][2] = x
+ macro[0][3] = y
+ top = self.process_data(macro)
+ self.block_operation = 'new'
+ self._check_collapsibles(top)
+ self.drag_group = find_group(top)
+
+ def process_data(self, block_data, offset=0):
+ """ Process block_data (from a macro, a file, or the clipboard). """
+
+ # Create the blocks (or turtle).
+ blocks = []
+ for blk in block_data:
+ if not self._found_a_turtle(blk):
+ blocks.append(self.load_block(blk, offset))
+
+ # Make the connections.
+ for i in range(len(blocks)):
+ cons = []
+ # Normally, it is simply a matter of copying the connections.
+ if blocks[i].connections is None:
+ if block_data[i][4] is not None:
+ for c in block_data[i][4]:
+ if c is None or c > (len(blocks) - 1):
+ cons.append(None)
+ else:
+ cons.append(blocks[c])
+ else:
+ debug_output("connection error %s" % (str(block_data[i])),
+ self.running_sugar)
+ cons.append(None)
+ elif blocks[i].connections == 'check':
+ # Convert old-style boolean and arithmetic blocks
+ cons.append(None) # Add an extra connection.
+ for c in block_data[i][4]:
+ if c is None:
+ cons.append(None)
+ else:
+ cons.append(blocks[c])
+ # If the boolean op was connected, readjust the plumbing.
+ if blocks[i].name in block_styles['boolean-style']:
+ if block_data[i][4][0] is not None:
+ c = block_data[i][4][0]
+ cons[0] = blocks[block_data[c][4][0]]
+ c0 = block_data[c][4][0]
+ for j, cj in enumerate(block_data[c0][4]):
+ if cj == c:
+ blocks[c0].connections[j] = blocks[i]
+ if c < i:
+ blocks[c].connections[0] = blocks[i]
+ blocks[c].connections[3] = None
+ else:
+ # Connection was to a block we haven't seen yet.
+ debug_output("Warning: dock to the future",
+ self.running_sugar)
+ else:
+ if block_data[i][4][0] is not None:
+ c = block_data[i][4][0]
+ cons[0] = blocks[block_data[c][4][0]]
+ c0 = block_data[c][4][0]
+ for j, cj in enumerate(block_data[c0][4]):
+ if cj == c:
+ blocks[c0].connections[j] = blocks[i]
+ if c < i:
+ blocks[c].connections[0] = blocks[i]
+ blocks[c].connections[1] = None
+ else:
+ # Connection was to a block we haven't seen yet.
+ debug_output("Warning: dock to the future",
+ self.running_sugar)
+ else:
+ debug_output("Warning: unknown connection state %s" % \
+ (str(blocks[i].connections)),
+ self.running_sugar)
+ blocks[i].connections = cons[:]
+
+ # Block sizes and shapes may have changed.
+ for blk in blocks:
+ self._adjust_dock_positions(blk)
+
+ # Look for any stacks that need to be collapsed or sandwiched
+ for blk in blocks:
+ if collapsed(blk):
+ collapse_stack(find_sandwich_top(blk))
+ elif blk.name == 'sandwichbottom' and collapsible(blk):
+ blk.svg.set_hide(True)
+ blk.svg.set_show(False)
+ blk.refresh()
+ grow_stack_arm(find_sandwich_top(blk))
+
+ # Resize blocks to current scale
+ self.resize_blocks(blocks)
+
+ if len(blocks) > 0:
+ return blocks[0]
+ else:
+ return None
+
+ def _adjust_dock_positions(self, blk):
+ """ Adjust the dock x, y positions """
+ if not self.interactive_mode:
+ return
+ (sx, sy) = blk.spr.get_xy()
+ for i, c in enumerate(blk.connections):
+ if i > 0 and c is not None and i < len(blk.docks):
+ bdock = blk.docks[i]
+ for j in range(len(c.docks)):
+ if j < len(c.connections) and c.connections[j] == blk:
+ cdock = c.docks[j]
+ nx = sx + bdock[2] - cdock[2]
+ ny = sy + bdock[3] - cdock[3]
+ c.spr.move((nx, ny))
+ self._adjust_dock_positions(c)
+
+ def _turtle_pressed(self, x, y):
+ (tx, ty) = self.selected_turtle.get_xy()
+ w = self.selected_turtle.spr.rect.width / 2
+ h = self.selected_turtle.spr.rect.height / 2
+ dx = x - tx - w
+ dy = y - ty - h
+ # if x, y is near the edge, rotate
+ if not hasattr(self.lc, 'value_blocks'):
+ self.lc.find_value_blocks()
+ self.lc.update_values = True
+ if (dx * dx) + (dy * dy) > ((w * w) + (h * h)) / 6:
+ self.drag_turtle = ('turn',
+ self.canvas.heading - atan2(dy, dx) / DEGTOR, 0)
+ else:
+ self.drag_turtle = ('move', x - tx, y - ty)
+
+ def _move_cb(self, win, event):
+ x, y = xy(event)
+ self.mouse_x = x
+ self.mouse_y = y
+ self._mouse_move(x, y)
+ return True
+
+ def _share_mouse_move(self):
+ ''' Share turtle movement and rotation after button up '''
+ if self.sharing():
+ nick = self.turtle_movement_to_share.get_name()
+ self.send_event("r|%s" % (
+ data_to_string([nick, round_int(self.canvas.heading)])))
+ if self.canvas.pendown:
+ self.send_event('p|%s' % (data_to_string([nick, False])))
+ put_pen_back_down = True
+ else:
+ put_pen_back_down = False
+ self.send_event("x|%s" % (
+ data_to_string([nick, [round_int(self.canvas.xcor),
+ round_int(self.canvas.ycor)]])))
+ if put_pen_back_down:
+ self.send_event('p|%s' % (data_to_string([nick, True])))
+ self.turtle_movement_to_share = None
+
+ def _mouse_move(self, x, y):
+ """ Process mouse movements """
+
+ if self.running_sugar and self.dragging_canvas[0]:
+ dx = self.dragging_canvas[1] - x
+ dy = self.dragging_canvas[2] - y
+ self.dragging_canvas[1] = x
+ self.dragging_canvas[2] = y
+ self.activity.adjust_sw(dx, dy)
+ return True
+
+ self.block_operation = 'move'
+
+ # First, check to see if we are dragging or rotating a turtle.
+ if self.selected_turtle is not None:
+ dtype, dragx, dragy = self.drag_turtle
+ (sx, sy) = self.selected_turtle.get_xy()
+ # self.canvas.set_turtle(self.selected_turtle.get_name())
+ if dtype == 'move':
+ dx = x - dragx - sx + self.selected_turtle.spr.rect.width / 2
+ dy = y - dragy - sy + self.selected_turtle.spr.rect.height / 2
+ self.selected_turtle.spr.set_layer(TOP_LAYER)
+ tx, ty = self.canvas.screen_to_turtle_coordinates(sx + dx,
+ sy + dy)
+ if self.canvas.pendown:
+ self.canvas.setpen(False)
+ self.canvas.setxy(tx, ty, share=False)
+ self.canvas.setpen(True)
+ else:
+ self.canvas.setxy(tx, ty, share=False)
+ else:
+ dx = x - sx - self.selected_turtle.spr.rect.width / 2
+ dy = y - sy - self.selected_turtle.spr.rect.height / 2
+ self.canvas.seth(int(dragx + atan2(dy, dx) / DEGTOR + 5) / \
+ 10 * 10, share=False)
+ self.lc.update_label_value('heading', self.canvas.heading)
+ self.turtle_movement_to_share = self.selected_turtle
+
+ # If we are hoving, show popup help.
+ elif self.drag_group is None:
+ self._show_popup(x, y)
+ return
+
+ # If we have a stack of blocks selected, move them.
+ elif self.drag_group[0] is not None:
+ blk = self.drag_group[0]
+
+ # Don't move a bottom blk if the stack is collapsed
+ if collapsed(blk):
+ return
+
+ self.selected_spr = blk.spr
+ dragx, dragy = self.drag_pos
+ (sx, sy) = blk.spr.get_xy()
+ dx = x - dragx - sx
+ dy = y - dragy - sy
+
+ # Take no action if there was a move of 0, 0.
+ if dx == 0 and dy == 0:
+ return
+
+ self.drag_group = find_group(blk)
+
+ # Prevent blocks from ending up with a negative x or y
+ for blk in self.drag_group:
+ (bx, by) = blk.spr.get_xy()
+ if bx + dx < 0:
+ dx = -bx
+ if by + dy < 0:
+ dy = -by
+
+ # Calculate a bounding box and only invalidate once.
+ minx = blk.spr.rect.x
+ miny = blk.spr.rect.y
+ maxx = blk.spr.rect.x + blk.spr.rect.width
+ maxy = blk.spr.rect.y + blk.spr.rect.height
+
+ for blk in self.drag_group:
+ if blk.spr.rect.x < minx:
+ minx = blk.spr.rect.x
+ if blk.spr.rect.x + blk.spr.rect.width > maxx:
+ maxx = blk.spr.rect.x + blk.spr.rect.width
+ if blk.spr.rect.y < miny:
+ miny = blk.spr.rect.y
+ if blk.spr.rect.y + blk.spr.rect.height > maxy:
+ maxy = blk.spr.rect.y + blk.spr.rect.height
+ blk.spr.rect.x += dx
+ blk.spr.rect.y += dy
+
+ if dx < 0:
+ minx += dx
+ else:
+ maxx += dx
+ if dy < 0:
+ miny += dy
+ else:
+ maxy += dy
+
+ self.rect.x = minx
+ self.rect.y = miny
+ self.rect.width = maxx - minx
+ self.rect.height = maxy - miny
+ self.window.queue_draw_area(self.rect.x,
+ self.rect.y,
+ self.rect.width,
+ self.rect.height)
+ self.dx += dx
+ self.dy += dy
+
+ def _show_popup(self, x, y):
+ """ Let's help our users by displaying a little help. """
+ spr = self.sprite_list.find_sprite((x, y))
+ blk = self.block_list.spr_to_block(spr)
+ if spr and blk is not None:
+ if self.timeout_tag[0] == 0:
+ self.timeout_tag[0] = self._do_show_popup(blk.name)
+ self.selected_spr = spr
+ else:
+ if self.timeout_tag[0] > 0:
+ try:
+ gobject.source_remove(self.timeout_tag[0])
+ self.timeout_tag[0] = 0
+ except:
+ self.timeout_tag[0] = 0
+ elif spr and hasattr(spr, 'type') and (spr.type == 'selector' or \
+ spr.type == 'palette' or \
+ spr.type == 'toolbar'):
+ if self.timeout_tag[0] == 0 and hasattr(spr, 'name'):
+ self.timeout_tag[0] = self._do_show_popup(spr.name)
+ self.selected_spr = spr
+ else:
+ if self.timeout_tag[0] > 0:
+ try:
+ gobject.source_remove(self.timeout_tag[0])
+ self.timeout_tag[0] = 0
+ except:
+ self.timeout_tag[0] = 0
+ else:
+ if self.timeout_tag[0] > 0:
+ try:
+ gobject.source_remove(self.timeout_tag[0])
+ self.timeout_tag[0] = 0
+ except:
+ self.timeout_tag[0] = 0
+
+ def _do_show_popup(self, block_name):
+ """ Fetch the help text and display it. """
+ if self.no_help:
+ return 0
+ if block_name in special_names:
+ special_block_name = special_names[block_name]
+ elif block_name in block_names:
+ special_block_name = block_names[block_name][0]
+ elif block_name in TOOLBAR_SHAPES:
+ special_block_name = ''
+ else:
+ special_block_name = _(block_name)
+ if block_name in help_strings:
+ label = help_strings[block_name]
+ else:
+ label = special_block_name
+ if self.last_label == label:
+ return 0
+ self.showlabel('help', label=label)
+ self.last_label = label
+ return 0
+
+ def _buttonrelease_cb(self, win, event):
+ """ Button release """
+ x, y = xy(event)
+ self.mouse_flag = 0
+ self.mouse_x = x
+ self.mouse_y = y
+ self.button_release(x, y)
+ if self.turtle_movement_to_share is not None:
+ self._share_mouse_move()
+ return True
+
+ def button_release(self, x, y):
+ if self.running_sugar and self.dragging_canvas[0]:
+ self.dragging_canvas[0] = False
+ self.dragging_canvas[1] = x
+ self.dragging_canvas[2] = y
+ self.activity.adjust_palette()
+ return True
+
+ # We may have been moving the turtle
+ if self.selected_turtle is not None:
+ (tx, ty) = self.selected_turtle.get_xy()
+ k = self.turtles.get_turtle_key(self.selected_turtle)
+
+ # Remove turtles by dragging them onto the trash palette.
+ if self._in_the_trash(tx, ty):
+ # If it is the default turtle, just recenter it.
+ if k == self.default_turtle_name:
+ self._move_turtle(0, 0)
+ self.canvas.heading = 0
+ self.canvas.turn_turtle()
+ self.lc.update_label_value('heading', self.canvas.heading)
+ else:
+ self.selected_turtle.hide()
+ self.turtles.remove_from_dict(k)
+ self.active_turtle = None
+ else:
+ self._move_turtle(tx - self.canvas.width / 2 + \
+ self.active_turtle.spr.rect.width / 2,
+ self.canvas.height / 2 - ty - \
+ self.active_turtle.spr.rect.height / 2)
+ self.selected_turtle = None
+ if self.active_turtle is None:
+ self.canvas.set_turtle(self.default_turtle_name)
+ return
+
+ # If we don't have a group of blocks, then there is nothing to do.
+ if self.drag_group is None:
+ return
+
+ blk = self.drag_group[0]
+ # Remove blocks by dragging them onto any palette.
+ if self.block_operation == 'move' and self._in_the_trash(x, y):
+ self._put_in_trash(blk, x, y)
+ self.drag_group = None
+ return
+
+ # Pull a stack of new blocks off of the category palette.
+ if self.block_operation == 'new':
+ for gblk in self.drag_group:
+ (bx, by) = gblk.spr.get_xy()
+ if self.orientation == 0:
+ gblk.spr.move((bx + 20,
+ by + PALETTE_HEIGHT + self.toolbar_offset))
+ else:
+ gblk.spr.move((bx + PALETTE_WIDTH, by + 20))
+
+ # Look to see if we can dock the current stack.
+ self._snap_to_dock()
+ self._check_collapsibles(blk)
+ for gblk in self.drag_group:
+ if gblk.status != 'collapsed':
+ gblk.spr.set_layer(BLOCK_LAYER)
+ self.drag_group = None
+
+ # Find the block we clicked on and process it.
+ # Consider a very small move a click (for touch interfaces)
+ if self.block_operation == 'click' or \
+ (self.hw in [XO175, XO30] and self.block_operation == 'move' and (
+ abs(self.dx) < MOTION_THRESHOLD and \
+ abs(self.dy < MOTION_THRESHOLD))):
+ self._click_block(x, y)
+
+ def remote_turtle(self, name):
+ ''' Is this a remote turtle? '''
+ if name == self.nick:
+ return False
+ if hasattr(self, 'remote_turtle_dictionary') and \
+ name in self.remote_turtle_dictionary:
+ return True
+ return False
+
+ def label_remote_turtle(self, name, colors=['#A0A0A0', '#C0C0C0']):
+ ''' Add a label to remote turtles '''
+ turtle = self.turtles.get_turtle(name)
+ if turtle is not None:
+ turtle.label_block = Block(self.block_list,
+ self.sprite_list, 'turtle-label', 0, 0,
+ 'label', [], 1.0 / self.scale,
+ colors)
+ turtle.label_block.spr.set_label_attributes(6.0 / self.scale)
+ if len(name) > 6:
+ turtle.label_block.spr.set_label(name[0:4] + '…')
+ else:
+ turtle.label_block.spr.set_label(name)
+ turtle.show()
+
+ def _move_turtle(self, x, y):
+ """ Move the selected turtle to (x, y). """
+ self.canvas.xcor = x
+ self.canvas.ycor = y
+ self.canvas.move_turtle()
+ if self.interactive_mode:
+ self.display_coordinates()
+ if self.running_sugar:
+ self.selected_turtle.spr.set_layer(TURTLE_LAYER)
+ self.lc.update_label_value('xcor',
+ self.canvas.xcor / self.coord_scale)
+ self.lc.update_label_value('ycor',
+ self.canvas.ycor / self.coord_scale)
+
+ def _click_block(self, x, y):
+ """ Click block: lots of special cases to handle... """
+ blk = self.block_list.spr_to_block(self.selected_spr)
+ if blk is None:
+ return
+ self.selected_blk = blk
+
+ if blk.name == 'number' or blk.name == 'string':
+ self.saved_string = blk.spr.labels[0]
+ blk.spr.labels[0] += CURSOR
+ if blk.name == 'number':
+ bx, by = blk.spr.get_xy()
+ bw, bh = blk.spr.get_dimensions()
+ tw, th = self.triangle_sprs[0].get_dimensions()
+ for spr in self.triangle_sprs:
+ spr.set_layer(TOP_LAYER)
+ self.triangle_sprs[0].move((int(bx + (bw - tw) / 2), by - th))
+ self.triangle_sprs[1].move((int(bx + (bw - tw) / 2), by + bh))
+
+ elif blk.name in block_styles['box-style-media'] and \
+ blk.name not in NO_IMPORT:
+ self._import_from_journal(self.selected_blk)
+ if blk.name == 'journal' and self.running_sugar:
+ self._load_description_block(blk)
+
+ elif blk.name == 'identity2' or blk.name == 'hspace':
+ group = find_group(blk)
+ if hide_button_hit(blk.spr, x, y):
+ dx = blk.reset_x()
+ elif show_button_hit(blk.spr, x, y):
+ dx = 20
+ blk.expand_in_x(dx)
+ else:
+ self._run_stack(blk)
+ return
+ for gblk in group:
+ if gblk != blk:
+ gblk.spr.move_relative((dx * blk.scale, 0))
+
+ elif blk.name == 'vspace':
+ group = find_group(blk)
+ if hide_button_hit(blk.spr, x, y):
+ dy = blk.reset_y()
+ elif show_button_hit(blk.spr, x, y):
+ dy = 20
+ blk.expand_in_y(dy)
+ else:
+ self._run_stack(blk)
+ return
+ for gblk in group:
+ if gblk != blk:
+ gblk.spr.move_relative((0, dy * blk.scale))
+ grow_stack_arm(find_sandwich_top(blk))
+
+ elif blk.name in expandable_blocks:
+ # Connection may be lost during expansion, so store it...
+ blk0 = blk.connections[0]
+ if blk0 is not None:
+ dock0 = blk0.connections.index(blk)
+
+ if hide_button_hit(blk.spr, x, y):
+ dy = blk.reset_y()
+ elif show_button_hit(blk.spr, x, y):
+ dy = 20
+ blk.expand_in_y(dy)
+ else:
+ self._run_stack(blk)
+ return
+
+ if blk.name in block_styles['boolean-style']:
+ self._expand_boolean(blk, blk.connections[2], dy)
+ else:
+ self._expand_expandable(blk, blk.connections[1], dy)
+
+ # and restore it...
+ if blk0 is not None:
+ blk.connections[0] = blk0
+ blk0.connections[dock0] = blk
+ self._cascade_expandable(blk)
+
+ grow_stack_arm(find_sandwich_top(blk))
+
+ elif blk.name in EXPANDABLE_ARGS or blk.name == 'nop':
+ if show_button_hit(blk.spr, x, y):
+ n = len(blk.connections)
+ group = find_group(blk.connections[n - 1])
+ if blk.name == 'myfunc1arg':
+ blk.spr.labels[1] = 'f(x, y)'
+ blk.spr.labels[2] = ' '
+ dy = blk.add_arg()
+ blk.primitive = 'myfunction2'
+ blk.name = 'myfunc2arg'
+ elif blk.name == 'myfunc2arg':
+ blk.spr.labels[1] = 'f(x, y, z)'
+ dy = blk.add_arg(False)
+ blk.primitive = 'myfunction3'
+ blk.name = 'myfunc3arg'
+ elif blk.name == 'userdefined':
+ dy = blk.add_arg()
+ blk.primitive = 'userdefined2'
+ blk.name = 'userdefined2args'
+ self._resize_skin(blk)
+ elif blk.name == 'userdefined2args':
+ dy = blk.add_arg(False)
+ blk.primitive = 'userdefined3'
+ blk.name = 'userdefined3args'
+ self._resize_skin(blk)
+ else:
+ dy = blk.add_arg()
+ for gblk in group:
+ gblk.spr.move_relative((0, dy))
+ blk.connections.append(blk.connections[n - 1])
+ argname = blk.docks[n - 1][0]
+ argvalue = default_values[blk.name][len(
+ default_values[blk.name]) - 1]
+ argblk = Block(self.block_list, self.sprite_list, argname,
+ 0, 0, 'block', [argvalue], self.block_scale)
+ argdock = argblk.docks[0]
+ (bx, by) = blk.spr.get_xy()
+ nx = bx + blk.docks[n - 1][2] - argdock[2]
+ ny = by + blk.docks[n - 1][3] - argdock[3]
+ argblk.spr.move((nx, ny))
+ argblk.spr.set_layer(TOP_LAYER)
+ argblk.connections = [blk, None]
+ blk.connections[n - 1] = argblk
+ if blk.name in block_styles['number-style-var-arg']:
+ self._cascade_expandable(blk)
+ grow_stack_arm(find_sandwich_top(blk))
+ elif blk.name in PYTHON_SKIN:
+ self._import_py()
+ else:
+ self._run_stack(blk)
+
+ elif blk.name in ['sandwichtop_no_arm_no_label',
+ 'sandwichtop_no_arm']:
+ restore_stack(blk)
+
+ elif blk.name in COLLAPSIBLE or blk.name == 'sandwichtop_no_label':
+ if blk.name == 'sandwichtop_no_label':
+ if hide_button_hit(blk.spr, x, y):
+ collapse_stack(blk)
+ else:
+ self._run_stack(blk)
+ top = find_sandwich_top(blk)
+ if collapsed(blk):
+ restore_stack(top) # deprecated (bottom block is invisible)
+ elif top is not None:
+ collapse_stack(top)
+ else:
+ self._run_stack(blk)
+
+ def _expand_boolean(self, blk, blk2, dy):
+ """ Expand a boolean blk if blk2 is too big to fit. """
+ group = find_group(blk2)
+ for gblk in find_group(blk):
+ if gblk not in group:
+ gblk.spr.move_relative((0, -dy * blk.scale))
+
+ def _expand_expandable(self, blk, blk2, dy):
+ """ Expand an expandable blk if blk2 is too big to fit. """
+ if blk2 is None:
+ group = [blk]
+ else:
+ group = find_group(blk2)
+ group.append(blk)
+ for gblk in find_group(blk):
+ if gblk not in group:
+ gblk.spr.move_relative((0, dy * blk.scale))
+ if blk.name in block_styles['compare-style'] or \
+ blk.name in block_styles['compare-porch-style']:
+ for gblk in find_group(blk):
+ gblk.spr.move_relative((0, -dy * blk.scale))
+
+ def _number_style(self, name):
+ if name in block_styles['number-style']:
+ return True
+ if name in block_styles['number-style-porch']:
+ return True
+ if name in block_styles['number-style-block']:
+ return True
+ if name in block_styles['number-style-var-arg']:
+ return True
+ return False
+
+ def _cascade_expandable(self, blk):
+ """ If expanding/shrinking a block, cascade. """
+ while self._number_style(blk.name):
+ if blk.connections[0] is None:
+ break
+ if blk.connections[0].name in expandable_blocks:
+ if blk.connections[0].connections.index(blk) != 1:
+ break
+ blk = blk.connections[0]
+ if blk.connections[1].name == 'myfunc2arg':
+ dy = 40 + blk.connections[1].ey - blk.ey
+ elif blk.connections[1].name == 'myfunc3arg':
+ dy = 60 + blk.connections[1].ey - blk.ey
+ else:
+ dy = 20 + blk.connections[1].ey - blk.ey
+ blk.expand_in_y(dy)
+ if dy != 0:
+ group = find_group(blk.connections[1])
+ group.append(blk)
+ for gblk in find_group(blk):
+ if gblk not in group:
+ gblk.spr.move_relative((0, dy * blk.scale))
+ if blk.name in block_styles['compare-style'] or \
+ blk.name in block_styles['compare-porch-style']:
+ for gblk in find_group(blk):
+ gblk.spr.move_relative((0, -dy * blk.scale))
+ else:
+ break
+
+ def _check_collapsibles(self, blk):
+ """ Check state of collapsible blocks upon change in dock state. """
+ group = find_group(blk)
+ for gblk in group:
+ if gblk.name in COLLAPSIBLE:
+ if collapsed(gblk):
+ gblk.svg.set_show(True)
+ gblk.svg.set_hide(False)
+ reset_stack_arm(find_sandwich_top(gblk))
+ elif collapsible(gblk):
+ gblk.svg.set_hide(True)
+ gblk.svg.set_show(False)
+ grow_stack_arm(find_sandwich_top(gblk))
+ else:
+ gblk.svg.set_hide(False)
+ gblk.svg.set_show(False)
+ # Ouch: When you tear off the sandwich bottom, you
+ # no longer have access to the group with the sandwich top
+ # so check them all.
+ for b in self.just_blocks():
+ if b.name in ['sandwichtop', 'sandwichtop_no_label']:
+ if find_sandwich_bottom(b) is None:
+ reset_stack_arm(b)
+ gblk.refresh()
+
+ def _run_stack(self, blk):
+ """ Run a stack of blocks. """
+ if blk is None:
+ return
+ self.lc.find_value_blocks() # Are there blocks to update?
+ # Is there a savesvg block?
+ if len(self.block_list.get_similar_blocks('block', 'savesvg')) > 0:
+ if self.canvas.cr_svg is None:
+ self.canvas.setup_svg_surface()
+ self._start_plugins() # Let the plugins know we are running.
+ top = find_top_block(blk)
+ self.lc.run_blocks(top, self.just_blocks(), True)
+ if self.interactive_mode:
+ gobject.idle_add(self.lc.doevalstep)
+ else:
+ while self.lc.doevalstep():
+ pass
+
+ def _snap_to_dock(self):
+ """ Snap a block (selected_block) to the dock of another block
+ (destination_block).
+ """
+ selected_block = self.drag_group[0]
+ best_destination = None
+ d = SNAP_THRESHOLD
+ for selected_block_dockn in range(len(selected_block.docks)):
+ for destination_block in self.just_blocks():
+ # Don't link to a block that is hidden
+ if destination_block.status == 'collapsed':
+ continue
+ # Don't link to a block to which you're already connected
+ if destination_block in self.drag_group:
+ continue
+ # Check each dock of destination for a possible connection
+ for destination_dockn in range(len(destination_block.docks)):
+ this_xy = dock_dx_dy(destination_block, destination_dockn,
+ selected_block, selected_block_dockn)
+ if magnitude(this_xy) > d:
+ continue
+ d = magnitude(this_xy)
+ best_xy = this_xy
+ best_destination = destination_block
+ best_destination_dockn = destination_dockn
+ best_selected_block_dockn = selected_block_dockn
+ if d < SNAP_THRESHOLD:
+ # Some combinations of blocks are not valid
+ if not arithmetic_check(selected_block, best_destination,
+ best_selected_block_dockn,
+ best_destination_dockn):
+ return
+ if not journal_check(selected_block, best_destination,
+ best_selected_block_dockn,
+ best_destination_dockn):
+ return
+
+ # Move the selected blocks into the docked position
+ for blk in self.drag_group:
+ (sx, sy) = blk.spr.get_xy()
+ blk.spr.move((sx + best_xy[0], sy + best_xy[1]))
+
+ # If there was already a block docked there, move it to the trash.
+ blk_in_dock = best_destination.connections[best_destination_dockn]
+ if blk_in_dock is not None and blk_in_dock != selected_block:
+ blk_in_dock.connections[0] = None
+ self._put_in_trash(blk_in_dock)
+
+ # Note the connection in destination dock
+ best_destination.connections[best_destination_dockn] = \
+ selected_block
+
+ # And in the selected block dock
+ if selected_block.connections is not None:
+ if best_selected_block_dockn < len(selected_block.connections):
+ selected_block.connections[best_selected_block_dockn] = \
+ best_destination
+
+ # Some destination blocks expand to accomodate large blocks
+ if best_destination.name in block_styles['boolean-style']:
+ if best_destination_dockn == 2 and \
+ (selected_block.name in block_styles['boolean-style'] or \
+ selected_block.name in block_styles['compare-style'] or \
+ selected_block.name in block_styles['compare-porch-style']
+ ):
+ dy = selected_block.ey - best_destination.ey
+ if selected_block.name in block_styles['boolean-style']:
+ # Even without expanding, boolean blocks are
+ # too large to fit in the lower dock position
+ dy += 45
+ best_destination.expand_in_y(dy)
+ self._expand_boolean(best_destination, selected_block, dy)
+ elif best_destination.name in expandable_blocks and \
+ best_destination_dockn == 1:
+ dy = 0
+ if (selected_block.name in expandable_blocks or
+ selected_block.name in block_styles[
+ 'number-style-var-arg']):
+ if selected_block.name == 'myfunc2arg':
+ dy = 40 + selected_block.ey - best_destination.ey
+ elif selected_block.name == 'myfunc3arg':
+ dy = 60 + selected_block.ey - best_destination.ey
+ else:
+ dy = 20 + selected_block.ey - best_destination.ey
+ best_destination.expand_in_y(dy)
+ else:
+ if best_destination.ey > 0:
+ dy = best_destination.reset_y()
+ if dy != 0:
+ self._expand_expandable(
+ best_destination, selected_block, dy)
+ self._cascade_expandable(best_destination)
+ grow_stack_arm(find_sandwich_top(best_destination))
+
+ def _disconnect(self, blk):
+ """ Disconnect block from stack above it. """
+ if blk is None:
+ return
+ if blk.connections[0] is None:
+ return
+ if collapsed(blk):
+ return
+ blk2 = blk.connections[0]
+ if blk in blk2.connections:
+ c = blk2.connections.index(blk)
+ blk2.connections[c] = None
+
+ if blk2.name in block_styles['boolean-style']:
+ if c == 2 and blk2.ey > 0:
+ dy = -blk2.ey
+ blk2.expand_in_y(dy)
+ self._expand_boolean(blk2, blk, dy)
+ elif blk2.name in expandable_blocks and c == 1:
+ if blk2.ey > 0:
+ dy = blk2.reset_y()
+ if dy != 0:
+ self._expand_expandable(blk2, blk, dy)
+ self._cascade_expandable(blk2)
+ grow_stack_arm(find_sandwich_top(blk2))
+
+ blk.connections[0] = None
+
+ def _import_from_journal(self, blk):
+ """ Import a file from the Sugar Journal """
+ # TODO: check blk name to set filter
+ if self.running_sugar:
+ chooser(self.parent, '', self._update_media_blk)
+ else:
+ fname, self.load_save_folder = get_load_name('.*',
+ self.load_save_folder)
+ if fname is None:
+ return
+ self._update_media_icon(blk, fname)
+
+ def _load_description_block(self, blk):
+ """ Look for a corresponding description block """
+ if blk is None or blk.name != 'journal' or len(blk.values) == 0 or \
+ blk.connections[0] is None:
+ return
+ _blk = blk.connections[0]
+ dblk = find_blk_below(_blk, 'description')
+ # Autoupdate the block if it is empty
+ if dblk != None and (len(dblk.values) == 0 or dblk.values[0] is None):
+ self._update_media_icon(dblk, None, blk.values[0])
+
+ def _update_media_blk(self, dsobject):
+ """ Called from the chooser to load a media block """
+ self._update_media_icon(self.selected_blk, dsobject,
+ dsobject.object_id)
+
+ def _update_media_icon(self, blk, name, value=''):
+ """ Update the icon on a 'loaded' media block. """
+ if blk.name == 'journal':
+ self._load_image_thumb(name, blk)
+ elif blk.name == 'audio':
+ self._block_skin('audioon', blk)
+ elif blk.name == 'video':
+ self._block_skin('videoon', blk)
+ else:
+ self._block_skin('descriptionon', blk)
+ if value == '':
+ value = name
+ if len(blk.values) > 0:
+ blk.values[0] = value
+ else:
+ blk.values.append(value)
+ blk.spr.set_label(' ')
+
+ def _load_image_thumb(self, picture, blk):
+ """ Replace icon with a preview image. """
+ pixbuf = None
+ self._block_skin('descriptionon', blk)
+
+ if self.running_sugar:
+ w, h = calc_image_size(blk.spr)
+ pixbuf = get_pixbuf_from_journal(picture, w, h)
+ else:
+ if movie_media_type(picture):
+ self._block_skin('videoon', blk)
+ blk.name = 'video'
+ elif audio_media_type(picture):
+ self._block_skin('audioon', blk)
+ blk.name = 'audio'
+ elif image_media_type(picture):
+ w, h = calc_image_size(blk.spr)
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(picture, w, h)
+ else:
+ blk.name = 'description'
+ if pixbuf is not None:
+ x, y = self._calc_image_offset('', blk.spr)
+ blk.set_image(pixbuf, x, y)
+ self._resize_skin(blk)
+
+ def _keypress_cb(self, area, event):
+ """ Keyboard """
+ keyname = gtk.gdk.keyval_name(event.keyval)
+ keyunicode = gtk.gdk.keyval_to_unicode(event.keyval)
+ if event.get_state() & gtk.gdk.MOD1_MASK:
+ alt_mask = True
+ alt_flag = 'T'
+ else:
+ alt_mask = False
+ alt_flag = 'F'
+ self._key_press(alt_mask, keyname, keyunicode)
+ return keyname
+
+ def _key_press(self, alt_mask, keyname, keyunicode):
+ if keyname is None:
+ return False
+
+ self.keypress = keyname
+
+ if alt_mask:
+ if keyname == "p":
+ self.hideshow_button()
+ elif keyname == 'q':
+ self.quit_plugins()
+ if self.gst_available:
+ stop_media(self.lc)
+ exit()
+ elif keyname == 'g':
+ self._align_to_grid()
+
+ elif self.selected_blk is not None:
+ if self.selected_blk.name == 'number':
+ self._process_numeric_input(keyname)
+ elif self.selected_blk.name == 'string':
+ self.process_alphanumeric_input(keyname, keyunicode)
+ if self.selected_blk is not None:
+ self.selected_blk.resize()
+ elif self.selected_blk.name != 'proto':
+ self._process_keyboard_commands(keyname, block_flag=True)
+
+ elif self.turtles.spr_to_turtle(self.selected_spr) is not None:
+ self._process_keyboard_commands(keyname, block_flag=False)
+
+ return True
+
+ def _process_numeric_input(self, keyname):
+ ''' Make sure numeric input is valid. '''
+ oldnum = self.selected_blk.spr.labels[0].replace(CURSOR, '')
+ if len(oldnum) == 0:
+ oldnum = '0'
+ if keyname == 'minus':
+ if oldnum == '0':
+ newnum = '-'
+ elif oldnum[0] != '-':
+ newnum = '-' + oldnum
+ else:
+ newnum = oldnum
+ elif keyname == 'comma' and self.decimal_point == ',' and \
+ ',' not in oldnum:
+ newnum = oldnum + ','
+ elif keyname == 'period' and self.decimal_point == '.' and \
+ '.' not in oldnum:
+ newnum = oldnum + '.'
+ elif keyname == 'BackSpace':
+ if len(oldnum) > 0:
+ newnum = oldnum[:len(oldnum) - 1]
+ else:
+ newnum = ''
+ elif keyname in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
+ if oldnum == '0':
+ newnum = keyname
+ else:
+ newnum = oldnum + keyname
+ elif keyname == 'Return':
+ self._unselect_block()
+ return
+ else:
+ newnum = oldnum
+ if newnum == '.':
+ newnum = '0.'
+ if newnum == ',':
+ newnum = '0,'
+ if len(newnum) > 0 and newnum != '-':
+ try:
+ float(newnum.replace(self.decimal_point, '.'))
+ except ValueError, e:
+ newnum = oldnum
+ self.selected_blk.spr.set_label(newnum + CURSOR)
+
+ def process_alphanumeric_input(self, keyname, keyunicode):
+ """ Make sure alphanumeric input is properly parsed. """
+ if len(self.selected_blk.spr.labels[0]) > 0:
+ c = self.selected_blk.spr.labels[0].count(CURSOR)
+ if c == 0:
+ oldleft = self.selected_blk.spr.labels[0]
+ oldright = ''
+ elif len(self.selected_blk.spr.labels[0]) == 1:
+ oldleft = ''
+ oldright = ''
+ else:
+ try: # Why are getting a ValueError on occasion?
+ oldleft, oldright = \
+ self.selected_blk.spr.labels[0].split(CURSOR)
+ except ValueError:
+ debug_output("[%s]" % self.selected_blk.spr.labels[0],
+ self.running_sugar)
+ oldleft = self.selected_blk.spr.labels[0]
+ oldright = ''
+ else:
+ oldleft = ''
+ oldright = ''
+ newleft = oldleft
+ if keyname in ['Shift_L', 'Shift_R', 'Control_L', 'Caps_Lock', \
+ 'Alt_L', 'Alt_R', 'KP_Enter', 'ISO_Level3_Shift']:
+ keyname = ''
+ keyunicode = 0
+ # Hack until I sort out input and unicode and dead keys,
+ if keyname[0:5] == 'dead_':
+ self.dead_key = keyname
+ keyname = ''
+ keyunicode = 0
+ if keyname == 'space':
+ keyunicode = 32
+ elif keyname == 'Tab':
+ keyunicode = 9
+ if keyname == 'BackSpace':
+ if len(oldleft) > 1:
+ newleft = oldleft[:len(oldleft) - 1]
+ else:
+ newleft = ''
+ if keyname == 'Delete':
+ if len(oldright) > 0:
+ oldright = oldright[1:]
+ elif keyname == 'Home':
+ oldright = oldleft + oldright
+ newleft = ''
+ elif keyname == 'Left':
+ if len(oldleft) > 0:
+ oldright = oldleft[len(oldleft) - 1:] + oldright
+ newleft = oldleft[:len(oldleft) - 1]
+ elif keyname == 'Right':
+ if len(oldright) > 0:
+ newleft = oldleft + oldright[0]
+ oldright = oldright[1:]
+ elif keyname == 'End':
+ newleft = oldleft + oldright
+ oldright = ''
+ elif keyname == 'Return':
+ newleft = oldleft + RETURN
+ elif keyname == 'Down':
+ self._unselect_block()
+ return
+ elif keyname == 'Up' or keyname == 'Escape': # Restore previous state
+ self.selected_blk.spr.set_label(self.saved_string)
+ self._unselect_block()
+ return
+ else:
+ if self.dead_key is not '':
+ keyunicode = \
+ DEAD_DICTS[DEAD_KEYS.index(self.dead_key[5:])][keyname]
+ self.dead_key = ''
+ if keyunicode > 0:
+ if unichr(keyunicode) != '\x00':
+ newleft = oldleft + unichr(keyunicode)
+ else:
+ newleft = oldleft
+ elif keyunicode == -1: # clipboard text
+ if keyname == '\n':
+ newleft = oldleft + RETURN
+ else:
+ newleft = oldleft + keyname
+ self.selected_blk.spr.set_label("%s%s%s" % (newleft, CURSOR, oldright))
+
+ def _process_keyboard_commands(self, keyname, block_flag=True):
+ """ Use the keyboard to move blocks and turtle """
+ mov_dict = {'KP_Up': [0, 20], 'j': [0, 20], 'Up': [0, 20],
+ 'KP_Down': [0, -20], 'k': [0, -20], 'Down': [0, -20],
+ 'KP_Left': [-20, 0], 'h': [-20, 0], 'Left': [-20, 0],
+ 'KP_Right': [20, 0], 'l': [20, 0], 'Right': [20, 0],
+ 'KP_Page_Down': [-1, -1], 'Page_Down': [-1, -1],
+ 'KP_Page_Up': [-1, -1], 'Page_Up': [-1, -1],
+ 'KP_End': [0, 0], 'End': [0, 0],
+ 'KP_Home': [0, 0], 'Home': [0, 0], 'space': [0, 0],
+ 'Return': [-1, -1], 'Esc': [-1, -1]}
+
+ if keyname not in mov_dict:
+ return True
+
+ if keyname in ['KP_End', 'End']:
+ self.run_button(0)
+ elif self.selected_spr is not None:
+ if not self.lc.running and block_flag:
+ blk = self.block_list.spr_to_block(self.selected_spr)
+ if keyname in ['Return', 'KP_Page_Up', 'Page_Up', 'Esc']:
+ (x, y) = blk.spr.get_xy()
+ self._click_block(x, y)
+ elif keyname in ['KP_Page_Down', 'Page_Down']:
+ if self.drag_group is None:
+ self.drag_group = find_group(blk)
+ self._put_in_trash(blk)
+ self.drag_group = None
+ elif keyname in ['KP_Home', 'Home', 'space']:
+ block = self.block_list.spr_to_block(self.selected_spr)
+ if block is None:
+ return True
+ block.unhighlight()
+ block = self.block_list.get_next_block_of_same_type(
+ block)
+ if block is not None:
+ self.selected_spr = block.spr
+ block.highlight()
+ else:
+ self._jog_block(blk, mov_dict[keyname][0],
+ mov_dict[keyname][1])
+ elif not block_flag:
+ self._jog_turtle(mov_dict[keyname][0], mov_dict[keyname][1])
+ return True
+
+ def _jog_turtle(self, dx, dy):
+ """ Jog turtle """
+ if dx == -1 and dy == -1:
+ self.canvas.xcor = 0
+ self.canvas.ycor = 0
+ else:
+ self.canvas.xcor += dx
+ self.canvas.ycor += dy
+ self.active_turtle = self.turtles.spr_to_turtle(self.selected_spr)
+ self.canvas.move_turtle()
+ self.display_coordinates()
+ self.selected_turtle = None
+
+ def _align_to_grid(self, grid=20):
+ """ Align blocks at the top of stacks to a grid """
+ for blk in self.block_list.list:
+ if blk.type == 'block':
+ top = find_top_block(blk)
+ if top == blk:
+ x = top.spr.get_xy()[0]
+ y = top.spr.get_xy()[1]
+ if x < 0:
+ dx = -x % grid
+ else:
+ dx = -(x % grid)
+ if y < 0:
+ dy = -y % grid
+ else:
+ dy = -(y % grid)
+ self._jog_block(top, dx, -dy)
+
+ def _jog_block(self, blk, dx, dy):
+ """ Jog block """
+ if blk.type == 'proto':
+ return
+ if collapsed(blk):
+ return
+ if dx == 0 and dy == 0:
+ return
+ self._disconnect(blk)
+ self.drag_group = find_group(blk)
+
+ for blk in self.drag_group:
+ (sx, sy) = blk.spr.get_xy()
+ if sx + dx < 0:
+ dx += -(sx + dx)
+ if sy + dy < 0:
+ dy += -(sy + dy)
+
+ for blk in self.drag_group:
+ (sx, sy) = blk.spr.get_xy()
+ blk.spr.move((sx + dx, sy - dy))
+
+ self._snap_to_dock()
+ self.drag_group = None
+
+ def _number_check(self):
+ """ Make sure a 'number' block contains a number. """
+ n = self.selected_blk.spr.labels[0].replace(CURSOR, '')
+ if n in ['-', '.', '-.', ',', '-,']:
+ n = 0
+ elif n is not None:
+ try:
+ f = float(n.replace(self.decimal_point, '.'))
+ if f > 1000000:
+ n = 1
+ self.showlabel("#overflowerror")
+ elif f < -1000000:
+ n = -1
+ self.showlabel("#overflowerror")
+ except ValueError:
+ n = 0
+ self.showlabel("#notanumber")
+ else:
+ n = 0
+ self.selected_blk.spr.set_label(str(n))
+ try:
+ self.selected_blk.values[0] = \
+ float(str(n).replace(self.decimal_point, '.'))
+ except ValueError:
+ self.selected_blk.values[0] = float(str(n))
+ except IndexError:
+ self.selected_blk.values[0] = float(str(n))
+
+ def _string_check(self):
+ s = self.selected_blk.spr.labels[0].replace(CURSOR, '')
+ self.selected_blk.spr.set_label(s)
+ self.selected_blk.values[0] = s.replace(RETURN, "\12")
+
+ def load_python_code_from_file(self, fname=None, add_new_block=True):
+ """ Load Python code from a file """
+ id = None
+ self.python_code = None
+ if fname is None:
+ fname, self.py_load_save_folder = get_load_name('.py',
+ self.py_load_save_folder)
+ if fname is None:
+ return id
+ try:
+ f = open(fname, 'r')
+ self.python_code = f.read()
+ f.close()
+ id = fname
+ except IOError:
+ error_output("Unable to read Python code from %s" % (fname),
+ self.running_sugar)
+ return id
+
+ # if we are running Sugar, copy the file into the Journal
+ if self.running_sugar:
+ from sugar.datastore import datastore
+ from sugar import profile
+
+ dsobject = datastore.create()
+ dsobject.metadata['title'] = os.path.basename(fname)
+ dsobject.metadata['icon-color'] = \
+ profile.get_color().to_string()
+ dsobject.metadata['mime_type'] = 'text/x-python'
+ dsobject.metadata['activity'] = 'org.laptop.Pippy'
+ dsobject.set_file_path(fname)
+ try:
+ datastore.write(dsobject)
+ id = dsobject.object_id
+ debug_output("Copying %s to the datastore" % (fname),
+ self.running_sugar)
+ except IOError:
+ error_output("Error copying %s to the datastore" % (fname),
+ self.running_sugar)
+ id = None
+ dsobject.destroy()
+
+ if add_new_block:
+ # add a new block for this code at turtle position
+ (tx, ty) = self.active_turtle.get_xy()
+ self._new_block('userdefined', tx, ty)
+ self.myblock[self.block_list.list.index(self.drag_group[0])] =\
+ self.python_code
+ self.set_userdefined(self.drag_group[0])
+ self.drag_group[0].values.append(dsobject.object_id)
+ self.drag_group = None
+ # Save object ID in block value
+ if self.selected_blk is not None:
+ if len(self.selected_blk.values) == 0:
+ self.selected_blk.values.append(dsobject.object_id)
+ else:
+ self.selected_blk.values[0] = dsobject.object_id
+ else:
+ if len(self.selected_blk.values) == 0:
+ self.selected_blk.values.append(fname)
+ else:
+ self.selected_blk.values[0] = fname
+
+ return id
+
+ def load_python_code_from_journal(self, dsobject, blk=None):
+ """ Read the Python code from the Journal object """
+ self.python_code = None
+ try:
+ debug_output("opening %s " % dsobject.file_path,
+ self.running_sugar)
+ file_handle = open(dsobject.file_path, "r")
+ self.python_code = file_handle.read()
+ file_handle.close()
+ except IOError:
+ debug_output("couldn't open %s" % dsobject.file_path,
+ self.running_sugar)
+ # Save the object id as the block value
+ if blk is None:
+ blk = self.selected_blk
+ if blk is not None:
+ if len(blk.values) == 0:
+ blk.values.append(dsobject.object_id)
+ else:
+ blk.values[0] = dsobject.object_id
+
+ def _import_py(self):
+ """ Import Python code into a block """
+ if self.running_sugar:
+ chooser(self.parent, 'org.laptop.Pippy',
+ self.load_python_code_from_journal)
+ else:
+ self.load_python_code_from_file(fname=None, add_new_block=False)
+
+ if self.selected_blk is not None:
+ self.myblock[self.block_list.list.index(self.selected_blk)] = \
+ self.python_code
+ self.set_userdefined(self.selected_blk)
+
+ def new_project(self):
+ """ Start a new project """
+ self.lc.stop_logo()
+ self._loaded_project = ""
+ # Put current project in the trash.
+ while len(self.just_blocks()) > 0:
+ blk = self.just_blocks()[0]
+ top = find_top_block(blk)
+ self._put_in_trash(top)
+ self.canvas.clearscreen()
+ self.save_file_name = None
+
+ def is_new_project(self):
+ """ Is this a new project or was a old project loaded from a file? """
+ return self._loaded_project == ""
+
+ def project_has_changed(self):
+ """ WARNING: order of JSON serialized data may have changed. """
+ try:
+ f = open(self._loaded_project, 'r')
+ saved_project_data = f.read()
+ f.close()
+ except:
+ debug_output("problem loading saved project data from %s" % \
+ (self._loaded_project),
+ self.running_sugar)
+ saved_project_data = ""
+ current_project_data = data_to_string(self.assemble_data_to_save())
+
+ return saved_project_data != current_project_data
+
+ def load_files(self, ta_file, create_new_project=True):
+ """ Load a project from a file """
+ if create_new_project:
+ self.new_project()
+ self._check_collapsibles(self.process_data(data_from_file(ta_file)))
+ self._loaded_project = ta_file
+
+ def load_file(self, create_new_project=True):
+ _file_name, self.load_save_folder = get_load_name('.ta',
+ self.load_save_folder)
+ if _file_name is None:
+ return
+ if _file_name[-3:] == '.ta':
+ _file_name = _file_name[0: -3]
+ self.load_files(_file_name + '.ta', create_new_project)
+ if create_new_project:
+ self.save_file_name = os.path.basename(_file_name)
+ if self.running_sugar:
+ self.activity.metadata['title'] = os.path.split(_file_name)[1]
+
+ def _found_a_turtle(self, blk):
+ """ Either [-1, 'turtle', ...] or [-1, ['turtle', key], ...] """
+ if blk[1] == 'turtle':
+ self.load_turtle(blk)
+ return True
+ elif type(blk[1]) in [list, tuple] and blk[1][0] == 'turtle':
+ if blk[1][1] == DEFAULT_TURTLE:
+ if self.nick is not None and self.nick is not '':
+ self.load_turtle(blk, self.nick)
+ else:
+ self.load_turtle(blk, blk[1][1])
+ return True
+ return False
+
+ def load_turtle(self, blk, key=1):
+ """ Restore a turtle from its saved state """
+ tid, name, xcor, ycor, heading, color, shade, pensize = blk
+ self.canvas.set_turtle(key)
+ self.canvas.setxy(xcor, ycor, pendown=False)
+ self.canvas.seth(heading)
+ self.canvas.setcolor(color)
+ self.canvas.setshade(shade)
+ self.canvas.setpensize(pensize)
+
+ def load_block(self, b, offset=0):
+ """ Restore individual blocks from saved state """
+ if self.running_sugar:
+ from sugar.datastore import datastore
+
+ # A block is saved as: (i, (btype, value), x, y, (c0,... cn))
+ # The x, y position is saved/loaded for backward compatibility
+ btype, value = b[1], None
+ if type(btype) == tuple:
+ btype, value = btype
+ elif type(btype) == list:
+ btype, value = btype[0], btype[1]
+ if btype in content_blocks or btype in COLLAPSIBLE:
+ if btype == 'number':
+ try:
+ values = [round_int(value)]
+ except ValueError:
+ values = [0]
+ elif btype in COLLAPSIBLE:
+ if value is not None:
+ values = [int(value)]
+ else:
+ values = []
+ else:
+ values = [value]
+ else:
+ values = []
+
+ if btype in OLD_DOCK:
+ check_dock = True
+ else:
+ check_dock = False
+ if btype in OLD_NAMES:
+ btype = OLD_NAMES[btype]
+
+ blk = Block(self.block_list, self.sprite_list, btype,
+ b[2] + self.canvas.cx + offset,
+ b[3] + self.canvas.cy + offset,
+ 'block', values, self.block_scale)
+
+ # Some blocks get transformed.
+ if btype in block_styles['basic-style-var-arg'] and value is not None:
+ # Is there code stored in this userdefined block?
+ if value > 0: # catch deprecated format (#2501)
+ self.python_code = None
+ if self.running_sugar:
+ debug_output(value, self.running_sugar)
+ # For security reasons, only open files found in
+ # Python samples directory
+ if os.path.exists(os.path.join(self.path, value)) and \
+ value[0:9] == 'pysamples':
+ self.selected_blk = blk
+ self.load_python_code_from_file(
+ fname=os.path.join(self.path, value),
+ add_new_block=False)
+ self.selected_blk = None
+ else: # or files from the Journal
+ try:
+ dsobject = datastore.get(value)
+ except: # Should be IOError, but dbus error is raised
+ dsobject = None
+ debug_output("couldn't get dsobject %s" % value,
+ self.running_sugar)
+ if dsobject is not None:
+ self.load_python_code_from_journal(dsobject, blk)
+ else:
+ self.selected_blk = blk
+ self.load_python_code_from_file(fname=value,
+ add_new_block=False)
+ self.selected_blk = None
+ if self.python_code is not None:
+ self.myblock[self.block_list.list.index(blk)] = \
+ self.python_code
+ self.set_userdefined(blk)
+ if btype == 'string' and blk.spr is not None:
+ blk.spr.set_label(blk.values[0].replace('\n', RETURN))
+ elif btype == 'start': # block size is saved in start block
+ if value is not None:
+ self.block_scale = value
+ elif btype in block_styles['box-style-media'] and blk.spr is not None:
+ if btype in EXPAND_SKIN:
+ if blk.ex == 0:
+ blk.expand_in_x(EXPAND_SKIN[btype][0])
+ if blk.ey == 0:
+ blk.expand_in_y(EXPAND_SKIN[btype][1])
+ if len(blk.values) == 0 or blk.values[0] == 'None' or \
+ blk.values[0] is None or btype in NO_IMPORT:
+ self._block_skin(btype + 'off', blk)
+ elif btype in ['video', 'audio', 'description']:
+ self._block_skin(btype + 'on', blk)
+ elif self.running_sugar:
+ try:
+ dsobject = datastore.get(blk.values[0])
+ if not movie_media_type(dsobject.file_path[-4:]):
+ w, h, = calc_image_size(blk.spr)
+ pixbuf = get_pixbuf_from_journal(dsobject, w, h)
+ if pixbuf is not None:
+ x, y = self._calc_image_offset('', blk.spr)
+ blk.set_image(pixbuf, x, y)
+ else:
+ self._block_skin('journalon', blk)
+ dsobject.destroy()
+ except:
+ try:
+ w, h, = calc_image_size(blk.spr)
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
+ blk.values[0], w, h)
+ x, y = self._calc_image_offset('', blk.spr)
+ blk.set_image(pixbuf, x, y)
+ except:
+ debug_output("Couldn't open dsobject (%s)" % \
+ (blk.values[0]), self.running_sugar)
+ self._block_skin('journaloff', blk)
+ else:
+ if not movie_media_type(blk.values[0][-4:]):
+ try:
+ w, h, = calc_image_size(blk.spr)
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
+ blk.values[0], w, h)
+ x, y = self._calc_image_offset('', blk.spr)
+ blk.set_image(pixbuf, x, y)
+ except:
+ self._block_skin('journaloff', blk)
+ else:
+ self._block_skin('journalon', blk)
+ blk.spr.set_label(' ')
+ blk.resize()
+ elif btype in EXPANDABLE or btype in expandable_blocks or \
+ btype in EXPANDABLE_ARGS or btype == 'nop':
+ if btype == 'vspace' or btype in expandable_blocks:
+ if value is not None:
+ blk.expand_in_y(value)
+ elif btype == 'hspace' or btype == 'identity2':
+ if value is not None:
+ blk.expand_in_x(value)
+ elif btype == 'templatelist' or btype == 'list':
+ for i in range(len(b[4]) - 4):
+ blk.add_arg()
+ elif btype == 'myfunc2arg' or btype == 'myfunc3arg' or\
+ btype == 'userdefined2args' or btype == 'userdefined3args':
+ blk.add_arg()
+ if btype == 'myfunc3arg' or btype == 'userdefined3args':
+ blk.add_arg(False)
+ if btype in PYTHON_SKIN:
+ if self.nop == 'pythonloaded':
+ self._block_skin('pythonon', blk)
+ else:
+ self._block_skin('pythonoff', blk)
+
+ if self.interactive_mode:
+ blk.spr.set_layer(BLOCK_LAYER)
+ if check_dock:
+ blk.connections = 'check'
+ if self.running_sugar and len(blk.spr.labels) > 0 and \
+ blk.name not in ['', ' ', 'number', 'string']:
+ if len(self.used_block_list) > 0:
+ self.used_block_list.append(', ')
+ if blk.name in special_names:
+ self.used_block_list.append(special_names[blk.name])
+ elif blk.spr.labels[0] not in self.used_block_list:
+ self.used_block_list.append(blk.spr.labels[0])
+ return blk
+
+ def load_start(self, ta_file=None):
+ """ Start a new project with a 'start' brick """
+ if ta_file is None:
+ self.process_data([[0, "start", PALETTE_WIDTH + 20,
+ self.toolbar_offset + PALETTE_HEIGHT + 20,
+ [None, None]]])
+ else:
+ self.process_data(data_from_file(ta_file))
+
+ def save_file(self, _file_name=None):
+ """ Start a project to a file """
+ if self.save_folder is not None:
+ self.load_save_folder = self.save_folder
+ if _file_name is None:
+ _file_name, self.load_save_folder = get_save_name('.ta',
+ self.load_save_folder, self.save_file_name)
+ if _file_name is None:
+ return
+ if _file_name[-3:] == '.ta':
+ _file_name = _file_name[0: -3]
+ data_to_file(self.assemble_data_to_save(), _file_name + '.ta')
+ self.save_file_name = os.path.basename(_file_name)
+ if not self.running_sugar:
+ self.save_folder = self.load_save_folder
+
+ def assemble_data_to_save(self, save_turtle=True, save_project=True):
+ """ Pack the project (or stack) into a datastream to be serialized """
+ _data = []
+ _blks = []
+
+ if save_project:
+ _blks = self.just_blocks()
+ else:
+ if self.selected_blk is None:
+ return []
+ _blks = find_group(find_top_block(self.selected_blk))
+
+ for _i, _blk in enumerate(_blks):
+ _blk.id = _i
+ for _blk in _blks:
+ if _blk.name in content_blocks or _blk.name in COLLAPSIBLE:
+ if len(_blk.values) > 0:
+ _name = (_blk.name, _blk.values[0])
+ else:
+ _name = (_blk.name)
+ elif _blk.name in block_styles['basic-style-var-arg'] and \
+ len(_blk.values) > 0:
+ _name = (_blk.name, _blk.values[0])
+ elif _blk.name in EXPANDABLE or _blk.name in expandable_blocks or\
+ _blk.name in EXPANDABLE_ARGS:
+ _ex, _ey = _blk.get_expand_x_y()
+ if _ex > 0:
+ _name = (_blk.name, _ex)
+ elif _ey > 0:
+ _name = (_blk.name, _ey)
+ else:
+ _name = (_blk.name, 0)
+ elif _blk.name == 'start': # save block_size in start block
+ _name = (_blk.name, self.block_scale)
+ else:
+ _name = (_blk.name)
+ if hasattr(_blk, 'connections') and _blk.connections is not None:
+ connections = [get_id(_cblk) for _cblk in _blk.connections]
+ else:
+ connections = None
+ (_sx, _sy) = _blk.spr.get_xy()
+ # Add a slight offset for copy/paste
+ if not save_project:
+ _sx += 20
+ _sy += 20
+ _data.append((_blk.id, _name, _sx - self.canvas.cx,
+ _sy - self.canvas.cy, connections))
+ if save_turtle:
+ for turtle in iter(self.turtles.dict):
+ # Don't save remote turtles
+ if not self.remote_turtle(turtle):
+ # Save default turtle as 'Yertle'
+ if turtle == self.nick:
+ turtle = DEFAULT_TURTLE
+ _data.append((-1, ['turtle', turtle],
+ self.canvas.xcor, self.canvas.ycor,
+ self.canvas.heading, self.canvas.color,
+ self.canvas.shade, self.canvas.pensize))
+ return _data
+
+ def display_coordinates(self):
+ """ Display the coordinates of the current turtle on the toolbar """
+ x = round_int(float(self.canvas.xcor) / self.coord_scale)
+ y = round_int(float(self.canvas.ycor) / self.coord_scale)
+ h = round_int(self.canvas.heading)
+ if self.running_sugar:
+ self.activity.coordinates_label.set_text("%s: %d %s: %d %s: %d" %\
+ (_("xcor"), x, _("ycor"), y, _("heading"), h))
+ self.activity.coordinates_label.show()
+ elif self.interactive_mode:
+ self.parent.set_title("%s — %s: %d %s: %d %s: %d" % \
+ (_("Turtle Art"), _("xcor"), x, _("ycor"), y, _("heading"), h))
+
+ def showlabel(self, shp, label=''):
+ """ Display a message on a status block """
+ if not self.interactive_mode:
+ debug_output(label, self.running_sugar)
+ return
+ if shp == 'syntaxerror' and str(label) != '':
+ if str(label)[1:] in self.status_shapes:
+ shp = str(label)[1:]
+ label = ''
+ else:
+ shp = 'status'
+ elif shp[0] == '#':
+ shp = shp[1:]
+ label = ''
+ self.status_spr.set_shape(self.status_shapes[shp])
+ self.status_spr.set_label_attributes(12.0, rescale=False)
+ if shp == 'status':
+ self.status_spr.set_label('"%s"' % (str(label)))
+ else:
+ self.status_spr.set_label(str(label))
+ self.status_spr.set_layer(STATUS_LAYER)
+ if shp == 'info':
+ self.status_spr.move((PALETTE_WIDTH, self.height - 400))
+ else:
+ # Adjust vertical position based on scrolled window adjustment
+ if self.running_sugar:
+ self.status_spr.move((0, self.height - 200 + \
+ self.activity.sw.get_vadjustment().get_value()))
+ elif self.interactive_mode:
+ self.status_spr.move((0, self.height - 100))
+
+ def calc_position(self, template):
+ """ Relative placement of portfolio objects (deprecated) """
+ w, h, x, y, dx, dy = TEMPLATES[template]
+ x *= self.canvas.width
+ y *= self.canvas.height
+ w *= (self.canvas.width - x)
+ h *= (self.canvas.height - y)
+ dx *= w
+ dy *= h
+ return(w, h, x, y, dx, dy)
+
+ def save_for_upload(self, _file_name):
+ """ Grab the current canvas and save it for upload """
+ if _file_name[-3:] == '.ta':
+ _file_name = _file_name[0: -3]
+ data_to_file(self.assemble_data_to_save(), _file_name + '.ta')
+ save_picture(self.canvas, _file_name + '.png')
+ ta_file = _file_name + '.ta'
+ image_file = _file_name + '.png'
+ return ta_file, image_file
+
+ def save_as_image(self, name="", svg=False, pixbuf=None):
+ """ Grab the current canvas and save it. """
+ if svg:
+ suffix = '.svg'
+ else:
+ suffix = '.png'
+
+ if not self.interactive_mode:
+ save_picture(self.canvas, name[:-3] + suffix)
+ return
+ if self.running_sugar:
+ if len(name) == 0:
+ filename = 'ta' + suffix
+ else:
+ filename = name + suffix
+ datapath = get_path(self.activity, 'instance')
+ elif len(name) == 0:
+ name = 'ta'
+ if self.save_folder is not None:
+ self.load_save_folder = self.save_folder
+ filename, self.load_save_folder = get_save_name(
+ suffix, self.load_save_folder, name)
+ datapath = self.load_save_folder
+ else:
+ datapath = os.getcwd()
+ filename = name + suffix
+ if filename is None:
+ return
+
+ file_path = os.path.join(datapath, filename)
+ if svg:
+ if self.canvas.cr_svg is None:
+ return
+ self.canvas.svg_reset()
+ else:
+ save_picture(self.canvas, file_path)
+
+ if self.running_sugar:
+ from sugar.datastore import datastore
+ from sugar import profile
+
+ dsobject = datastore.create()
+ if len(name) == 0:
+ dsobject.metadata['title'] = "%s %s" % \
+ (self.activity.metadata['title'], _("image"))
+ else:
+ dsobject.metadata['title'] = name
+ dsobject.metadata['icon-color'] = profile.get_color().to_string()
+ if svg:
+ dsobject.metadata['mime_type'] = 'image/svg+xml'
+ dsobject.set_file_path(os.path.join(datapath, 'output.svg'))
+ else:
+ dsobject.metadata['mime_type'] = 'image/png'
+ dsobject.set_file_path(file_path)
+ datastore.write(dsobject)
+ dsobject.destroy()
+ self.saved_pictures.append((dsobject.object_id, svg))
+ if svg:
+ os.remove(os.path.join(datapath, 'output.svg'))
+ else:
+ os.remove(file_path)
+ else:
+ if svg:
+ output = subprocess.check_output(
+ ['mv', os.path.join(datapath, 'output.svg'),
+ os.path.join(datapath, filename)])
+ self.saved_pictures.append((file_path, svg))
+
+ def just_blocks(self):
+ """ Filter out 'proto', 'trash', and 'deleted' blocks """
+ just_blocks_list = []
+ for _blk in self.block_list.list:
+ if _blk.type == 'block':
+ just_blocks_list.append(_blk)
+ return just_blocks_list
+
+ def _width_and_height(self, blk):
+ """ What are the width and height of a stack? """
+ minx = 10000
+ miny = 10000
+ maxx = -10000
+ maxy = -10000
+ for gblk in find_group(blk):
+ (x, y) = gblk.spr.get_xy()
+ w, h = gblk.spr.get_dimensions()
+ if x < minx:
+ minx = x
+ if y < miny:
+ miny = y
+ if x + w > maxx:
+ maxx = x + w
+ if y + h > maxy:
+ maxy = y + h
+ return(maxx - minx, maxy - miny)
+
+ # Utilities related to putting a image 'skin' on a block
+
+ def _calc_image_offset(self, name, spr, iw=0, ih=0):
+ """ Calculate the postion for placing an image onto a sprite. """
+ _l, _t = spr.label_left_top()
+ if name == '':
+ return _l, _t
+ _w = spr.label_safe_width()
+ _h = spr.label_safe_height()
+ if iw == 0:
+ iw = self.media_shapes[name].get_width()
+ ih = self.media_shapes[name].get_height()
+ return int(_l + (_w - iw) / 2), int(_t + (_h - ih) / 2)
+
+ def _calc_w_h(self, name, spr):
+ """ Calculate new image size """
+ target_w = spr.label_safe_width()
+ target_h = spr.label_safe_height()
+ if name == '':
+ return target_w, target_h
+ image_w = self.media_shapes[name].get_width()
+ image_h = self.media_shapes[name].get_height()
+ scale_factor = float(target_w) / image_w
+ new_w = target_w
+ new_h = image_h * scale_factor
+ if new_h > target_h:
+ scale_factor = float(target_h) / new_h
+ new_h = target_h
+ new_w = target_w * scale_factor
+ return int(new_w), int(new_h)
+
+ def _proto_skin(self, name, n, i):
+ """ Utility for calculating proto skin images """
+ x, y = self._calc_image_offset(name, self.palettes[n][i].spr)
+ self.palettes[n][i].spr.set_image(self.media_shapes[name], 1, x, y)
+
+ def _block_skin(self, name, blk):
+ """ Some blocks get a skin """
+ x, y = self._calc_image_offset(name, blk.spr)
+ blk.set_image(self.media_shapes[name], x, y)
+ self._resize_skin(blk)
+
+ def _resize_skin(self, blk):
+ """ Resize the 'skin' when block scale changes. """
+ if blk.name in PYTHON_SKIN:
+ w, h = self._calc_w_h('pythonoff', blk.spr)
+ x, y = self._calc_image_offset('pythonoff', blk.spr, w, h)
+ elif blk.name == 'journal':
+ if len(blk.values) == 1 and blk.values[0] is not None:
+ w, h = self._calc_w_h('', blk.spr)
+ x, y = self._calc_image_offset('journaloff', blk.spr, w, h)
+ else:
+ w, h = self._calc_w_h('journaloff', blk.spr)
+ x, y = self._calc_image_offset('journaloff', blk.spr, w, h)
+ else:
+ # w, h = self._calc_w_h('descriptionoff', blk.spr)
+ w, h = self._calc_w_h('', blk.spr)
+ # x, y = self._calc_image_offset('descriptionoff', blk.spr, w, h)
+ x, y = self._calc_image_offset('', blk.spr, w, h)
+ blk.scale_image(x, y, w, h)
+
+
+def dock_dx_dy(block1, dock1n, block2, dock2n):
+ """ Find the distance between the dock points of two blocks. """
+ _dock1 = block1.docks[dock1n]
+ _dock2 = block2.docks[dock2n]
+ _d1type, _d1dir, _d1x, _d1y = _dock1[0:4]
+ _d2type, _d2dir, _d2x, _d2y = _dock2[0:4]
+ if block1 == block2:
+ return (100, 100)
+ if _d1dir == _d2dir:
+ return (100, 100)
+ if (_d2type is not 'number') or (dock2n is not 0):
+ if block1.connections is not None and \
+ dock1n < len(block1.connections) and \
+ block1.connections[dock1n] is not None:
+ return (100, 100)
+ if block2.connections is not None and \
+ dock2n < len(block2.connections) and \
+ block2.connections[dock2n] is not None:
+ return (100, 100)
+ if _d1type != _d2type:
+ if block1.name in string_or_number_args:
+ if _d2type == 'number' or _d2type == 'string':
+ pass
+ elif block1.name in CONTENT_ARGS:
+ if _d2type in content_blocks:
+ pass
+ else:
+ return (100, 100)
+ (_b1x, _b1y) = block1.spr.get_xy()
+ (_b2x, _b2y) = block2.spr.get_xy()
+ return ((_b1x + _d1x) - (_b2x + _d2x), (_b1y + _d1y) - (_b2y + _d2y))
diff --git a/activity/activity-turtleart.svg b/activity/activity-turtleart.svg
new file mode 100644
index 0000000..8fb0dd4
--- /dev/null
+++ b/activity/activity-turtleart.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#000">
+ <!ENTITY fill_color "#eee">
+]><svg height="55px" viewBox="0 0 55 55" width="55px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="&stroke_color;" fill="&fill_color;">
+ <g
+ transform="matrix(0.32702086,0,0,0.32702086,-28.281255,4.0632089)"
+ id="g3861">
+ <path
+ d="m 229.4265,82.10708 a 34.448574,31.598513 0 1 1 -68.8971,0 34.448574,31.598513 0 1 1 68.8971,0 z"
+ id="path3825"
+ style="fill:#000000;fill-opacity:1;stroke:none" />
+ <path
+ d="m 146.8466,108.06462 c 0,0 16.2359,10.15496 24.2773,15.19797 3.6656,2.29879 3.4995,-9.01041 6.1186,-11.44782 1.9482,-0.15766 15.138,8.36289 23.0931,7.10554 8.2572,-1.3051 15.3714,-6.28257 20.4032,-13.78888 -0.5953,-2.28828 -5.5925,-0.008 -7.6013,-0.91343 -2.8453,4.78261 -5.6956,6.90448 -10.0387,7.00464 -13.9586,0.32192 -33.532,-19.36329 -35.3303,-23.68516 4.3069,0.8831 11.4973,1.51301 14.2111,-2.56589 1.5823,-2.3782 -0.543,-5.35067 -2.1712,-7.69767 -3.0708,-4.42645 -11.4011,-4.77895 -13.2242,-8.48717 -2.0107,-4.08982 4.488,-8.63853 8.2898,-11.15177 4.5762,-3.02521 11.481,-8.36867 16.1849,-4.04621 2.4052,2.2102 -7.5659,0.16838 -8.882,3.15802 -1.7425,3.95823 5.8602,4.97597 6.3161,9.27668 0.4237,3.99739 -3.7475,7.27492 -4.3423,11.25045 -0.4099,2.73981 1.6536,5.73092 0.5921,8.2898 -1.2332,2.97281 -7.8906,3.15173 -7.3029,6.31604 10.1484,6.52995 20.5559,10.53313 31.7776,11.44783 9.0031,0.73386 19.1464,0.10081 26.6458,-4.93441 5.4546,-3.6623 10.6274,-10.00969 10.6583,-16.57961 0.03,-6.29744 -5.6333,-11.51446 -10.0662,-15.98748 -6.7701,-6.83137 -18.11,-8.21253 -24.2773,-15.59272 -2.6481,-3.16893 -1.2426,-9.24702 -4.737,-11.44783 -7.9984,-1.60894 -19.6673,-0.96019 -29.0143,1.57901 -11.3585,3.08564 -23.6697,8.68727 -31.3828,16.18486 -4.051,9.65189 9.514,19.53633 9.2766,30.00119 -0.1808,7.96857 -9.6714,21.31664 -9.6714,21.31664"
+ id="path3821"
+ style="fill:&stroke_color;;fill-opacity:1;stroke:&fill_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 178.3733,63.02406 -1.8587,2.72615 c 0,0 -2.6364,-0.66145 -2.7262,-1.73482 -0.2829,-3.38076 4.202,-7.30607 7.5589,-6.81537 1.5666,4.57181 5.2044,8.55019 5.2044,8.55019 l -7.4349,-4.08922 -1.7348,0.74349"
+ id="path3823"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 221.1936,90.19267 -0.819,4.70927"
+ id="path3834"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 238.3927,87.32616 2.0475,2.66176"
+ id="path3836"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 141.89,121.50238 c 0,0 -29.3042,3.77758 -44,-21.33334 -3.9435,-6.73831 -2.7258,-15.11656 -2,-22.66666 2.1847,-22.72591 12.5505,-46.54858 22,-61.33334 20.3839,-1.77411 47.6318,-0.0892 70.6667,2.66667 4.623,0.5531 28.2288,7.16728 32.6667,14.66667 2.2322,3.77212 -0.7807,20.81554 -2.6651,20.39909 -10.8427,-2.39622 -7.4629,-16.14428 -10.3501,-15.81288 -10.3315,1.20112 -23.4198,0.59834 -31.741,3.3315 -8.8873,3.54201 -27.3925,11.3578 -30.4555,17.88252 -2.6561,5.65803 11.5495,23.95055 10.2746,28.17891 -2.3379,7.75325 -13.9551,33.45122 -14.3241,33.9338"
+ id="path2997"
+ style="fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 175.0714,119.31507 2.7633,-6.51342 c 0,0 15.3169,6.68998 23.2904,5.92129 5.2145,-0.5027 14.2111,-6.7108 14.2111,-6.7108 l 5.9212,13.81634 c 0,0 -16.5168,2.99189 -24.672,1.77639 -7.6013,-1.13294 -21.514,-8.2898 -21.514,-8.2898 z"
+ id="path3806"
+ style="fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 112.7004,115.17017 c 0,0 5.8137,1.1256 8.0925,-0.19738 4.9111,-2.8512 2.925,-12.1206 7.6976,-15.19797 5.0919,-3.28324 12.7474,1.93531 18.1587,-0.78951 2.7991,-1.40946 5.9212,-7.30292 5.9212,-7.30292"
+ id="path3767"
+ style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 99.7723,102.48516 c 0,0 11.7349,0.53347 15.198,-2.17114 4.4756,-3.49537 5.0962,-12.1206 9.8688,-15.19797 5.0919,-3.28324 11.5631,1.93531 16.9744,-0.78951 2.7991,-1.40946 7.6976,-10.06619 7.6976,-10.06619"
+ id="path3767-4"
+ style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="translate(-1185.4433,-637.16429)"
+ id="g3797"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1">
+ <path
+ d="m 1280.3799,717.7936 12.04,-11.05307 -6.1187,0.39476 1.9738,-12.43471 8.0924,-3.3554 -2.3685,13.42159 c 0,0 27.6772,-26.02525 44.2123,-34.9356 9.3957,-5.06314 29.6064,-9.86881 30.3959,-10.06619 0.7895,-0.19738 16.777,0.59213 16.777,0.59213 -7.5003,-3.3554 -16.5796,-5.32916 -26.0537,-5.32916 l -2.3685,5.13178 -20.1324,0.19738 4.1449,-5.72391 -6.7108,-0.59213 -15.1979,8.88193 15.0006,-1.38163 -4.1449,11.84257 -15.7901,0.98688 4.9344,-11.25044 -16.777,13.61896 8.2898,-0.19738 -0.7895,10.8557 -12.04,2.76327 3.7502,-12.03996 -15.3954,11.84258 -2.7632,9.86882"
+ id="path3787"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 1287.8802,703.38514 1.7764,0.39475 1.7764,-5.92129 -2.1711,-0.39475 z"
+ id="path3789"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 1303.2756,685.62127 0.9869,-5.13178 2.3685,3.75015 z"
+ id="path3791"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 1320.8421,671.2128 1.1842,-5.13178 7.5003,-0.19738 -2.7633,5.13179 -5.1317,-0.19738"
+ id="path3793"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 1345.1193,658.18597 0,-4.1449 10.461,0.7895 -1.1843,3.15802 z"
+ id="path3795"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <path
+ d="m 97.5025,100.16957 18.7508,-21.11926 17.9612,-13.42159 c 0,0 8.0484,-5.69596 12.2373,-8.28981 8.5409,-5.28872 16.7256,-11.61232 26.2511,-14.80322 8.9376,-2.99395 18.73,-2.20005 28.0274,-3.75015 1.6545,-0.27585 4.9344,-0.98688 4.9344,-0.98688 l -0.9869,-8.48718 9.0793,3.94753 -0.5921,1.97376 -5.3291,-2.17114 1.1842,5.72391 0.9869,-2.17113 5.9213,2.17113 0.9869,-5.52653 -12.6321,-5.72391 -5.9213,7.30292 -21.9088,2.76327 -3.9475,-7.30293 13.2242,-3.55277 0.5921,3.94753 -7.6976,1.18425 -0.1974,2.36852 11.2504,-2.17114 -1.7764,-7.30292 -17.7638,3.55277 -7.6977,15.19798 -15.3953,9.0793 -6.5135,-4.53965 14.0138,-9.67144 0,2.56589 -9.4741,5.72392 c 0,0 0.3947,1.77638 1.3816,1.57901 0.9869,-0.19738 10.2636,-5.52654 10.2636,-5.52654 l -1.1843,-6.71079 -17.9612,11.6452 -8.4872,17.56649 -8.4872,7.30292 -2.9606,-3.94753 6.7108,-7.30292 0.5921,1.57901 -5.3291,4.93441 7.6976,-5.13179 -2.9606,-4.9344 -10.461,9.67143 -2.9606,13.81634 -7.8951,8.68456 -4.3422,-1.77639 5.3291,-6.51341 0.7895,1.38163 -2.7632,4.53966 3.3554,-9.27669 -7.8951,6.90817 -2.1712,9.67143 z"
+ id="path3804"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100644
index 0000000..dde81c2
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,10 @@
+[Activity]
+name = Amazonas Tortuga
+activity_version = 1
+license = MIT
+bundle_id = org.sugarlabs.AmazonasTortuga
+exec = sugar-activity AmazonasTortugaActivity.AmazonasTortugaActivity
+icon = activity-turtleart
+show_launcher = yes
+update_url = http://wiki.laptop.org/go/Activities/G1G1
+mime_types = application/x-turtle-art
diff --git a/activity/application-x-turtle-art.svg b/activity/application-x-turtle-art.svg
new file mode 100644
index 0000000..a885712
--- /dev/null
+++ b/activity/application-x-turtle-art.svg
@@ -0,0 +1,120 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://w
+ww.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]>
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve">
+<g
+ transform="translate(-0.5507515,-1.7569535)">
+ <line
+ style="fill:none;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ y2="4.9"
+ y1="16.2"
+ x2="52.4"
+ x1="41.2" />
+ <polyline
+ style="fill:none;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ points="51.6,15.3 41.2,16.2 42.1,5.8" />
+</g>
+<polygon
+ points="11.0,6.1 31.9,6.1 43.9,18.0 43.9,48.9 10.9,48.9 10.9,6.1 "
+ transform="translate(-6,2)"
+ stroke="&stroke_color;"
+ style="fill:none;stroke-width:3.5" />
+<polyline
+ points="43.818,18.027 31.874,18.027 31.874,6.088"
+ stroke="&stroke_color;"
+ style="fill:none;stroke-width:3.5"
+ transform="translate(-6,2)" />
+<path
+ d="M 21.379465,44.19975 C 21.102965,44.19975 20.829965,44.18225 20.560965,44.14975 L 21.254965,45.32425 L 21.938965,44.16775 C 21.753465,44.18275 21.568465,44.19975 21.379465,44.19975 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+<g
+ transform="matrix(0.5,0,0,0.5,7.6,20.0)"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+ <path
+ d="M 40.16,11.726 C 37.996,11.726 36.202,13.281 35.817,15.333 C 37.676,16.678 39.274,18.448 40.492,20.541 C 42.777,20.369 44.586,18.48 44.586,16.151 C 44.586,13.707 42.604,11.726 40.16,11.726 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 40.713,39.887 C 39.489,42.119 37.853,44.018 35.916,45.443 C 36.437,47.307 38.129,48.682 40.16,48.682 C 42.603,48.682 44.586,46.702 44.586,44.258 C 44.586,42.003 42.893,40.162 40.713,39.887 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 14.273,39.871 C 12.02,40.077 10.249,41.95 10.249,44.258 C 10.249,46.701 12.229,48.682 14.673,48.682 C 16.737,48.682 18.457,47.262 18.945,45.35 C 17.062,43.934 15.47,42.061 14.273,39.871 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 19.026,15.437 C 18.683,13.334 16.872,11.726 14.673,11.726 C 12.229,11.726 10.249,13.707 10.249,16.15 C 10.249,18.532 12.135,20.46 14.494,20.556 C 15.68,18.513 17.226,16.772 19.026,15.437 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+<path
+ d="M 21.379465,26.34175 C 22.333465,26.34175 23.243465,26.54725 24.088465,26.90575 C 24.458965,26.36775 24.677465,25.71725 24.677465,25.01425 C 24.677465,23.17075 23.182965,21.67575 21.338965,21.67575 C 19.495465,21.67575 18.000965,23.17075 18.000965,25.01425 C 18.000965,25.72825 18.226965,26.38875 18.608965,26.93125 C 19.470965,26.55675 20.402465,26.34175 21.379465,26.34175 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+<g
+ transform="matrix(0.5,0,0,0.5,7.630965,20.06025)"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+ <path
+ d="M 43.102,30.421 C 43.102,35.1554 41.4568,39.7008 38.5314,43.0485 C 35.606,46.3963 31.6341,48.279 27.497,48.279 C 23.3599,48.279 19.388,46.3963 16.4626,43.0485 C 13.5372,39.7008 11.892,35.1554 11.892,30.421 C 11.892,20.6244 18.9364,12.563 27.497,12.563 C 36.0576,12.563 43.102,20.6244 43.102,30.421 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+<g
+ transform="matrix(0.5,0,0,0.5,7.630965,20.06025)"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+ <path
+ d="M 25.875,33.75 L 24.333,29.125 L 27.497,26.538 L 31.112,29.164 L 29.625,33.833 L 25.875,33.75 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 27.501,41.551 C 23.533,41.391 21.958,39.542 21.958,39.542 L 25.528,35.379 L 29.993,35.547 L 33.125,39.667 C 33.125,39.667 30.235,41.661 27.501,41.551 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 18.453,33.843 C 17.604,30.875 18.625,26.959 18.625,26.959 L 22.625,29.126 L 24.118,33.755 L 20.536,37.988 C 20.536,37.987 19.071,35.998 18.453,33.843 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 19.458,25.125 C 19.458,25.125 19.958,23.167 22.497,21.303 C 24.734,19.66 26.962,19.583 26.962,19.583 L 26.925,24.564 L 23.404,27.314 L 19.458,25.125 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 32.084,27.834 L 28.625,24.959 L 29,19.75 C 29,19.75 30.834,19.708 32.959,21.417 C 35.187,23.208 36.321,26.4 36.321,26.4 L 32.084,27.834 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 31.292,34.042 L 32.605,29.578 L 36.792,28.042 C 36.792,28.042 37.469,30.705 36.75,33.709 C 36.21,35.965 34.666,38.07 34.666,38.07 L 31.292,34.042 z"
+ stroke="&stroke_color;"
+ fill="&fill_color;"
+ style="fill-opacity:1;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/activity/menu-icon.svg b/activity/menu-icon.svg
new file mode 100644
index 0000000..433c130
--- /dev/null
+++ b/activity/menu-icon.svg
@@ -0,0 +1,83 @@
+<?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="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2">
+ <defs
+ id="defs40" />
+ <g
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,-6.8005861,7.9133051)"
+ id="g3661"
+ style="fill:#00a000;fill-opacity:1">
+ <path
+ d="m 27.497,48.279 c -0.553,0 -1.099,-0.035 -1.637,-0.1 l 1.388,2.349 1.368,-2.313 c -0.371,0.03 -0.741,0.064 -1.119,0.064 z"
+ id="path6"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-opacity:1" />
+ <g
+ id="g8"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="m 40.16,11.726 c -2.164,0 -3.958,1.555 -4.343,3.607 1.859,1.345 3.457,3.115 4.675,5.208 2.285,-0.172 4.094,-2.061 4.094,-4.39 0,-2.444 -1.982,-4.425 -4.426,-4.425 z"
+ id="path10"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 40.713,39.887 c -1.224,2.232 -2.86,4.131 -4.797,5.556 0.521,1.864 2.213,3.239 4.244,3.239 2.443,0 4.426,-1.98 4.426,-4.424 0,-2.255 -1.693,-4.096 -3.873,-4.371 z"
+ id="path12"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 14.273,39.871 c -2.253,0.206 -4.024,2.079 -4.024,4.387 0,2.443 1.98,4.424 4.424,4.424 2.064,0 3.784,-1.42 4.272,-3.332 -1.883,-1.416 -3.475,-3.289 -4.672,-5.479 z"
+ id="path14"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.026,15.437 c -0.343,-2.103 -2.154,-3.711 -4.353,-3.711 -2.444,0 -4.424,1.981 -4.424,4.424 0,2.382 1.886,4.31 4.245,4.406 1.186,-2.043 2.732,-3.784 4.532,-5.119 z"
+ id="path16"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ d="m 27.497,12.563 c 1.908,0 3.728,0.411 5.418,1.128 0.741,-1.076 1.178,-2.377 1.178,-3.783 0,-3.687 -2.989,-6.677 -6.677,-6.677 -3.687,0 -6.676,2.99 -6.676,6.677 0,1.428 0.452,2.749 1.216,3.834 1.724,-0.749 3.587,-1.179 5.541,-1.179 z"
+ id="path18"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g20"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="m 43.102,30.421 c 0,4.7344 -1.6452,9.2798 -4.5706,12.6275 -2.9254,3.3478 -6.8973,5.2305 -11.0344,5.2305 -4.1371,0 -8.109,-1.8827 -11.0344,-5.2305 -2.9254,-3.3477 -4.5706,-7.8931 -4.5706,-12.6275 0,-9.7966 7.0444,-17.858 15.605,-17.858 8.5606,0 15.605,8.0614 15.605,17.858 z"
+ id="path22"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ id="g24"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="m 25.875,33.75 -1.542,-4.625 3.164,-2.587 3.615,2.626 -1.487,4.669 z"
+ id="path26"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 27.501,41.551 c -3.968,-0.16 -5.543,-2.009 -5.543,-2.009 l 3.57,-4.163 4.465,0.168 3.132,4.12 c 0,0 -2.89,1.994 -5.624,1.884 z"
+ id="path28"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 18.453,33.843 c -0.849,-2.968 0.172,-6.884 0.172,-6.884 l 4,2.167 1.493,4.629 -3.582,4.233 c 0,-10e-4 -1.465,-1.99 -2.083,-4.145 z"
+ id="path30"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.458,25.125 c 0,0 0.5,-1.958 3.039,-3.822 2.237,-1.643 4.465,-1.72 4.465,-1.72 l -0.037,4.981 -3.521,2.75 -3.946,-2.189 z"
+ id="path32"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 32.084,27.834 28.625,24.959 29,19.75 c 0,0 1.834,-0.042 3.959,1.667 2.228,1.791 3.362,4.983 3.362,4.983 l -4.237,1.434 z"
+ id="path34"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 31.292,34.042 1.313,-4.464 4.187,-1.536 c 0,0 0.677,2.663 -0.042,5.667 -0.54,2.256 -2.084,4.361 -2.084,4.361 l -3.374,-4.028 z"
+ id="path36"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+</svg>
diff --git a/activity/mimetypes.xml b/activity/mimetypes.xml
new file mode 100644
index 0000000..aa24955
--- /dev/null
+++ b/activity/mimetypes.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
+<mime-type type="application/x-turtle-art">
+<comment xml:lang="en">Turtle Art</comment>
+<glob pattern="*.ta"/>
+</mime-type>
+</mime-info>
diff --git a/activity/turtleart.svg b/activity/turtleart.svg
new file mode 100644
index 0000000..433c130
--- /dev/null
+++ b/activity/turtleart.svg
@@ -0,0 +1,83 @@
+<?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="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2">
+ <defs
+ id="defs40" />
+ <g
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,-6.8005861,7.9133051)"
+ id="g3661"
+ style="fill:#00a000;fill-opacity:1">
+ <path
+ d="m 27.497,48.279 c -0.553,0 -1.099,-0.035 -1.637,-0.1 l 1.388,2.349 1.368,-2.313 c -0.371,0.03 -0.741,0.064 -1.119,0.064 z"
+ id="path6"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-opacity:1" />
+ <g
+ id="g8"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="m 40.16,11.726 c -2.164,0 -3.958,1.555 -4.343,3.607 1.859,1.345 3.457,3.115 4.675,5.208 2.285,-0.172 4.094,-2.061 4.094,-4.39 0,-2.444 -1.982,-4.425 -4.426,-4.425 z"
+ id="path10"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 40.713,39.887 c -1.224,2.232 -2.86,4.131 -4.797,5.556 0.521,1.864 2.213,3.239 4.244,3.239 2.443,0 4.426,-1.98 4.426,-4.424 0,-2.255 -1.693,-4.096 -3.873,-4.371 z"
+ id="path12"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 14.273,39.871 c -2.253,0.206 -4.024,2.079 -4.024,4.387 0,2.443 1.98,4.424 4.424,4.424 2.064,0 3.784,-1.42 4.272,-3.332 -1.883,-1.416 -3.475,-3.289 -4.672,-5.479 z"
+ id="path14"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.026,15.437 c -0.343,-2.103 -2.154,-3.711 -4.353,-3.711 -2.444,0 -4.424,1.981 -4.424,4.424 0,2.382 1.886,4.31 4.245,4.406 1.186,-2.043 2.732,-3.784 4.532,-5.119 z"
+ id="path16"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ d="m 27.497,12.563 c 1.908,0 3.728,0.411 5.418,1.128 0.741,-1.076 1.178,-2.377 1.178,-3.783 0,-3.687 -2.989,-6.677 -6.677,-6.677 -3.687,0 -6.676,2.99 -6.676,6.677 0,1.428 0.452,2.749 1.216,3.834 1.724,-0.749 3.587,-1.179 5.541,-1.179 z"
+ id="path18"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g20"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="m 43.102,30.421 c 0,4.7344 -1.6452,9.2798 -4.5706,12.6275 -2.9254,3.3478 -6.8973,5.2305 -11.0344,5.2305 -4.1371,0 -8.109,-1.8827 -11.0344,-5.2305 -2.9254,-3.3477 -4.5706,-7.8931 -4.5706,-12.6275 0,-9.7966 7.0444,-17.858 15.605,-17.858 8.5606,0 15.605,8.0614 15.605,17.858 z"
+ id="path22"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ id="g24"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="m 25.875,33.75 -1.542,-4.625 3.164,-2.587 3.615,2.626 -1.487,4.669 z"
+ id="path26"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 27.501,41.551 c -3.968,-0.16 -5.543,-2.009 -5.543,-2.009 l 3.57,-4.163 4.465,0.168 3.132,4.12 c 0,0 -2.89,1.994 -5.624,1.884 z"
+ id="path28"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 18.453,33.843 c -0.849,-2.968 0.172,-6.884 0.172,-6.884 l 4,2.167 1.493,4.629 -3.582,4.233 c 0,-10e-4 -1.465,-1.99 -2.083,-4.145 z"
+ id="path30"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.458,25.125 c 0,0 0.5,-1.958 3.039,-3.822 2.237,-1.643 4.465,-1.72 4.465,-1.72 l -0.037,4.981 -3.521,2.75 -3.946,-2.189 z"
+ id="path32"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 32.084,27.834 28.625,24.959 29,19.75 c 0,0 1.834,-0.042 3.959,1.667 2.228,1.791 3.362,4.983 3.362,4.983 l -4.237,1.434 z"
+ id="path34"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 31.292,34.042 1.313,-4.464 4.187,-1.536 c 0,0 0.677,2.663 -0.042,5.667 -0.54,2.256 -2.084,4.361 -2.084,4.361 l -3.374,-4.028 z"
+ id="path36"
+ style="fill:#00a000;fill-opacity:1;stroke:#004000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+</svg>
diff --git a/amazonas-tortuga.py b/amazonas-tortuga.py
new file mode 100755
index 0000000..e639e38
--- /dev/null
+++ b/amazonas-tortuga.py
@@ -0,0 +1,712 @@
+#!/usr/bin/env python
+#Copyright (c) 2007-8, Playful Invention Company
+#Copyright (c) 2008-12, Walter Bender
+#Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+
+#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.
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import cairo
+
+import getopt
+import sys
+import os
+import os.path
+import cStringIO
+import errno
+import ConfigParser
+
+try:
+ # Try to use XDG Base Directory standard for config files.
+ import xdg.BaseDirectory
+ CONFIG_HOME = os.path.join(xdg.BaseDirectory.xdg_config_home, 'turtleart')
+except ImportError, e:
+ # Default to `.config` per the spec.
+ CONFIG_HOME = os.path.expanduser(os.path.join('~', '.config', 'turtleart'))
+
+argv = sys.argv[:] # Workaround for import behavior of gst in tagplay
+sys.argv[1:] = [] # Execution of import gst cannot see '--help' or '-h'
+
+import gettext
+
+# Commenting out bindtextdomain so that GNOME can find the installed .mo files,
+# but it will not find the .mo files in the local locale directory when running
+# from a git repository or the unzipped .xo file.
+# gettext.bindtextdomain('org.laptop.TurtleArtActivity', 'locale')
+
+import gettext
+
+from TurtleArt.taconstants import OVERLAY_LAYER, DEFAULT_TURTLE_COLORS
+from TurtleArt.tautils import data_to_string, data_from_string, get_save_name
+from TurtleArt.tawindow import TurtleArtWindow
+from TurtleArt.taexporthtml import save_html
+from TurtleArt.taexportlogo import save_logo
+
+from util.menubuilder import MenuBuilder
+
+
+class TurtleMain():
+ ''' Launch Turtle Confusion from outside of Sugar. '''
+ _INSTALL_PATH = '/usr/share/sugar/activities/TurtleArt.activity'
+ _ALTERNATIVE_INSTALL_PATH = \
+ '/usr/local/share/sugar/activities/TurtleArt.activity'
+ _ICON_SUBPATH = 'images/turtle.png'
+ _GNOME_PLUGIN_SUBPATH = 'gnome_plugins'
+
+ def __init__(self):
+ self._abspath = os.path.abspath('.')
+ self._execdirname = self._get_execution_dir()
+ if self._execdirname is not None:
+ os.chdir(self._execdirname)
+ file_activity_info = ConfigParser.ConfigParser()
+ activity_info_path = os.path.abspath('./activity/activity.info')
+ file_activity_info.read(activity_info_path)
+ bundle_id = file_activity_info.get('Activity', 'bundle_id')
+ gettext.textdomain(bundle_id)
+ global _
+ _ = gettext.gettext
+ self._HELP_MSG = 'turtleart.py: ' + _('usage is') + '''
+ \tturtleconfusion.py
+ \tturtleconfusion.py project.ta
+ \tturtleconfusion.py --output_png project.ta
+ \tturtleconfusion.py -o project
+ \tturtleconfusion.py --run project.ta
+ \tturtleconfusion.py -r project'''
+ self._init_vars()
+ self._parse_command_line()
+ self._ensure_sugar_paths()
+ self._plugins = []
+
+ if self._output_png:
+ # Fix me: We need to create a cairo surface to work with
+ pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8,
+ gtk.gdk.screen_width(),
+ gtk.gdk.screen_height())
+ # self.canvas, mask = pixbuf.render_pixmap_and_mask()
+ self.canvas = pixbuf
+ self._build_window(interactive=False)
+ self._draw_and_quit()
+ else:
+ self._read_initial_pos()
+ self._init_plugins()
+ self._setup_gtk()
+ self._build_window()
+ self._run_plugins()
+ self._start_gtk()
+
+ def get_config_home(self):
+ return CONFIG_HOME
+
+ def _get_gnome_plugin_home(self):
+ ''' Use plugin directory associated with execution path. '''
+ if os.path.exists(os.path.join(self._execdirname,
+ self._GNOME_PLUGIN_SUBPATH)):
+ return os.path.join(self._execdirname, self._GNOME_PLUGIN_SUBPATH)
+ else:
+ return None
+
+ def _get_plugin_candidates(self, path):
+ ''' Look for plugin files in plugin directory. '''
+ plugin_files = []
+ if path is not None:
+ candidates = os.listdir(path)
+ for c in candidates:
+ if c[-10:] == '_plugin.py' and c[0] != '#' and c[0] != '.':
+ plugin_files.append(c.split('.')[0])
+ return plugin_files
+
+ def _init_plugins(self):
+ ''' Try launching any plugins we may have found. '''
+ for p in self._get_plugin_candidates(self._get_gnome_plugin_home()):
+ P = p.capitalize()
+ f = \
+ "def f(self): from gnome_plugins.%s import %s; return %s(self)" % (p, P, P)
+ plugin = {}
+ try:
+ exec f in globals(), plugin
+ self._plugins.append(plugin.values()[0](self))
+ except ImportError, e:
+ print 'failed to import %s: %s' % (P, str(e))
+
+ def _run_plugins(self):
+ ''' Tell the plugin about the TurtleWindow instance. '''
+ for p in self._plugins:
+ p.set_tw(self.tw)
+
+ def _mkdir_p(self, path):
+ '''Create a directory in a fashion similar to `mkdir -p`.'''
+ try:
+ os.makedirs(path)
+ except OSError, exc:
+ if exc.errno == errno.EEXIST:
+ pass
+ else:
+ raise
+
+ def _makepath(self, 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)
+
+ def _start_gtk(self):
+ ''' Get a main window set up. '''
+ self.win.connect('configure_event', self.tw.update_overlay_position)
+ self.tw.parent = self.win
+ if self._ta_file is None:
+ self.tw.load_start()
+ else:
+ print self._ta_file
+ self.tw.load_start(self._ta_file)
+ self.tw.lc.trace = 0
+ if self._run_on_launch:
+ self._do_run_cb()
+ gtk.main()
+
+ def _draw_and_quit(self):
+ ''' Non-interactive mode: run the project, save it to a file
+ and quit. '''
+ self.tw.load_start(self._ta_file)
+ self.tw.lc.trace = 0
+ self.tw.run_button(0)
+ self.tw.save_as_image(self._ta_file)
+
+ def _build_window(self, interactive=True):
+ ''' Initialize the TurtleWindow instance. '''
+ if interactive:
+ win = self.canvas.get_window()
+ cr = win.cairo_create()
+ surface = cr.get_target()
+ else:
+ img_surface = cairo.ImageSurface(cairo.FORMAT_RGB24,
+ 1024, 768)
+ cr = cairo.Context(img_surface)
+ surface = cr.get_target()
+ self.turtle_canvas = surface.create_similar(
+ cairo.CONTENT_COLOR, gtk.gdk.screen_width() * 2,
+ gtk.gdk.screen_height() * 2)
+ self.tw = TurtleArtWindow(self.canvas, self._execdirname,
+ turtle_canvas=self.turtle_canvas)
+ self.tw.save_folder = os.path.expanduser('~')
+
+ def _init_vars(self):
+ ''' If we are invoked to start a project from Gnome, we should make
+ sure our current directory is TA's source dir. '''
+ self._ta_file = None
+ self._output_png = False
+ self.i = 0 # FIXME: use a better name for this variable
+ self.scale = 2.0
+ self.tw = None
+
+ def _parse_command_line(self):
+ ''' Try to make sense of the command-line arguments. '''
+ try:
+ opts, args = getopt.getopt(argv[1:], 'hor',
+ ['help', '_output_png', 'run'])
+ except getopt.GetoptError, err:
+ print str(err)
+ print self._HELP_MSG
+ sys.exit(2)
+ self._run_on_launch = False
+ for o, a in opts:
+ if o in ('-h', '--help'):
+ print self._HELP_MSG
+ sys.exit()
+ if o in ('-o', '--output_png'):
+ self._output_png = True
+ elif o in ('-r', '--run'):
+ self._run_on_launch = True
+ else:
+ assert False, _('No option action:') + ' ' + o
+ if args:
+ self._ta_file = args[0]
+
+ if len(args) > 1 or self._output_png and self._ta_file is None:
+ print self._HELP_MSG
+ sys.exit()
+
+ if self._ta_file is not None:
+ if not self._ta_file.endswith(('.ta')):
+ self._ta_file += '.ta'
+ if not os.path.exists(self._ta_file):
+ assert False, ('%s: %s' % (self._ta_file, _('File not found')))
+
+ def _ensure_sugar_paths(self):
+ ''' Make sure Sugar paths are present. '''
+ tapath = os.path.join(os.environ['HOME'], '.sugar', 'default',
+ 'org.laptop.TurtleArtActivity')
+ map(self._makepath, (os.path.join(tapath, 'data/'),
+ os.path.join(tapath, 'instance/')))
+
+ def _read_initial_pos(self):
+ ''' Read saved configuration. '''
+ try:
+ data_file = open(os.path.join(CONFIG_HOME, 'turtleartrc'), 'r')
+ except IOError:
+ # Opening the config file failed
+ # We'll assume it needs to be created
+ try:
+ self._mkdir_p(CONFIG_HOME)
+ data_file = open(os.path.join(CONFIG_HOME, 'turtleartrc'),
+ 'a+')
+ except IOError, e:
+ # We can't write to the configuration file, use
+ # a faux file that will persist for the length of
+ # the session.
+ print _('Configuration directory not writable: %s') % (e)
+ data_file = cStringIO.StringIO()
+ 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)
+ try:
+ self.x = int(data_file.readline())
+ self.y = int(data_file.readline())
+ self.width = int(data_file.readline())
+ self.height = int(data_file.readline())
+ except ValueError:
+ self.x = 50
+ self.y = 50
+ self.width = 800
+ self.height = 550
+
+ def _setup_gtk(self):
+ ''' Set up a scrolled window in which to run Turtle Blocks. '''
+ win = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ win.set_default_size(self.width, self.height)
+ win.move(self.x, self.y)
+ win.maximize()
+ win.set_title(_('Turtle Art'))
+ if os.path.exists(os.path.join(self._execdirname, self._ICON_SUBPATH)):
+ win.set_icon_from_file(os.path.join(self._execdirname,
+ self._ICON_SUBPATH))
+ win.connect('delete_event', self._quit_ta)
+
+ vbox = gtk.VBox(False, 0)
+ win.add(vbox)
+ vbox.show()
+
+ menu_bar = self._get_menu_bar()
+ vbox.pack_start(menu_bar, False, False, 2)
+ menu_bar.show()
+
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ sw.show()
+ canvas = gtk.DrawingArea()
+ width = gtk.gdk.screen_width() * 2
+ height = gtk.gdk.screen_height() * 2
+ canvas.set_size_request(width, height)
+ sw.add_with_viewport(canvas)
+ canvas.show()
+ vbox.pack_end(sw, True, True)
+
+ win.show_all()
+ self.win = win
+ self.canvas = canvas
+
+ def _get_menu_bar(self):
+ ''' Instead of Sugar toolbars, use GNOME menus. '''
+ menu = gtk.Menu()
+ MenuBuilder.make_menu_item(menu, _('New'), self._do_new_cb)
+ MenuBuilder.make_menu_item(menu, _('Open'), self._do_open_cb)
+ MenuBuilder.make_menu_item(menu, _('Save'), self._do_save_cb)
+ MenuBuilder.make_menu_item(menu, _('Save as'), self._do_save_as_cb)
+ MenuBuilder.make_menu_item(menu, _('Save as image'),
+ self._do_save_picture_cb)
+ MenuBuilder.make_menu_item(menu, _('Save as HTML'),
+ self._do_save_html_cb)
+ MenuBuilder.make_menu_item(menu, _('Save as Logo'),
+ self._do_save_logo_cb)
+ MenuBuilder.make_menu_item(menu, _('Quit'), self.destroy)
+ activity_menu = MenuBuilder.make_sub_menu(menu, _('File'))
+
+ menu = gtk.Menu()
+ MenuBuilder.make_menu_item(menu, _('Cartesian coordinates'),
+ self._do_cartesian_cb)
+ MenuBuilder.make_menu_item(menu, _('Polar coordinates'),
+ self._do_polar_cb)
+ MenuBuilder.make_menu_item(menu, _('Rescale coordinates'),
+ self._do_rescale_cb)
+ MenuBuilder.make_menu_item(menu, _('Grow blocks'),
+ self._do_resize_cb, 1.5)
+ MenuBuilder.make_menu_item(menu, _('Shrink blocks'),
+ self._do_resize_cb, 0.667)
+ MenuBuilder.make_menu_item(menu, _('Reset block size'),
+ self._do_resize_cb, -1)
+ MenuBuilder.make_menu_item(menu, _('Turn off hover help'),
+ self._do_hover_help_off_cb)
+ MenuBuilder.make_menu_item(menu, _('Turn on hover help'),
+ self._do_hover_help_on_cb)
+ view_menu = MenuBuilder.make_sub_menu(menu, _('View'))
+
+ menu = gtk.Menu()
+ MenuBuilder.make_menu_item(menu, _('Copy'), self._do_copy_cb)
+ MenuBuilder.make_menu_item(menu, _('Paste'), self._do_paste_cb)
+ edit_menu = MenuBuilder.make_sub_menu(menu, _('Edit'))
+
+ menu = gtk.Menu()
+ MenuBuilder.make_menu_item(menu, _('Show palette'),
+ self._do_palette_cb)
+ MenuBuilder.make_menu_item(menu, _('Hide palette'),
+ self._do_hide_palette_cb)
+ MenuBuilder.make_menu_item(menu, _('Show/hide blocks'),
+ self._do_hideshow_cb)
+ tool_menu = MenuBuilder.make_sub_menu(menu, _('Tools'))
+
+ menu = gtk.Menu()
+ MenuBuilder.make_menu_item(menu, _('Clean'), self._do_eraser_cb)
+ MenuBuilder.make_menu_item(menu, _('Run'), self._do_run_cb)
+ MenuBuilder.make_menu_item(menu, _('Step'), self._do_step_cb)
+ MenuBuilder.make_menu_item(menu, _('Stop'), self._do_stop_cb)
+ turtle_menu = MenuBuilder.make_sub_menu(menu, _('Turtle'))
+
+
+ menu = gtk.Menu()
+ self._level = 0
+ self._levels = self._get_levels()
+ self._custom_filepath = None
+ for i in range(40):
+ MenuBuilder.make_menu_item(menu, _('Challenge') + ' ' + str(i + 1),
+ self._do_level_cb, i)
+ turtle_menu = MenuBuilder.make_sub_menu(menu, _('Challenges'))
+
+ menu_bar = gtk.MenuBar()
+ menu_bar.append(activity_menu)
+ menu_bar.append(edit_menu)
+ menu_bar.append(view_menu)
+ menu_bar.append(tool_menu)
+ menu_bar.append(turtle_menu)
+
+ # Add menus for plugins
+ for p in self._plugins:
+ menu_bar.append(p.get_menu())
+ return menu_bar
+
+ def _quit_ta(self, widget=None, e=None):
+ ''' Save changes on exit '''
+ project_empty = self.tw.is_project_empty()
+ if not project_empty:
+ if self.tw.is_new_project():
+ self._show_save_dialog(True)
+ else:
+ if self.tw.project_has_changed():
+ self._show_save_dialog(False)
+ gtk.main_quit()
+
+ def _show_save_dialog(self, new_project=True):
+ ''' Dialog for save project '''
+ dlg = gtk.MessageDialog(parent=None, type=gtk.MESSAGE_INFO,
+ buttons=gtk.BUTTONS_OK_CANCEL,
+ message_format=_(
+ 'You have unsaved work. Would you like to save before quitting?'))
+ dlg.set_title(_('Save project?'))
+ dlg.set_property('skip-taskbar-hint', False)
+
+ resp = dlg.run()
+ dlg.destroy()
+ if resp == gtk.RESPONSE_OK:
+ if new_project:
+ self._save_as()
+ else:
+ self._save_changes()
+
+ def _do_new_cb(self, widget):
+ ''' Callback for new project. '''
+ self.tw.new_project()
+ self.tw.load_start()
+
+ def _do_open_cb(self, widget):
+ ''' Callback for open project. '''
+ self.tw.load_file(True)
+
+ def _do_save_cb(self, widget):
+ ''' Callback for save project. '''
+ self.tw.save_file()
+
+ def _do_save_as_cb(self, widget):
+ ''' Callback for save-as project. '''
+ self._save_as()
+
+ def _save_as(self):
+ ''' Save as is called from callback and quit '''
+ self.tw.save_file_name = None
+ self.tw.save_file()
+
+ def _save_changes(self):
+ ''' Save changes to current project '''
+ self.tw.save_file_name = None
+ self.tw.save_file(self.tw._loaded_project)
+
+ def _do_save_picture_cb(self, widget):
+ ''' Callback for save canvas. '''
+ self.tw.save_as_image()
+
+ def _do_save_html_cb(self, widget):
+ ''' Callback for save project to HTML. '''
+ html = save_html(self, self.tw, False)
+ if len(html) == 0:
+ return
+ save_type = '.html'
+ if len(self.tw.saved_pictures) > 0:
+ if self.tw.saved_pictures[0].endswith(('.svg')):
+ save_type = '.xml'
+ filename, self.tw.load_save_folder = get_save_name(save_type,
+ self.tw.load_save_folder, 'portfolio')
+ f = file(filename, 'w')
+ f.write(html)
+ f.close()
+ self.tw.saved_pictures = []
+
+ def _do_save_logo_cb(self, widget):
+ ''' Callback for save project to Logo. '''
+ logocode = save_logo(self.tw)
+ if len(logocode) == 0:
+ return
+ save_type = '.lg'
+ filename, self.tw.load_save_folder = get_save_name(save_type,
+ self.tw.load_save_folder, 'logosession')
+ f = file(filename, 'w')
+ f.write(logocode)
+ f.close()
+
+ def _do_resize_cb(self, widget, factor):
+ ''' Callback to resize blocks. '''
+ if factor == -1:
+ self.tw.block_scale = 2.0
+ else:
+ self.tw.block_scale *= factor
+ self.tw.resize_blocks()
+
+ def _do_cartesian_cb(self, button):
+ ''' Callback to display/hide Cartesian coordinate overlay. '''
+ self.tw.set_cartesian(True)
+
+ def _do_polar_cb(self, button):
+ ''' Callback to display/hide Polar coordinate overlay. '''
+ self.tw.set_polar(True)
+
+ def _do_rescale_cb(self, button):
+ ''' Callback to rescale coordinate space. '''
+ if self.tw.coord_scale == 1:
+ self.tw.coord_scale = self.tw.height / 200
+ self.tw.eraser_button()
+ if self.tw.cartesian is True:
+ self.tw.overlay_shapes['Cartesian_labeled'].hide()
+ self.tw.overlay_shapes['Cartesian'].set_layer(OVERLAY_LAYER)
+ else:
+ self.tw.coord_scale = 1
+ self.tw.eraser_button()
+ if self.tw.cartesian is True:
+ self.tw.overlay_shapes['Cartesian'].hide()
+ self.tw.overlay_shapes['Cartesian_labeled'].set_layer(
+ OVERLAY_LAYER)
+
+ def _do_hover_help_on_cb(self, button):
+ ''' Turn hover help on '''
+ self.tw.no_help = False
+
+ def _do_hover_help_off_cb(self, button):
+ ''' Turn hover help off '''
+ self.tw.no_help = True
+ self.tw.last_label = None
+ self.tw.status_spr.hide()
+
+ def _do_palette_cb(self, widget):
+ ''' Callback to show/hide palette of blocks. '''
+ self.tw.show_palette(self.i)
+ self.i += 1
+ if self.i == len(self.tw.palettes):
+ self.i = 0
+
+ def _do_hide_palette_cb(self, widget):
+ ''' Hide the palette of blocks. '''
+ self.tw.hide_palette()
+
+ def _do_hideshow_cb(self, widget):
+ ''' Hide/show the blocks. '''
+ self.tw.hideshow_button()
+
+ def _do_eraser_cb(self, widget):
+ ''' Callback for eraser button. '''
+ self.tw.eraser_button()
+ return
+
+ def _do_run_cb(self, widget):
+ ''' Callback for run button (rabbit). '''
+ self.tw.lc.trace = 0
+ self.tw.hideblocks()
+ self.tw.run_button(0, running_from_button_push=True)
+ return
+
+ def _do_step_cb(self, widget):
+ ''' Callback for step button (turtle). '''
+ self.tw.lc.trace = 0
+ self.tw.run_button(3, running_from_button_push=True)
+ return
+
+ def _do_trace_cb(self, widget):
+ ''' Callback for debug button (bug). '''
+ self.tw.lc.trace = 1
+ self.tw.run_button(9, running_from_button_push=True)
+ return
+
+ def _do_stop_cb(self, widget):
+ ''' Callback for stop button. '''
+ self.tw.lc.trace = 0
+ self.tw.showblocks()
+ self.tw.stop_button()
+ return
+
+ def restore_challenge(self):
+ ''' Restore the current challange after a clear screen '''
+ if self._custom_filepath is None:
+ self._load_level()
+ else:
+ self._load_level(custom=True)
+
+ def _load_level(self, custom=False):
+ self.tw.canvas.clearscreen()
+ if custom:
+ self.tw.canvas.setxy(0, 0, pendown=False)
+ self.tw.lc.insert_image(center=True,
+ filepath=self._custom_filepath,
+ resize=True, offset=False)
+ else:
+ self.tw.canvas.setxy(int(-gtk.gdk.screen_width() / 2), 0,
+ pendown=False)
+ self.tw.lc.insert_image(center=False, resize=False,
+ filepath=os.path.join(
+ self._get_execution_dir(), 'images',
+ 'turtle-a.png'))
+ # Slight offset to account for stroke width
+ if self._level + 1 in self.offsets:
+ xoffset = self.offsets[self._level + 1][0]
+ yoffset = self.offsets[self._level + 1][1]
+ else:
+ xoffset = 0
+ yoffset = 0
+ self.tw.canvas.setxy(-2.5 + xoffset, -2.5 + yoffset, pendown=False)
+ self.tw.lc.insert_image(center=False,
+ filepath=os.path.join(
+ self._get_execution_dir(), 'challenges',
+ self._levels[self._level] + '.svg'), resize=False,
+ offset=True)
+ self.tw.canvas.setxy(0, 0, pendown=False)
+
+ def _do_level_cb(self, widget, level):
+ ''' Callback to resize blocks. '''
+ self._level = level
+ self._load_level()
+
+ def _get_levels(self):
+ ''' Look for level files in lessons directory. '''
+ level_files = []
+
+ for i in range(40):
+ level_files.append('confusion-%d' % (i+1))
+
+ self.offsets = {}
+ offset_fd = open(os.path.join(self._get_execution_dir(), 'challenges',
+ 'offsets'))
+ for line in offset_fd:
+ try:
+ idx, offsets = line.strip('\n').split(':')
+ xoffset, yoffset = offsets.split(',')
+ self.offsets[int(idx)] = (int(xoffset), int(yoffset))
+ except ValueError:
+ pass
+ offset_fd.close()
+ return level_files
+
+ def _do_copy_cb(self, button):
+ ''' Callback for copy button. '''
+ clipBoard = gtk.Clipboard()
+ data = self.tw.assemble_data_to_save(False, False)
+ if data is not []:
+ text = data_to_string(data)
+ clipBoard.set_text(text)
+
+ def _do_paste_cb(self, button):
+ ''' Callback for paste button. '''
+ clipBoard = gtk.Clipboard()
+ text = clipBoard.wait_for_text()
+ if text is not None:
+ if self.tw.selected_blk is not None and\
+ self.tw.selected_blk.name == 'string':
+ for i in text:
+ self.tw.process_alphanumeric_input(i, -1)
+ self.tw.selected_blk.resize()
+ else:
+ self.tw.process_data(data_from_string(text),
+ self.tw.paste_offset)
+ self.tw.paste_offset += 20
+
+ def _window_event(self, event, data):
+ ''' Callback for resize event. '''
+ data_file = open('.turtleartrc', 'w')
+ data_file.write(str(data.x) + '\n')
+ data_file.write(str(data.y) + '\n')
+ data_file.write(str(data.width) + '\n')
+ data_file.write(str(data.height) + '\n')
+
+ def destroy(self, event, data=None):
+ ''' Callback for destroy event. '''
+ gtk.main_quit()
+
+ def nick_changed(self, nick):
+ ''' TODO: Rename default turtle in dictionary '''
+ pass
+
+ def color_changed(self, colors):
+ ''' Reskin turtle with collaboration colors '''
+ turtle = self.tw.turtles.get_turtle(self.tw.default_turtle_name)
+ try:
+ turtle.colors = colors.split(',')
+ except:
+ turtle.colors = DEFAULT_TURTLE_COLORS
+ turtle.custom_shapes = True # Force regeneration of shapes
+ turtle.reset_shapes()
+ turtle.show()
+
+ def _get_execution_dir(self):
+ ''' From whence is the program being executed? '''
+ dirname = os.path.dirname(__file__)
+ if dirname == '':
+ if os.path.exists(os.path.join('~', 'Activities',
+ 'TurtleArt.activity')):
+ return os.path.join('~', 'Activities', 'TurtleArt.activity')
+ elif os.path.exists(self._INSTALL_PATH):
+ return self._INSTALL_PATH
+ elif os.path.exists(self._ALTERNATIVE_INSTALL_PATH):
+ return self._ALTERNATIVE_INSTALL_PATH
+ else:
+ return os.path.abspath('.')
+ else:
+ return os.path.abspath(dirname)
+
+
+if __name__ == '__main__':
+ TurtleMain()
diff --git a/challenges/amazonas-1.svg b/challenges/amazonas-1.svg
new file mode 100644
index 0000000..85f443e
--- /dev/null
+++ b/challenges/amazonas-1.svg
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,133.17736,19.304487)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+</svg>
diff --git a/challenges/amazonas-10.svg b/challenges/amazonas-10.svg
new file mode 100644
index 0000000..1739638
--- /dev/null
+++ b/challenges/amazonas-10.svg
@@ -0,0 +1,411 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4131"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4133"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-69"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-96"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-87"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-40"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4285"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4287"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="M 3.1842866,103.10283 98.288312,47.696243 193.39234,102.89054 97.439169,158.29713 z"
+ id="path5489"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:#7f7f7f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ id="g4083">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,226.35918,76.594049)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <path
+ d="M 28.021722,103.5274 98.500598,62.131676 168.97947,103.5274 98.076026,144.28627 z"
+ id="path6119"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <g
+ transform="matrix(0.5,0,0,0.5,48.975654,51.68773)"
+ id="g4083-1"
+ style="stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ d="M 52.2223,103.5274 98.288312,76.354822 145.62804,103.31512 97.651455,130.48769 z"
+ id="path6121"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:none" />
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,-28.552651,25.23772)"
+ id="g3994-8">
+ <path
+ d="m 146.16597,-108.12177 0,14.585629"
+ id="path2995-3-1-8"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="74.397118"
+ y="150.22018"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3-5"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="74.397118"
+ y="150.22018"
+ id="tspan4235-7-8"
+ style="font-size:10px">55</tspan></text>
+ <path
+ d="m 146.32105,-52.352642 0,-14.585631"
+ id="path2995-3-1-8-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <g
+ transform="matrix(0.74289795,0,0,0.74289795,25.183482,26.578043)"
+ id="g4083-1-1"
+ style="stroke-width:6.73039961;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(30.255316,19.03157)"
+ id="g4321">
+ <path
+ d="M 92.428426,57.769579 79.796901,65.062393"
+ id="path2995-3-1-8-3"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="4.121335"
+ y="99.936424"
+ transform="matrix(0.8660254,-0.5,0.5,0.8660254,0,0)"
+ id="text4233-3-5-7"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="4.121335"
+ y="99.936424"
+ id="tspan4235-7-8-7"
+ style="font-size:10px">77.5</tspan></text>
+ <path
+ d="M 22.004987,98.720153 34.636514,91.427337"
+ id="path2995-3-1-8-1-7"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-11.svg b/challenges/amazonas-11.svg
new file mode 100644
index 0000000..a36058c
--- /dev/null
+++ b/challenges/amazonas-11.svg
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="600"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <g
+ transform="translate(0.54624033,-0.8754605)"
+ id="g3003">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,141.92967,23.204117)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <g
+ transform="translate(192.45868,-0.8754605)"
+ id="g3003-9">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-45"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(384.37068,-0.8754605)"
+ id="g3003-4">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-12.svg b/challenges/amazonas-12.svg
new file mode 100644
index 0000000..bd79359
--- /dev/null
+++ b/challenges/amazonas-12.svg
@@ -0,0 +1,483 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="600"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-87"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-69"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-96"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4131"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4133"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-0"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-16"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <g
+ transform="translate(0.54624033,-0.8754605)"
+ id="g3003">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,135.92967,21.204117)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <g
+ transform="translate(192.45868,-0.8754605)"
+ id="g3003-9">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-45"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(384.37068,-0.8754605)"
+ id="g3003-4">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(217.00814,135.42497)"
+ id="g3547-0">
+ <path
+ d="m -166.48859,-33.26589 47.72799,-27.33268"
+ id="path2993-0-8"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-33.26589 -47.728,-27.33268"
+ id="path2993-9-2-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -166.48859,-32.58405 47.72799,27.33268"
+ id="path2993-5-7-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-32.58405 -47.728,27.33268"
+ id="path2993-9-4-4-6"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(408.92058,135.42497)"
+ id="g3547-0-0">
+ <path
+ d="m -166.48859,-33.26589 47.72799,-27.33268"
+ id="path2993-0-8-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-33.26589 -47.728,-27.33268"
+ id="path2993-9-2-5-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -166.48859,-32.58405 47.72799,27.33268"
+ id="path2993-5-7-9-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-32.58405 -47.728,27.33268"
+ id="path2993-9-4-4-6-6"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(600.83258,135.42497)"
+ id="g3547-0-3">
+ <path
+ d="m -166.48859,-33.26589 47.72799,-27.33268"
+ id="path2993-0-8-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-33.26589 -47.728,-27.33268"
+ id="path2993-9-2-5-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -166.48859,-32.58405 47.72799,27.33268"
+ id="path2993-5-7-9-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-32.58405 -47.728,27.33268"
+ id="path2993-9-4-4-6-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(173.8,109.4)"
+ id="g3628">
+ <path
+ d="m -63.899696,-27.804392 -12.631525,7.292814"
+ id="path2995-3-1-8"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-75.161217"
+ y="-51.974945"
+ transform="matrix(0.8660254,-0.5,0.5,0.8660254,0,0)"
+ id="text4233-3-5"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-75.161217"
+ y="-51.974945"
+ id="tspan4235-7-8"
+ style="font-size:10px">55</tspan></text>
+ <path
+ d="M -112.11964,0.21447485 -99.48811,-7.0783406"
+ id="path2995-3-1-8-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-13.svg b/challenges/amazonas-13.svg
new file mode 100644
index 0000000..562c399
--- /dev/null
+++ b/challenges/amazonas-13.svg
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="600"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <g
+ transform="translate(0.54624033,-0.8754605)"
+ id="g3003">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,136.79609,19.995628)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <g
+ transform="translate(192.45868,-0.8754605)"
+ id="g3003-9">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-45"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(384.37068,-0.8754605)"
+ id="g3003-4">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ d="m 52.619212,101.68667 45.560537,-27.593002 46.843931,28.234702 -46.843931,27.593 z"
+ id="path3728"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:none" />
+ <g
+ transform="translate(217.00814,135.42497)"
+ id="g3547-0">
+ <path
+ d="m -166.48859,-33.26589 47.72799,-27.33268"
+ id="path2993-0-8"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-33.26589 -47.728,-27.33268"
+ id="path2993-9-2-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -166.48859,-32.58405 47.72799,27.33268"
+ id="path2993-5-7-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-32.58405 -47.728,27.33268"
+ id="path2993-9-4-4-6"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ d="m 244.20775,102.17915 45.56054,-27.592998 46.84393,28.234698 -46.84393,27.593 z"
+ id="path3728-2"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:none" />
+ <g
+ transform="translate(408.92058,135.42497)"
+ id="g3547-0-5">
+ <path
+ d="m -166.48859,-33.26589 47.72799,-27.33268"
+ id="path2993-0-8-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-33.26589 -47.728,-27.33268"
+ id="path2993-9-2-5-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -166.48859,-32.58405 47.72799,27.33268"
+ id="path2993-5-7-9-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-32.58405 -47.728,27.33268"
+ id="path2993-9-4-4-6-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ d="m 436.11975,102.17915 45.56053,-27.592999 46.84394,28.234699 -46.84394,27.593 z"
+ id="path3728-29"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:none" />
+ <g
+ transform="translate(600.83258,135.42497)"
+ id="g3547-0-0">
+ <path
+ d="m -166.48859,-33.26589 47.72799,-27.33268"
+ id="path2993-0-8-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-33.26589 -47.728,-27.33268"
+ id="path2993-9-2-5-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -166.48859,-32.58405 47.72799,27.33268"
+ id="path2993-5-7-9-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -70.5326,-32.58405 -47.728,27.33268"
+ id="path2993-9-4-4-6-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-14.svg b/challenges/amazonas-14.svg
new file mode 100644
index 0000000..b50d5ad
--- /dev/null
+++ b/challenges/amazonas-14.svg
@@ -0,0 +1,656 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="600"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4285"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4287"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-40"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-87"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-69"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-96"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4131"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4133"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-31"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-07"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-0"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4909"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4911"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4913"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4915"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4917"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4919"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <g
+ transform="translate(0.54624033,-0.8754605)"
+ id="g3003">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,225.58043,75.710049)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <g
+ transform="translate(192.45868,-0.8754605)"
+ id="g3003-9">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-45"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(384.37068,-0.8754605)"
+ id="g3003-4">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(656.91244,194.08649)"
+ id="g5025">
+ <g
+ transform="matrix(0.5,0,0,0.5,-223.56611,-143.27422)"
+ id="g4083-1"
+ style="stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.74289795,0,0,0.74289795,-247.35828,-168.38391)"
+ id="g4083-1-1"
+ style="stroke-width:6.73039961;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+ <g
+ transform="translate(465.00044,194.08649)"
+ id="g4875-2">
+ <g
+ transform="matrix(0.5,0,0,0.5,-223.56611,-143.27422)"
+ id="g4083-1-0"
+ style="stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,-301.09441,-169.72423)"
+ id="g3994-8-8">
+ <path
+ d="m 146.16597,-108.12177 0,14.585629"
+ id="path2995-3-1-8-37"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="74.397118"
+ y="150.22018"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3-5-0"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="74.397118"
+ y="150.22018"
+ id="tspan4235-7-8-9"
+ style="font-size:10px">55</tspan></text>
+ <path
+ d="m 146.32105,-52.352642 0,-14.585631"
+ id="path2995-3-1-8-1-73"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <g
+ transform="matrix(0.74289795,0,0,0.74289795,-247.35828,-168.38391)"
+ id="g4083-1-1-9"
+ style="stroke-width:6.73039961;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-9-8"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-3-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-0-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-9-6"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(-242.28644,-175.93038)"
+ id="g4321-7">
+ <path
+ d="M 92.428426,57.769579 79.796901,65.062393"
+ id="path2995-3-1-8-3-2"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="4.121335"
+ y="99.936424"
+ transform="matrix(0.8660254,-0.5,0.5,0.8660254,0,0)"
+ id="text4233-3-5-7-9"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="4.121335"
+ y="99.936424"
+ id="tspan4235-7-8-7-4"
+ style="font-size:10px">77.5</tspan></text>
+ <path
+ d="M 22.004987,98.720153 34.636514,91.427337"
+ id="path2995-3-1-8-1-7-4"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ </g>
+ <g
+ transform="translate(273.088,194.08649)"
+ id="g5025-9">
+ <g
+ transform="matrix(0.5,0,0,0.5,-223.56611,-143.27422)"
+ id="g4083-1-9"
+ style="stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-10"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.74289795,0,0,0.74289795,-247.35828,-168.38391)"
+ id="g4083-1-1-7"
+ style="stroke-width:6.73039961;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-9-80"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-3-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-0-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-9-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+</svg>
diff --git a/challenges/amazonas-15.svg b/challenges/amazonas-15.svg
new file mode 100644
index 0000000..838c9a8
--- /dev/null
+++ b/challenges/amazonas-15.svg
@@ -0,0 +1,864 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="600"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4285"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4287"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-40"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-87"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-69"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-96"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4131"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4133"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-66"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4285-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4287-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-8-0"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-40-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-87-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-69-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-96-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4131-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4133-6"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5-0"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-44"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-53"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-0"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-6"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-62"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-6"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <g
+ transform="translate(0.54624033,-0.8754605)"
+ id="g3003">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,226.3516,75.641452)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <g
+ transform="translate(192.45868,-0.8754605)"
+ id="g3003-9">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-45"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(384.37068,-0.8754605)"
+ id="g3003-4">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ d="m 244.46869,102.43966 46.70287,-27.59716 46.70287,27.80944 -47.33972,27.59715 z"
+ id="path4173"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:none" />
+ <g
+ transform="matrix(0.5,0,0,0.5,241.43433,50.81227)"
+ id="g4083-1"
+ style="stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.74289795,0,0,0.74289795,217.64216,25.70258)"
+ id="g4083-1-1"
+ style="stroke-width:6.73039961;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(-294.81171,5.7017363)"
+ id="g5733">
+ <path
+ d="m 707.23048,96.407193 69.74789,-40.458739 71.98182,41.203379 -71.98182,40.706957 z"
+ id="path4840"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:none" />
+ <path
+ d="M 730.43083,96.692123 777.1337,69.094971 823.83657,96.904403 776.49684,124.50155 z"
+ id="path4173-2"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <g
+ transform="matrix(0.5,0,0,0.5,728.15804,45.110533)"
+ id="g4083-1-7"
+ style="stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-8"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-6"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.74289795,0,0,0.74289795,704.36587,20.000846)"
+ id="g4083-1-1-3"
+ style="stroke-width:6.73039961;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-9-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-3-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-0-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-9-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+ <g
+ transform="translate(-678.63615,5.7017363)"
+ id="g5733-0">
+ <path
+ d="m 707.23048,96.407193 69.74789,-40.458739 71.98182,41.203379 -71.98182,40.706957 z"
+ id="path4840-8"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:none" />
+ <path
+ d="M 730.43083,96.692123 777.1337,69.094971 823.83657,96.904403 776.49684,124.50155 z"
+ id="path4173-2-2"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <g
+ transform="matrix(0.5,0,0,0.5,728.15804,45.110533)"
+ id="g4083-1-7-5"
+ style="stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-8-6"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-6-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-2-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-5-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.74289795,0,0,0.74289795,704.36587,20.000846)"
+ id="g4083-1-1-3-0"
+ style="stroke-width:6.73039961;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-9-5-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-3-7-8"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-0-9-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-9-7-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,-29.742219,24.144679)"
+ id="g3994-8">
+ <path
+ d="m 146.16597,-108.12177 0,14.585629"
+ id="path2995-3-1-8"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="74.397118"
+ y="150.22018"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3-5"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="74.397118"
+ y="150.22018"
+ id="tspan4235-7-8"
+ style="font-size:10px">55</tspan></text>
+ <path
+ d="m 146.32105,-52.352642 0,-14.585631"
+ id="path2995-3-1-8-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <g
+ transform="translate(421.36663,23.378085)"
+ id="g4321">
+ <path
+ d="M 92.428426,57.769579 79.796901,65.062393"
+ id="path2995-3-1-8-3"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="4.121335"
+ y="99.936424"
+ transform="matrix(0.8660254,-0.5,0.5,0.8660254,0,0)"
+ id="text4233-3-5-7"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="4.121335"
+ y="99.936424"
+ id="tspan4235-7-8-7"
+ style="font-size:10px">77.5</tspan></text>
+ <path
+ d="M 22.004987,98.720153 34.636514,91.427337"
+ id="path2995-3-1-8-1-7"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-16.svg b/challenges/amazonas-16.svg
new file mode 100644
index 0000000..0dc222a
--- /dev/null
+++ b/challenges/amazonas-16.svg
@@ -0,0 +1,307 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 2.5,122.5 0,-40 40,0 0,-40 40,0 0,-40 40,0 0,40 40,0 0,40 40,0 0,40 -40,0 0,40 -40,0 0,40 -40,0 0,-40 -40,0 0,-40 z"
+ id="path3005"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g4049">
+ <path
+ d="m 85.498883,47.573331 7.74004,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="98.102356"
+ y="50.509941"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="98.102356"
+ y="50.509941"
+ id="tspan4235"
+ style="font-size:9.86077881px">40</tspan></text>
+ <path
+ d="m 120.18084,47.592547 -7.74004,0"
+ id="path2995-9"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-17.svg b/challenges/amazonas-17.svg
new file mode 100644
index 0000000..00b1228
--- /dev/null
+++ b/challenges/amazonas-17.svg
@@ -0,0 +1,316 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 2.5,122.5 0,-40 40,0 0,-40 40,0 0,-40 40,0 0,40 40,0 0,40 40,0 0,40 -40,0 0,40 -40,0 0,40 -40,0 0,-40 -40,0 0,-40 z"
+ id="path3005"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g4049">
+ <path
+ d="m 85.498883,47.573331 7.74004,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="98.102356"
+ y="50.509941"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="98.102356"
+ y="50.509941"
+ id="tspan4235"
+ style="font-size:9.86077881px">40</tspan></text>
+ <path
+ d="m 120.18084,47.592547 -7.74004,0"
+ id="path2995-9"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <rect
+ width="42.5"
+ height="42.5"
+ rx="24"
+ ry="0"
+ x="81.25"
+ y="81.25"
+ id="rect4074"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/challenges/amazonas-18.svg b/challenges/amazonas-18.svg
new file mode 100644
index 0000000..84ddacd
--- /dev/null
+++ b/challenges/amazonas-18.svg
@@ -0,0 +1,316 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 2.5,122.5 0,-40 40,0 0,-40 40,0 0,-40 40,0 0,40 40,0 0,40 40,0 0,40 -40,0 0,40 -40,0 0,40 -40,0 0,-40 -40,0 0,-40 z"
+ id="path3005"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g4049">
+ <path
+ d="m 85.498883,47.573331 7.74004,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="98.102356"
+ y="50.509941"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="98.102356"
+ y="50.509941"
+ id="tspan4235"
+ style="font-size:9.86077881px">40</tspan></text>
+ <path
+ d="m 120.18084,47.592547 -7.74004,0"
+ id="path2995-9"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <rect
+ width="42.5"
+ height="42.5"
+ rx="24"
+ ry="0"
+ x="81.25"
+ y="81.25"
+ id="rect4074"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/challenges/amazonas-19.svg b/challenges/amazonas-19.svg
new file mode 100644
index 0000000..d77ab2e
--- /dev/null
+++ b/challenges/amazonas-19.svg
@@ -0,0 +1,316 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 2.5,122.5 0,-40 40,0 0,-40 40,0 0,-40 40,0 0,40 40,0 0,40 40,0 0,40 -40,0 0,40 -40,0 0,40 -40,0 0,-40 -40,0 0,-40 z"
+ id="path3005"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g4049">
+ <path
+ d="m 85.498883,47.573331 7.74004,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="98.102356"
+ y="50.509941"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="98.102356"
+ y="50.509941"
+ id="tspan4235"
+ style="font-size:9.86077881px">40</tspan></text>
+ <path
+ d="m 120.18084,47.592547 -7.74004,0"
+ id="path2995-9"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <rect
+ width="42.5"
+ height="42.5"
+ rx="24"
+ ry="0"
+ x="81.25"
+ y="81.25"
+ id="rect4074"
+ style="fill:#ffffff;fill-opacity:1;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/challenges/amazonas-2.svg b/challenges/amazonas-2.svg
new file mode 100644
index 0000000..5def30e
--- /dev/null
+++ b/challenges/amazonas-2.svg
@@ -0,0 +1,266 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 2.5,102.5 c 135.97643,-0.69024 191.5,0 191.5,0"
+ id="path3003"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,226.35918,76.594049)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <g
+ transform="translate(-8.3170911,74.685141)"
+ id="g5715">
+ <g
+ transform="matrix(0.97234963,0,0,1,0.27715027,0)"
+ id="g4037">
+ <path
+ d="m 11.522128,100.84054 72.69378,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1.13166273px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 208.04135,100.85782 -72.69379,0"
+ id="path2995-3"
+ style="fill:none;stroke:#000000;stroke-width:1.13166285px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="100.58539"
+ y="104.48931"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="100.58539"
+ y="104.48931"
+ id="tspan4235"
+ style="font-size:10px">194</tspan></text>
+ </g>
+ </g>
+</svg>
diff --git a/challenges/amazonas-20.svg b/challenges/amazonas-20.svg
new file mode 100644
index 0000000..93b383b
--- /dev/null
+++ b/challenges/amazonas-20.svg
@@ -0,0 +1,329 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="405"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 2.5,122.5 0,-40 40,0 0,-40 40,0 0,-40 40,0 0,40 40,0 0,40 40,0 0,40 -40,0 0,40 -40,0 0,40 -40,0 0,-40 -40,0 0,-40 z"
+ id="path3005"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g4049">
+ <path
+ d="m 85.498883,47.573331 7.74004,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="98.102356"
+ y="50.509941"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="98.102356"
+ y="50.509941"
+ id="tspan4235"
+ style="font-size:9.86077881px">40</tspan></text>
+ <path
+ d="m 120.18084,47.592547 -7.74004,0"
+ id="path2995-9"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <rect
+ width="42.5"
+ height="42.5"
+ rx="24"
+ ry="0"
+ x="81.25"
+ y="81.25"
+ id="rect4074"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 202.5,122.5 0,-40 40,0 0,-40 40,0 0,-40 40,0 0,40 40,0 0,40 40,0 0,40 -40,0 0,40 -40,0 0,40 -40,0 0,-40 -40,0 0,-40 z"
+ id="path3005-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <rect
+ width="42.5"
+ height="42.5"
+ rx="24"
+ ry="0"
+ x="281.25"
+ y="81.25"
+ id="rect4074-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/challenges/amazonas-21.svg b/challenges/amazonas-21.svg
new file mode 100644
index 0000000..fbb1d64
--- /dev/null
+++ b/challenges/amazonas-21.svg
@@ -0,0 +1,329 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="405"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 202.5,122.5 -40,0 0,40 -40,0 0,40 -40,0 0,-40 -40,0 0,-40 -40,0 0,-40 40,0 0,-40 40,0 0,-40 40,0 0,40 40,0 0,40 40,0"
+ id="path3005"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g4049">
+ <path
+ d="m 85.498883,47.573331 7.74004,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="98.102356"
+ y="50.509941"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="98.102356"
+ y="50.509941"
+ id="tspan4235"
+ style="font-size:9.86077881px">40</tspan></text>
+ <path
+ d="m 120.18084,47.592547 -7.74004,0"
+ id="path2995-9"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <rect
+ width="42.5"
+ height="42.5"
+ rx="24"
+ ry="0"
+ x="81.25"
+ y="81.25"
+ id="rect4074"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 162.5,82.5 40,0 0,-40 40,0 0,-40 40,0 0,40 40,0 0,40 40,0 0,40 -40,0 0,40 -40,0 0,40 -40,0 0,-40 -40,0 0,-40 -40,0"
+ id="path3005-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <rect
+ width="42.5"
+ height="42.5"
+ rx="24"
+ ry="0"
+ x="241.25"
+ y="81.25"
+ id="rect4074-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/challenges/amazonas-22.svg b/challenges/amazonas-22.svg
new file mode 100644
index 0000000..6f1aa36
--- /dev/null
+++ b/challenges/amazonas-22.svg
@@ -0,0 +1,316 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 2.5,202.5 0,-200 40,0 0,40 40,0 0,40 40,0 0,40 40,0 0,40 40,0 0,40 z"
+ id="path3005"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g4049">
+ <path
+ d="m 85.498883,47.573331 7.74004,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="98.102356"
+ y="50.509941"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="98.102356"
+ y="50.509941"
+ id="tspan4235"
+ style="font-size:9.86077881px">40</tspan></text>
+ <path
+ d="m 120.18084,47.592547 -7.74004,0"
+ id="path2995-9"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <rect
+ width="200.06024"
+ height="200.06024"
+ rx="0"
+ ry="0"
+ x="2.4698792"
+ y="2.4698792"
+ id="rect3030"
+ style="fill:none;stroke:#7f7f7f;stroke-width:4.93975925;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/challenges/amazonas-23.svg b/challenges/amazonas-23.svg
new file mode 100644
index 0000000..159372d
--- /dev/null
+++ b/challenges/amazonas-23.svg
@@ -0,0 +1,453 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-54"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4046"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4048"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-48"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4090"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4092"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-31"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4138"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4140"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 3.1060653,201.89394 0,-198.7878769 198.7878647,0 0,198.7878769 z"
+ id="path4000"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.21212149;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 29.42,175.58 0,-146.16 146.16,0 0,146.16 z"
+ id="path4000-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.09000015;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 54.382351,150.61765 0,-96.235299 96.235299,0 0,96.235299 z"
+ id="path4000-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.01470566;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 79.277779,125.72222 0,-46.444441 46.444441,0 0,46.444441 z"
+ id="path4000-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5.80555534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(1.1846986,0,0,1.2024474,-227.84119,-42.479606)"
+ id="g4049-9">
+ <path
+ d="m 241.2406,89.573331 7.74004,0"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="273.16318"
+ y="91.925209"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233-4"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="273.16318"
+ y="91.925209"
+ id="tspan4235-6"
+ style="font-size:9.86077881px">100</tspan></text>
+ <path
+ d="m 316.43912,89.592547 -7.74004,0"
+ id="path2995-9-5"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <path
+ d="m 31.872581,40.372618 9.169615,0"
+ id="path2995-0-8"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="93.353088"
+ y="43.811108"
+ transform="scale(0.97877325,1.0216871)"
+ id="text4233-4-0"
+ xml:space="preserve"
+ style="font-size:47.07693863px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="93.353088"
+ y="43.811108"
+ id="tspan4235-6-6"
+ style="font-size:11.76923466px">150</tspan></text>
+ <path
+ d="m 172.96016,40.395724 -9.16961,0"
+ id="path2995-9-5-1"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 7.65881,14.465983 9.16961,0"
+ id="path2995-0-8-0"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="93.445038"
+ y="18.454386"
+ transform="scale(0.97877325,1.0216871)"
+ id="text4233-4-0-3"
+ xml:space="preserve"
+ style="font-size:47.07693863px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="93.445038"
+ y="18.454386"
+ id="tspan4235-6-6-0"
+ style="font-size:11.76923466px">200</tspan></text>
+ <path
+ d="m 196.74638,14.489089 -9.16961,0"
+ id="path2995-9-5-1-4"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <g
+ transform="matrix(1.1846986,0,0,1.2024474,-227.84119,-18.872772)"
+ id="g4049">
+ <path
+ d="m 261.49888,89.573331 7.74004,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="276.58725"
+ y="91.925209"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="276.58725"
+ y="91.925209"
+ id="tspan4235"
+ style="font-size:9.86077881px">50</tspan></text>
+ <path
+ d="m 296.18084,89.592547 -7.74004,0"
+ id="path2995-9"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-24.svg b/challenges/amazonas-24.svg
new file mode 100644
index 0000000..8cc533b
--- /dev/null
+++ b/challenges/amazonas-24.svg
@@ -0,0 +1,453 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-54"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4046"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4048"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-48"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4090"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4092"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-31"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4138"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4140"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 3.1060653,201.89394 0,-198.7878769 198.7878647,0 0,198.7878769 z"
+ id="path4000"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.21212149;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 29.42,175.58 0,-146.16 146.16,0 0,146.16 z"
+ id="path4000-3"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:#7f7f7f;stroke-width:6.09000015;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 54.382351,150.61765 0,-96.235299 96.235299,0 0,96.235299 z"
+ id="path4000-2"
+ style="fill:#ffffff;fill-opacity:1;stroke:#7f7f7f;stroke-width:6.01470566;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 79.277779,125.72222 0,-46.444441 46.444441,0 0,46.444441 z"
+ id="path4000-4"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:#7f7f7f;stroke-width:5.80555534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(1.1846986,0,0,1.2024474,-227.84119,-42.479606)"
+ id="g4049-9">
+ <path
+ d="m 241.2406,89.573331 7.74004,0"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="273.16318"
+ y="91.925209"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233-4"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="273.16318"
+ y="91.925209"
+ id="tspan4235-6"
+ style="font-size:9.86077881px">100</tspan></text>
+ <path
+ d="m 316.43912,89.592547 -7.74004,0"
+ id="path2995-9-5"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <path
+ d="m 31.872581,40.372618 9.169615,0"
+ id="path2995-0-8"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="93.353088"
+ y="43.811108"
+ transform="scale(0.97877325,1.0216871)"
+ id="text4233-4-0"
+ xml:space="preserve"
+ style="font-size:47.07693863px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="93.353088"
+ y="43.811108"
+ id="tspan4235-6-6"
+ style="font-size:11.76923466px">150</tspan></text>
+ <path
+ d="m 172.96016,40.395724 -9.16961,0"
+ id="path2995-9-5-1"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 7.65881,14.465983 9.16961,0"
+ id="path2995-0-8-0"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="93.445038"
+ y="18.454386"
+ transform="scale(0.97877325,1.0216871)"
+ id="text4233-4-0-3"
+ xml:space="preserve"
+ style="font-size:47.07693863px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="93.445038"
+ y="18.454386"
+ id="tspan4235-6-6-0"
+ style="font-size:11.76923466px">200</tspan></text>
+ <path
+ d="m 196.74638,14.489089 -9.16961,0"
+ id="path2995-9-5-1-4"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <g
+ transform="matrix(1.1846986,0,0,1.2024474,-227.84119,-18.872772)"
+ id="g4049">
+ <path
+ d="m 261.49888,89.573331 7.74004,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="276.58725"
+ y="91.925209"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="276.58725"
+ y="91.925209"
+ id="tspan4235"
+ style="font-size:9.86077881px">50</tspan></text>
+ <path
+ d="m 296.18084,89.592547 -7.74004,0"
+ id="path2995-9"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-25.svg b/challenges/amazonas-25.svg
new file mode 100644
index 0000000..1b0ce8b
--- /dev/null
+++ b/challenges/amazonas-25.svg
@@ -0,0 +1,453 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-54"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4046"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4048"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-48"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4090"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4092"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-31"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4138"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4140"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 3.1060653,201.89394 0,-198.7878769 198.7878647,0 0,198.7878769 z"
+ id="path4000"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:#7f7f7f;stroke-width:6.21212149;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 29.42,175.58 0,-146.16 146.16,0 0,146.16 z"
+ id="path4000-3"
+ style="fill:#ffffff;fill-opacity:1;stroke:#7f7f7f;stroke-width:6.09000015;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 54.382351,150.61765 0,-96.235299 96.235299,0 0,96.235299 z"
+ id="path4000-2"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:#7f7f7f;stroke-width:6.01470566;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 79.277779,125.72222 0,-46.444441 46.444441,0 0,46.444441 z"
+ id="path4000-4"
+ style="fill:#ffffff;fill-opacity:1;stroke:#7f7f7f;stroke-width:5.80555534;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(1.1846986,0,0,1.2024474,-227.84119,-42.479606)"
+ id="g4049-9">
+ <path
+ d="m 241.2406,89.573331 7.74004,0"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="273.16318"
+ y="91.925209"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233-4"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="273.16318"
+ y="91.925209"
+ id="tspan4235-6"
+ style="font-size:9.86077881px">100</tspan></text>
+ <path
+ d="m 316.43912,89.592547 -7.74004,0"
+ id="path2995-9-5"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <path
+ d="m 31.872581,40.372618 9.169615,0"
+ id="path2995-0-8"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="93.353088"
+ y="43.811108"
+ transform="scale(0.97877325,1.0216871)"
+ id="text4233-4-0"
+ xml:space="preserve"
+ style="font-size:47.07693863px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="93.353088"
+ y="43.811108"
+ id="tspan4235-6-6"
+ style="font-size:11.76923466px">150</tspan></text>
+ <path
+ d="m 172.96016,40.395724 -9.16961,0"
+ id="path2995-9-5-1"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 7.65881,14.465983 9.16961,0"
+ id="path2995-0-8-0"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="93.445038"
+ y="18.454386"
+ transform="scale(0.97877325,1.0216871)"
+ id="text4233-4-0-3"
+ xml:space="preserve"
+ style="font-size:47.07693863px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="93.445038"
+ y="18.454386"
+ id="tspan4235-6-6-0"
+ style="font-size:11.76923466px">200</tspan></text>
+ <path
+ d="m 196.74638,14.489089 -9.16961,0"
+ id="path2995-9-5-1-4"
+ style="fill:none;stroke:#000000;stroke-width:1.19353998;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <g
+ transform="matrix(1.1846986,0,0,1.2024474,-227.84119,-18.872772)"
+ id="g4049">
+ <path
+ d="m 261.49888,89.573331 7.74004,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="276.58725"
+ y="91.925209"
+ transform="scale(0.98607787,1.0141187)"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:39.44311523px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="276.58725"
+ y="91.925209"
+ id="tspan4235"
+ style="font-size:9.86077881px">50</tspan></text>
+ <path
+ d="m 296.18084,89.592547 -7.74004,0"
+ id="path2995-9"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-26.svg b/challenges/amazonas-26.svg
new file mode 100644
index 0000000..cb6dc24
--- /dev/null
+++ b/challenges/amazonas-26.svg
@@ -0,0 +1,361 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-54"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4046"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4048"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-48"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4090"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4092"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-31"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4138"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4140"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 2.5,202.5 0,-200 173,0 0,173 0,0 -120.5,0 0,-123 70.5,0 0,73 -23,0 0,-43 -23,0 0,67 73,0 0,-120 -123,0 0,173 -27,0"
+ id="path3990"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 202.5,2.5 0,200 -173,0 0,-173 0,0 123,0 0,120 -73.000002,0 0,-67.500001 23.000002,0 0,43.000001 23,0 0,-72.5 -70.5,0 0,123 120.5,0 0,-173 27,0"
+ id="path3990-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/challenges/amazonas-27.svg b/challenges/amazonas-27.svg
new file mode 100644
index 0000000..6b13c09
--- /dev/null
+++ b/challenges/amazonas-27.svg
@@ -0,0 +1,361 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-54"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4046"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4048"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-48"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4090"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4092"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-31"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4138"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4140"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 2.5,202.5 0,-200 173,0 0,173 0,0 -120.5,0 0,-123 70.5,0 0,73 -23,0 0,-43 -23,0 0,67 73,0 0,-120 -123,0 0,173 -27,0"
+ id="path3990"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 202.5,2.5 0,200 -173,0 0,-173 0,0 123,0 0,120 -73.000002,0 0,-67.500001 23.000002,0 0,43.000001 23,0 0,-72.5 -70.5,0 0,123 120.5,0 0,-173 27,0"
+ id="path3990-7"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:#7f7f7f;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/challenges/amazonas-28.svg b/challenges/amazonas-28.svg
new file mode 100644
index 0000000..15916ab
--- /dev/null
+++ b/challenges/amazonas-28.svg
@@ -0,0 +1,369 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="405"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7-2"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-55"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-5"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-84"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-54"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4046"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4048"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-48"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-3"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4090"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4092"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-31"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4138"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4140"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="m 2.5,202.5 0,-200 173,0 0,173 0,0 -120.5,0 0,-123 70.5,0 0,73 -23,0 0,-43 -23,0 0,67 73,0 0,-120 -123,0 0,173 -27,0"
+ id="path3990"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 202.5,102.5 0,100 -173,0 0,-173 0,0 123,0 0,120 -73.000002,0 0,-67.500001 23.000002,0 0,43.000001 23,0 0,-72.5 -70.5,0 0,123 120.5,0 0,-173 27,0"
+ id="path3990-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 402.49999,202.5 -199.99999,0 0,-173 173,0 0,0 0,120.5 -123,0 0,-70.500001 73,0 0,23.000001 -43,0 0,23 67,0 0,-73.000001 -120,0 0,123.000001 172.99999,0 0,27"
+ id="path3990-1"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 202.5,2.5404056 200,0 0,173.0000044 -173,0 0,0 0,-123.000005 120,0 0,73.000015 -67.5,0 0,-23.00001 43,0 0,-23.000001 -72.5,0 0,70.500001 123,0 0,-120.500004 -173,0"
+ id="path3990-7-8"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/challenges/amazonas-3.svg b/challenges/amazonas-3.svg
new file mode 100644
index 0000000..1559e1b
--- /dev/null
+++ b/challenges/amazonas-3.svg
@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 2.5,102.5 c 135.97643,-0.69024 191.5,0 191.5,0"
+ id="path3003"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,226.35918,76.594049)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <g
+ transform="translate(-8.3170911,74.685141)"
+ id="g5715">
+ <g
+ transform="matrix(0.97234963,0,0,1,0.27715027,0)"
+ id="g4037">
+ <path
+ d="m 11.522128,100.84054 72.69378,0"
+ id="path2995"
+ style="fill:none;stroke:#000000;stroke-width:1.13166273px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 208.04135,100.85782 -72.69379,0"
+ id="path2995-3"
+ style="fill:none;stroke:#000000;stroke-width:1.13166285px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="100.58539"
+ y="104.48931"
+ id="text4233"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="100.58539"
+ y="104.48931"
+ id="tspan4235"
+ style="font-size:10px">194</tspan></text>
+ </g>
+ </g>
+ <path
+ d="m 98.5,49.69697 c 0,93.18182 0,109.05303 0,109.05303"
+ id="path4062"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/challenges/amazonas-4.svg b/challenges/amazonas-4.svg
new file mode 100644
index 0000000..ac65335
--- /dev/null
+++ b/challenges/amazonas-4.svg
@@ -0,0 +1,331 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4131"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4133"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-69"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-96"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-87"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <g
+ id="g4083">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,226.35918,76.594049)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <g
+ transform="matrix(0.5,0,0,0.5,48.975654,51.68773)"
+ id="g4083-1"
+ style="stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,-14.156977,35.973477)"
+ id="g3994-8">
+ <path
+ d="m 146.16597,-108.12177 0,14.585629"
+ id="path2995-3-1-8"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="74.397118"
+ y="150.22018"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3-5"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="74.397118"
+ y="150.22018"
+ id="tspan4235-7-8"
+ style="font-size:10px">55</tspan></text>
+ <path
+ d="m 146.32105,-52.352642 0,-14.585631"
+ id="path2995-3-1-8-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-5.svg b/challenges/amazonas-5.svg
new file mode 100644
index 0000000..68c9d86
--- /dev/null
+++ b/challenges/amazonas-5.svg
@@ -0,0 +1,342 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4131"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4133"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-69"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-96"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-87"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-40"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4285"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4287"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <g
+ id="g4083">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,226.35918,76.594049)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <path
+ d="M 145.9293,103.71638 98.201307,131.04906 M 49.973312,103.71638 97.701307,131.04906 M 145.9293,103.03454 98.201307,75.701861 M 49.973312,103.03454 97.701307,75.701861"
+ id="path2993-9-4-4"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 51.842818,103.50491 97.357404,76.73162 145.54932,103.99169 98.817765,131.00837 53.059785,104.23509"
+ id="path3974"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:none" />
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,-13.462253,37.164002)"
+ id="g3994-8">
+ <path
+ d="m 146.16597,-108.12177 0,14.585629"
+ id="path2995-3-1-8"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="74.397118"
+ y="150.22018"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3-5"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="74.397118"
+ y="150.22018"
+ id="tspan4235-7-8"
+ style="font-size:10px">55</tspan></text>
+ <path
+ d="m 146.32105,-52.352642 0,-14.585631"
+ id="path2995-3-1-8-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-6.svg b/challenges/amazonas-6.svg
new file mode 100644
index 0000000..ffb2437
--- /dev/null
+++ b/challenges/amazonas-6.svg
@@ -0,0 +1,346 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4131"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4133"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-69"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-96"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-87"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-40"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4285"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4287"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <g
+ id="g4083">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ d="M 2.9720008,103.31512 98.076026,48.969958 193.18005,103.73969 97.651455,158.08484 z"
+ id="path3264"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:none" />
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,226.35918,76.594049)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <path
+ d="M 145.9293,103.71638 98.201307,131.04906 M 49.973312,103.71638 97.701307,131.04906 M 145.9293,103.03454 98.201307,75.701861 M 49.973312,103.03454 97.701307,75.701861"
+ id="path2993-9-4-4"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 51.842818,103.50491 97.357404,76.73162 145.54932,103.99169 98.817765,131.00837 53.059785,104.23509"
+ id="path3974"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,-13.462253,37.164002)"
+ id="g3994-8">
+ <path
+ d="m 146.16597,-108.12177 0,14.585629"
+ id="path2995-3-1-8"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="74.397118"
+ y="150.22018"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3-5"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="74.397118"
+ y="150.22018"
+ id="tspan4235-7-8"
+ style="font-size:10px">55</tspan></text>
+ <path
+ d="m 146.32105,-52.352642 0,-14.585631"
+ id="path2995-3-1-8-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-7.svg b/challenges/amazonas-7.svg
new file mode 100644
index 0000000..74dda19
--- /dev/null
+++ b/challenges/amazonas-7.svg
@@ -0,0 +1,399 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4131"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4133"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-69"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-96"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-87"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-40"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4285"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4287"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <g
+ id="g4083">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,226.35918,76.594049)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <g
+ transform="matrix(0.5,0,0,0.5,48.975654,51.68773)"
+ id="g4083-1"
+ style="stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,-28.552651,25.23772)"
+ id="g3994-8">
+ <path
+ d="m 146.16597,-108.12177 0,14.585629"
+ id="path2995-3-1-8"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="74.397118"
+ y="150.22018"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3-5"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="74.397118"
+ y="150.22018"
+ id="tspan4235-7-8"
+ style="font-size:10px">55</tspan></text>
+ <path
+ d="m 146.32105,-52.352642 0,-14.585631"
+ id="path2995-3-1-8-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <g
+ transform="matrix(0.74289795,0,0,0.74289795,25.183482,26.578043)"
+ id="g4083-1-1"
+ style="stroke-width:6.73039961;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(30.255316,19.03157)"
+ id="g4321">
+ <path
+ d="M 92.428426,57.769579 79.796901,65.062393"
+ id="path2995-3-1-8-3"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="4.121335"
+ y="99.936424"
+ transform="matrix(0.8660254,-0.5,0.5,0.8660254,0,0)"
+ id="text4233-3-5-7"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="4.121335"
+ y="99.936424"
+ id="tspan4235-7-8-7"
+ style="font-size:10px">77.5</tspan></text>
+ <path
+ d="M 22.004987,98.720153 34.636514,91.427337"
+ id="path2995-3-1-8-1-7"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-8.svg b/challenges/amazonas-8.svg
new file mode 100644
index 0000000..e484d1b
--- /dev/null
+++ b/challenges/amazonas-8.svg
@@ -0,0 +1,403 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4131"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4133"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-69"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-96"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-87"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-40"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4285"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4287"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="M 52.010014,103.31512 98.712884,75.717965 145.41575,103.5274 98.076026,131.12455 z"
+ id="path4173"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:none" />
+ <g
+ id="g4083">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,226.35918,76.594049)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <g
+ transform="matrix(0.5,0,0,0.5,48.975654,51.68773)"
+ id="g4083-1"
+ style="stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,-28.552651,25.23772)"
+ id="g3994-8">
+ <path
+ d="m 146.16597,-108.12177 0,14.585629"
+ id="path2995-3-1-8"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="74.397118"
+ y="150.22018"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3-5"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="74.397118"
+ y="150.22018"
+ id="tspan4235-7-8"
+ style="font-size:10px">55</tspan></text>
+ <path
+ d="m 146.32105,-52.352642 0,-14.585631"
+ id="path2995-3-1-8-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <g
+ transform="matrix(0.74289795,0,0,0.74289795,25.183482,26.578043)"
+ id="g4083-1-1"
+ style="stroke-width:6.73039961;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(30.255316,19.03157)"
+ id="g4321">
+ <path
+ d="M 92.428426,57.769579 79.796901,65.062393"
+ id="path2995-3-1-8-3"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="4.121335"
+ y="99.936424"
+ transform="matrix(0.8660254,-0.5,0.5,0.8660254,0,0)"
+ id="text4233-3-5-7"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="4.121335"
+ y="99.936424"
+ id="tspan4235-7-8-7"
+ style="font-size:10px">77.5</tspan></text>
+ <path
+ d="M 22.004987,98.720153 34.636514,91.427337"
+ id="path2995-3-1-8-1-7"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/amazonas-9.svg b/challenges/amazonas-9.svg
new file mode 100644
index 0000000..d3f90dc
--- /dev/null
+++ b/challenges/amazonas-9.svg
@@ -0,0 +1,407 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="205"
+ height="205"
+ id="svg2">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16">
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-9"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-9"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-6"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-7"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4268"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4270"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-2"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735-4"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-4"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-8"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5733"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5735"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-5"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-1"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5099"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5101"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5095"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5097"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker5091"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path5093"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-3"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-97"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-7"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-0"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4131"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4133"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-69"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-96"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-1"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-87"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="Arrow2Mstart-8"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path3792-40"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ <marker
+ refX="0"
+ refY="0"
+ orient="auto"
+ id="marker4285"
+ style="overflow:visible">
+ <path
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6,0.6)"
+ id="path4287"
+ style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
+ </marker>
+ </defs>
+ <path
+ d="M 28.048086,102.98439 97.795981,62.525651 169.7778,103.72903 97.795981,144.43599 z"
+ id="path4840"
+ style="fill:#7f7f7f;fill-opacity:1;stroke:none" />
+ <g
+ id="g4083">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,226.35918,76.594049)"
+ id="g3994">
+ <path
+ d="m 16.965852,144.30017 0,-43.75725"
+ id="path2995-0"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <path
+ d="m 16.987605,45.050682 0,14.58563"
+ id="path2995-3-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="-85.85524"
+ y="20.616865"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="-85.85524"
+ y="20.616865"
+ id="tspan4235-7"
+ style="font-size:10px">110</tspan></text>
+ </g>
+ <path
+ d="M 51.248436,103.26932 97.95131,75.672168 144.65418,103.4816 97.31445,131.07875 z"
+ id="path4173"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <g
+ transform="matrix(0.5,0,0,0.5,48.975654,51.68773)"
+ id="g4083-1"
+ style="stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4"
+ style="fill:none;stroke:#7f7f7f;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.5,0.8660254,-0.8660254,0.5,-28.552651,25.23772)"
+ id="g3994-8">
+ <path
+ d="m 146.16597,-108.12177 0,14.585629"
+ id="path2995-3-1-8"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="74.397118"
+ y="150.22018"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="text4233-3-5"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="74.397118"
+ y="150.22018"
+ id="tspan4235-7-8"
+ style="font-size:10px">55</tspan></text>
+ <path
+ d="m 146.32105,-52.352642 0,-14.585631"
+ id="path2995-3-1-8-1"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+ <g
+ transform="matrix(0.74289795,0,0,0.74289795,25.183482,26.578043)"
+ id="g4083-1-1"
+ style="stroke-width:6.73039961;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ d="M 1.995316,102.69362 97.451307,48.028261"
+ id="path2993-0-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,102.69362 98.451307,48.028261"
+ id="path2993-9-2-3"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 1.995316,104.0573 95.455991,54.66536"
+ id="path2993-5-7-0"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 193.9073,104.0573 98.451307,158.72266"
+ id="path2993-9-4-4-9"
+ style="fill:none;stroke:#7f7f7f;stroke-width:6.73039961;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="translate(30.255316,19.03157)"
+ id="g4321">
+ <path
+ d="M 92.428426,57.769579 79.796901,65.062393"
+ id="path2995-3-1-8-3"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ <text
+ x="4.121335"
+ y="99.936424"
+ transform="matrix(0.8660254,-0.5,0.5,0.8660254,0,0)"
+ id="text4233-3-5-7"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="4.121335"
+ y="99.936424"
+ id="tspan4235-7-8-7"
+ style="font-size:10px">77.5</tspan></text>
+ <path
+ d="M 22.004987,98.720153 34.636514,91.427337"
+ id="path2995-3-1-8-1-7"
+ style="fill:none;stroke:#000000;stroke-width:1.13199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:url(#Arrow2Mstart)" />
+ </g>
+</svg>
diff --git a/challenges/offsets b/challenges/offsets
new file mode 100644
index 0000000..b991582
--- /dev/null
+++ b/challenges/offsets
@@ -0,0 +1,23 @@
+1:0, -100
+2:0, -100
+3:0, -100
+4:0, -100
+5:0, -100
+6:0, -100
+7:0, -100
+8:0, -100
+9:0, -100
+10:0, -100
+11:0, -100
+12:0, -100
+13:0, -100
+14:0, -100
+15:0, -100
+16:0, -80
+17:0, -80
+18:0, -80
+19:0, -80
+20:0, -80
+21:0, -80
+
+
diff --git a/collaboration/__init__.py b/collaboration/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/collaboration/__init__.py
diff --git a/collaboration/activity.py b/collaboration/activity.py
new file mode 100644
index 0000000..bd58e92
--- /dev/null
+++ b/collaboration/activity.py
@@ -0,0 +1,720 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# 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.
+
+"""UI interface to an activity in the presence service
+
+STABLE.
+"""
+
+import logging
+from functools import partial
+
+import dbus
+from dbus import PROPERTIES_IFACE
+import gobject
+from telepathy.client import Channel
+from telepathy.interfaces import CHANNEL, \
+ CHANNEL_INTERFACE_GROUP, \
+ CHANNEL_TYPE_TUBES, \
+ CHANNEL_TYPE_TEXT, \
+ CONNECTION, \
+ PROPERTIES_INTERFACE
+from telepathy.constants import CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES, \
+ HANDLE_TYPE_ROOM, \
+ HANDLE_TYPE_CONTACT, \
+ PROPERTY_FLAG_WRITE
+
+
+CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
+CONN_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
+
+_logger = logging.getLogger('sugar.presence.activity')
+
+
+class Activity(gobject.GObject):
+ """UI interface for an Activity in the presence service
+
+ Activities in the presence service represent your and other user's
+ shared activities.
+
+ Properties:
+ id
+ color
+ name
+ type
+ joined
+ """
+ __gsignals__ = {
+ 'buddy-joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'buddy-left': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'new-channel': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
+ }
+
+ __gproperties__ = {
+ 'id': (str, None, None, None, gobject.PARAM_READABLE),
+ 'name': (str, None, None, None, gobject.PARAM_READWRITE),
+ 'tags': (str, None, None, None, gobject.PARAM_READWRITE),
+ 'color': (str, None, None, None, gobject.PARAM_READWRITE),
+ 'type': (str, None, None, None, gobject.PARAM_READABLE),
+ 'private': (bool, None, None, True, gobject.PARAM_READWRITE),
+ 'joined': (bool, None, None, False, gobject.PARAM_READABLE),
+ }
+
+ def __init__(self, account_path, connection, room_handle=None,
+ properties=None):
+ if room_handle is None and properties is None:
+ raise ValueError('Need to pass one of room_handle or properties')
+
+ if properties is None:
+ properties = {}
+
+ gobject.GObject.__init__(self)
+
+ self._account_path = account_path
+ self.telepathy_conn = connection
+ self.telepathy_text_chan = None
+ self.telepathy_tubes_chan = None
+
+ self.room_handle = room_handle
+ self._join_command = None
+ self._share_command = None
+ self._id = properties.get('id', None)
+ self._color = properties.get('color', None)
+ self._name = properties.get('name', None)
+ self._type = properties.get('type', None)
+ self._tags = properties.get('tags', None)
+ self._private = properties.get('private', True)
+ self._joined = properties.get('joined', False)
+ self._channel_self_handle = None
+ self._text_channel_group_flags = 0
+ self._buddies = {}
+
+ self._get_properties_call = None
+ if not self.room_handle is None:
+ self._start_tracking_properties()
+
+ def _start_tracking_properties(self):
+ bus = dbus.SessionBus()
+ self._get_properties_call = bus.call_async(
+ self.telepathy_conn.requested_bus_name,
+ self.telepathy_conn.object_path,
+ CONN_INTERFACE_ACTIVITY_PROPERTIES,
+ 'GetProperties',
+ 'u',
+ (self.room_handle,),
+ reply_handler=self.__got_properties_cb,
+ error_handler=self.__error_handler_cb,
+ utf8_strings=True)
+
+ # As only one Activity instance is needed per activity process,
+ # we can afford listening to ActivityPropertiesChanged like this.
+ self.telepathy_conn.connect_to_signal(
+ 'ActivityPropertiesChanged',
+ self.__activity_properties_changed_cb,
+ dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES)
+
+ def __activity_properties_changed_cb(self, room_handle, properties):
+ print('%r: Activity properties changed to %r', self,
+ properties)
+ self._update_properties(properties)
+
+ def __got_properties_cb(self, properties):
+ print('__got_properties_cb %r', properties)
+ self._get_properties_call = None
+ self._update_properties(properties)
+
+ def __error_handler_cb(self, error):
+ print('__error_handler_cb %r', error)
+
+ def _update_properties(self, new_props):
+ val = new_props.get('name', self._name)
+ if isinstance(val, str) and val != self._name:
+ self._name = val
+ self.notify('name')
+ val = new_props.get('tags', self._tags)
+ if isinstance(val, str) and val != self._tags:
+ self._tags = val
+ self.notify('tags')
+ val = new_props.get('color', self._color)
+ if isinstance(val, str) and val != self._color:
+ self._color = val
+ self.notify('color')
+ val = bool(new_props.get('private', self._private))
+ if val != self._private:
+ self._private = val
+ self.notify('private')
+ val = new_props.get('id', self._id)
+ if isinstance(val, str) and self._id is None:
+ self._id = val
+ self.notify('id')
+ val = new_props.get('type', self._type)
+ if isinstance(val, str) and self._type is None:
+ self._type = val
+ self.notify('type')
+
+ def object_path(self):
+ """Get our dbus object path"""
+ return self._object_path
+
+ def do_get_property(self, pspec):
+ """Retrieve a particular property from our property dictionary"""
+
+ if pspec.name == 'joined':
+ return self._joined
+
+ if self._get_properties_call is not None:
+ print('%r: Blocking on GetProperties() because someone '
+ 'wants property %s', self, pspec.name)
+ self._get_properties_call.block()
+
+ if pspec.name == 'id':
+ return self._id
+ elif pspec.name == 'name':
+ return self._name
+ elif pspec.name == 'color':
+ return self._color
+ elif pspec.name == 'type':
+ return self._type
+ elif pspec.name == 'tags':
+ return self._tags
+ elif pspec.name == 'private':
+ return self._private
+
+ def do_set_property(self, pspec, val):
+ """Set a particular property in our property dictionary"""
+ # FIXME: need an asynchronous API to set these properties,
+ # particularly 'private'
+
+ if pspec.name == 'name':
+ self._name = val
+ elif pspec.name == 'color':
+ self._color = val
+ elif pspec.name == 'tags':
+ self._tags = val
+ elif pspec.name == 'private':
+ self._private = val
+ else:
+ raise ValueError('Unknown property %r', pspec.name)
+
+ self._publish_properties()
+
+ def set_private(self, val, reply_handler, error_handler):
+ print('set_private %r', val)
+ self._activity.SetProperties({'private': bool(val)},
+ reply_handler=reply_handler,
+ error_handler=error_handler)
+
+ def get_joined_buddies(self):
+ """Retrieve the set of Buddy objects attached to this activity
+
+ returns list of presence Buddy objects that we can successfully
+ create from the buddy object paths that PS has for this activity.
+ """
+ return self._buddies.values()
+
+ def get_buddy_by_handle(self, handle):
+ """Retrieve the Buddy object given a telepathy handle.
+
+ buddy object paths are cached in self._handle_to_buddy_path,
+ so we can get the buddy without calling PS.
+ """
+ object_path = self._handle_to_buddy_path.get(handle, None)
+ if object_path:
+ buddy = self._ps_new_object(object_path)
+ return buddy
+ return None
+
+ def invite(self, buddy, message, response_cb):
+ """Invite the given buddy to join this activity.
+
+ The callback will be called with one parameter: None on success,
+ or an exception on failure.
+ """
+ if not self._joined:
+ raise RuntimeError('Cannot invite a buddy to an activity that is'
+ 'not shared.')
+ self.telepathy_text_chan.AddMembers([buddy.contact_handle], message,
+ dbus_interface=CHANNEL_INTERFACE_GROUP,
+ reply_handler=partial(self.__invite_cb, response_cb),
+ error_handler=partial(self.__invite_cb, response_cb))
+
+ def __invite_cb(self, response_cb, error=None):
+ response_cb(error)
+
+ def set_up_tubes(self, reply_handler, error_handler):
+ raise NotImplementedError()
+
+ def __joined_cb(self, join_command, error):
+ print('%r: Join finished %r', self, error)
+ if error is not None:
+ self.emit('joined', error is None, str(error))
+ self.telepathy_text_chan = join_command.text_channel
+ self.telepathy_tubes_chan = join_command.tubes_channel
+ self._channel_self_handle = join_command.channel_self_handle
+ self._text_channel_group_flags = join_command.text_channel_group_flags
+ self._start_tracking_buddies()
+ self._start_tracking_channel()
+
+ def _start_tracking_buddies(self):
+ group = self.telepathy_text_chan[CHANNEL_INTERFACE_GROUP]
+
+ group.GetAllMembers(reply_handler=self.__get_all_members_cb,
+ error_handler=self.__error_handler_cb)
+
+ group.connect_to_signal('MembersChanged',
+ self.__text_channel_members_changed_cb)
+
+ def _start_tracking_channel(self):
+ channel = self.telepathy_text_chan[CHANNEL]
+ channel.connect_to_signal('Closed', self.__text_channel_closed_cb)
+
+ def __get_all_members_cb(self, members, local_pending, remote_pending):
+ print('__get_all_members_cb %r %r', members,
+ self._text_channel_group_flags)
+ if self._channel_self_handle in members:
+ members.remove(self._channel_self_handle)
+
+ if not members:
+ return
+
+ self._resolve_handles(members, reply_cb=self._add_initial_buddies)
+
+ def _resolve_handles(self, input_handles, reply_cb):
+ def get_handle_owners_cb(handles):
+ self.telepathy_conn.InspectHandles(HANDLE_TYPE_CONTACT, handles,
+ reply_handler=reply_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=CONNECTION)
+
+ if self._text_channel_group_flags & \
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+
+ group = self.telepathy_text_chan[CHANNEL_INTERFACE_GROUP]
+ group.GetHandleOwners(input_handles,
+ reply_handler=get_handle_owners_cb,
+ error_handler=self.__error_handler_cb)
+ else:
+ get_handle_owners_cb(input_handles)
+
+ def _add_initial_buddies(self, contact_ids):
+ print('__add_initial_buddies %r', contact_ids)
+ #for contact_id in contact_ids:
+ # self._buddies[contact_id] = self._get_buddy(contact_id)
+ # Once we have the initial members, we can finish the join process
+ self._joined = True
+ self.emit('joined', True, None)
+
+ def __text_channel_members_changed_cb(self, message, added, removed,
+ local_pending, remote_pending,
+ actor, reason):
+ print('__text_channel_members_changed_cb %r',
+ [added, message, added, removed, local_pending,
+ remote_pending, actor, reason])
+ if self._channel_self_handle in added:
+ added.remove(self._channel_self_handle)
+ if added:
+ self._resolve_handles(added, reply_cb=self._add_buddies)
+
+ if self._channel_self_handle in removed:
+ removed.remove(self._channel_self_handle)
+ if removed:
+ self._resolve_handles(added, reply_cb=self._remove_buddies)
+
+ def _add_buddies(self, contact_ids):
+ for contact_id in contact_ids:
+ if contact_id not in self._buddies:
+ buddy = self._get_buddy(contact_id)
+ self.emit('buddy-joined', buddy)
+ self._buddies[contact_id] = buddy
+
+ def _remove_buddies(self, contact_ids):
+ for contact_id in contact_ids:
+ if contact_id in self._buddies:
+ buddy = self._get_buddy(contact_id)
+ self.emit('buddy-left', buddy)
+ del self._buddies[contact_id]
+
+ def _get_buddy(self, contact_id):
+ return None
+ #if contact_id in self._buddies:
+ # return self._buddies[contact_id]
+ #else:
+ # return Buddy(self._account_path, contact_id)
+
+ def join(self):
+ """Join this activity.
+
+ Emits 'joined' and otherwise does nothing if we're already joined.
+ """
+ if self._join_command is not None:
+ return
+
+ if self._joined:
+ self.emit('joined', True, None)
+ return
+
+ print('%r: joining', self)
+
+ self._join_command = _JoinCommand(self.telepathy_conn,
+ self.room_handle)
+ self._join_command.connect('finished', self.__joined_cb)
+ self._join_command.run()
+
+ def share(self, share_activity_cb, share_activity_error_cb):
+ if not self.room_handle is None:
+ raise ValueError('Already have a room handle')
+
+ self._share_command = _ShareCommand(self.telepathy_conn, self._id)
+ self._share_command.connect('finished',
+ partial(self.__shared_cb,
+ share_activity_cb,
+ share_activity_error_cb))
+ self._share_command.run()
+
+ def __shared_cb(self, share_activity_cb, share_activity_error_cb,
+ share_command, error):
+ print('%r: Share finished %r', self, error)
+ if error is None:
+ print "There was no error!"
+ self._joined = True
+ self.room_handle = share_command.room_handle
+ self.telepathy_text_chan = share_command.text_channel
+ self.telepathy_tubes_chan = share_command.tubes_channel
+ self._channel_self_handle = share_command.channel_self_handle
+ self._text_channel_group_flags = \
+ share_command.text_channel_group_flags
+ self._publish_properties()
+ self._start_tracking_properties()
+ self._start_tracking_buddies()
+ self._start_tracking_channel()
+ share_activity_cb(self)
+ else:
+ print("error = %s" % error)
+ share_activity_error_cb(self, error)
+
+ def _publish_properties(self):
+ properties = {}
+
+ if self._color is not None:
+ properties['color'] = str(self._color)
+ if self._name is not None:
+ properties['name'] = str(self._name)
+ if self._type is not None:
+ properties['type'] = self._type
+ if self._tags is not None:
+ properties['tags'] = self._tags
+ properties['private'] = self._private
+
+ self.telepathy_conn.SetProperties(
+ self.room_handle,
+ properties,
+ dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES)
+
+ def __share_error_cb(self, share_activity_error_cb, error):
+ logging.debug('%r: Share failed because: %s', self, error)
+ share_activity_error_cb(self, error)
+
+ # GetChannels() wrapper
+
+ def get_channels(self):
+ """Retrieve communications channel descriptions for the activity
+
+ Returns a tuple containing:
+ - the D-Bus well-known service name of the connection
+ (FIXME: this is redundant; in Telepathy it can be derived
+ from that of the connection)
+ - the D-Bus object path of the connection
+ - a list of D-Bus object paths representing the channels
+ associated with this activity
+ """
+ bus_name = self.telepathy_conn.requested_bus_name
+ connection_path = self.telepathy_conn.object_path
+ channels = [self.telepathy_text_chan.object_path,
+ self.telepathy_tubes_chan.object_path]
+
+ print('%r: bus name is %s, connection is %s, channels are %r',
+ self, bus_name, connection_path, channels)
+ return bus_name, connection_path, channels
+
+ # Leaving
+ def __text_channel_closed_cb(self):
+ self._joined = False
+ self.emit('joined', False, 'left activity')
+
+ def leave(self):
+ """Leave this shared activity"""
+ print('%r: leaving', self)
+ self.telepathy_text_chan.Close()
+
+
+class _BaseCommand(gobject.GObject):
+ __gsignals__ = {
+ 'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self.text_channel = None
+ self.text_channel_group_flags = None
+ self.tubes_channel = None
+ self.room_handle = None
+ self.channel_self_handle = None
+
+ def run(self):
+ raise NotImplementedError()
+
+
+class _ShareCommand(_BaseCommand):
+ def __init__(self, connection, activity_id):
+ _BaseCommand.__init__(self)
+
+ self._connection = connection
+ self._activity_id = activity_id
+ self._finished = False
+ self._join_command = None
+
+ def run(self):
+ self._connection.RequestHandles(
+ HANDLE_TYPE_ROOM,
+ [self._activity_id],
+ reply_handler=self.__got_handles_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=CONNECTION)
+
+ def __got_handles_cb(self, handles):
+ logging.debug('__got_handles_cb %r', handles)
+ self.room_handle = handles[0]
+
+ self._join_command = _JoinCommand(self._connection, self.room_handle)
+ self._join_command.connect('finished', self.__joined_cb)
+ self._join_command.run()
+
+ def __joined_cb(self, join_command, error):
+ print('%r: Join finished %r', self, error)
+ if error is not None:
+ self._finished = True
+ self.emit('finished', error)
+ return
+
+ self.text_channel = join_command.text_channel
+ self.text_channel_group_flags = join_command.text_channel_group_flags
+ self.tubes_channel = join_command.tubes_channel
+
+ self._connection.AddActivity(
+ self._activity_id,
+ self.room_handle,
+ reply_handler=self.__added_activity_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=CONN_INTERFACE_BUDDY_INFO)
+
+ def __added_activity_cb(self):
+ self._finished = True
+ self.emit('finished', None)
+
+ def __error_handler_cb(self, error):
+ self._finished = True
+ self.emit('finished', error)
+
+
+class _JoinCommand(_BaseCommand):
+ def __init__(self, connection, room_handle):
+ _BaseCommand.__init__(self)
+
+ self._connection = connection
+ self._finished = False
+ self.room_handle = room_handle
+ self._global_self_handle = None
+
+ def run(self):
+ if self._finished:
+ raise RuntimeError('This command has already finished')
+ self._connection.Get(CONNECTION, 'SelfHandle',
+ reply_handler=self.__get_self_handle_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=PROPERTIES_IFACE)
+
+ def __get_self_handle_cb(self, handle):
+ self._global_self_handle = handle
+
+ self._connection.RequestChannel(CHANNEL_TYPE_TEXT,
+ HANDLE_TYPE_ROOM, self.room_handle, True,
+ reply_handler=self.__create_text_channel_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=CONNECTION)
+
+ self._connection.RequestChannel(CHANNEL_TYPE_TUBES,
+ HANDLE_TYPE_ROOM, self.room_handle, True,
+ reply_handler=self.__create_tubes_channel_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=CONNECTION)
+
+ def __create_text_channel_cb(self, channel_path):
+ Channel(self._connection.requested_bus_name, channel_path,
+ ready_handler=self.__text_channel_ready_cb)
+
+ def __create_tubes_channel_cb(self, channel_path):
+ print "Creating tubes channel with bus name %s" % self._connection.requested_bus_name
+ print "Creating tubes channel with channel path %s" % channel_path
+ Channel(self._connection.requested_bus_name, channel_path,
+ ready_handler=self.__tubes_channel_ready_cb)
+
+ def __error_handler_cb(self, error):
+ self._finished = True
+ self.emit('finished', error)
+
+ def __tubes_channel_ready_cb(self, channel):
+ print('%r: Tubes channel %r is ready', self, channel)
+ self.tubes_channel = channel
+ self._tubes_ready()
+
+ def __text_channel_ready_cb(self, channel):
+ print('%r: Text channel %r is ready', self, channel)
+ self.text_channel = channel
+ self._tubes_ready()
+
+ def _tubes_ready(self):
+ if self.text_channel is None or \
+ self.tubes_channel is None:
+ return
+
+ print('%r: finished setting up tubes', self)
+
+ self._add_self_to_channel()
+
+ def __text_channel_group_flags_changed_cb(self, added, removed):
+ print('__text_channel_group_flags_changed_cb %r %r', added,
+ removed)
+ self.text_channel_group_flags |= added
+ self.text_channel_group_flags &= ~removed
+
+ def _add_self_to_channel(self):
+ # FIXME: cope with non-Group channels here if we want to support
+ # non-OLPC-compatible IMs
+
+ group = self.text_channel[CHANNEL_INTERFACE_GROUP]
+
+ def got_all_members(members, local_pending, remote_pending):
+ print('got_all_members members %r local_pending %r '
+ 'remote_pending %r', members, local_pending,
+ remote_pending)
+
+ if self.text_channel_group_flags & \
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ self_handle = self.channel_self_handle
+ else:
+ self_handle = self._global_self_handle
+
+ if self_handle in local_pending:
+ print('%r: We are in local pending - entering', self)
+ group.AddMembers([self_handle], '',
+ reply_handler=lambda: None,
+ error_handler=lambda e: self._join_failed_cb(e,
+ 'got_all_members AddMembers'))
+
+ if members:
+ self.__text_channel_members_changed_cb('', members, (),
+ (), (), 0, 0)
+
+ def got_group_flags(flags):
+ self.text_channel_group_flags = flags
+ # by the time we hook this, we need to know the group flags
+ group.connect_to_signal('MembersChanged',
+ self.__text_channel_members_changed_cb)
+
+ # bootstrap by getting the current state. This is where we find
+ # out whether anyone was lying to us in their PEP info
+ group.GetAllMembers(reply_handler=got_all_members,
+ error_handler=self.__error_handler_cb)
+
+ def got_self_handle(channel_self_handle):
+ self.channel_self_handle = channel_self_handle
+ group.connect_to_signal('GroupFlagsChanged',
+ self.__text_channel_group_flags_changed_cb)
+ group.GetGroupFlags(reply_handler=got_group_flags,
+ error_handler=self.__error_handler_cb)
+
+ group.GetSelfHandle(reply_handler=got_self_handle,
+ error_handler=self.__error_handler_cb)
+
+ def __text_channel_members_changed_cb(self, message, added, removed,
+ local_pending, remote_pending,
+ actor, reason):
+ print('__text_channel_members_changed_cb added %r removed %r '
+ 'local_pending %r remote_pending %r channel_self_handle '
+ '%r', added, removed, local_pending, remote_pending,
+ self.channel_self_handle)
+
+ if self.text_channel_group_flags & \
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ self_handle = self.channel_self_handle
+ else:
+ self_handle = self._global_self_handle
+
+ if self_handle in added:
+ if PROPERTIES_INTERFACE not in self.text_channel:
+ self._finished = True
+ self.emit('finished', None)
+ else:
+ self.text_channel[PROPERTIES_INTERFACE].ListProperties(
+ reply_handler=self.__list_properties_cb,
+ error_handler=self.__error_handler_cb)
+
+ def __list_properties_cb(self, prop_specs):
+ # FIXME: invite-only ought to be set on private activities; but
+ # since only the owner can change invite-only, that would break
+ # activity scope changes.
+ props = {
+ # otherwise buddy resolution breaks
+ 'anonymous': False,
+ # anyone who knows about the channel can join
+ 'invite-only': False,
+ # so non-owners can invite others
+ 'invite-restricted': False,
+ # vanish when there are no members
+ 'persistent': False,
+ # don't appear in server room lists
+ 'private': True,
+ }
+ props_to_set = []
+ for ident, name, sig_, flags in prop_specs:
+ value = props.pop(name, None)
+ if value is not None:
+ if flags & PROPERTY_FLAG_WRITE:
+ props_to_set.append((ident, value))
+ # FIXME: else error, but only if we're creating the room?
+ # FIXME: if props is nonempty, then we want to set props that aren't
+ # supported here - raise an error?
+
+ if props_to_set:
+ self.text_channel[PROPERTIES_INTERFACE].SetProperties(
+ props_to_set, reply_handler=self.__set_properties_cb,
+ error_handler=self.__error_handler_cb)
+ else:
+ self._finished = True
+ self.emit('finished', None)
+
+ def __set_properties_cb(self):
+ self._finished = True
+ self.emit('finished', None)
diff --git a/collaboration/buddy.py b/collaboration/buddy.py
new file mode 100644
index 0000000..b117940
--- /dev/null
+++ b/collaboration/buddy.py
@@ -0,0 +1,234 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import gobject
+import gconf
+import dbus
+from telepathy.client import Connection
+from telepathy.interfaces import CONNECTION
+
+from xocolor import XoColor
+import connection_watcher
+
+
+CONNECTION_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
+
+_owner_instance = None
+
+
+class BaseBuddyModel(gobject.GObject):
+ __gtype_name__ = 'SugarBaseBuddyModel'
+
+ def __init__(self, **kwargs):
+ self._key = None
+ self._nick = None
+ self._color = None
+ self._tags = None
+ self._current_activity = None
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ def get_nick(self):
+ return self._nick
+
+ def set_nick(self, nick):
+ self._nick = nick
+
+ nick = gobject.property(type=object, getter=get_nick, setter=set_nick)
+
+ def get_key(self):
+ return self._key
+
+ def set_key(self, key):
+ self._key = key
+
+ key = gobject.property(type=object, getter=get_key, setter=set_key)
+
+ def get_color(self):
+ return self._color
+
+ def set_color(self, color):
+ self._color = color
+
+ color = gobject.property(type=object, getter=get_color, setter=set_color)
+
+ def get_tags(self):
+ return self._tags
+
+ tags = gobject.property(type=object, getter=get_tags)
+
+ def get_current_activity(self):
+ return self._current_activity
+
+ def set_current_activity(self, current_activity):
+ if self._current_activity != current_activity:
+ self._current_activity = current_activity
+ self.notify('current-activity')
+
+ current_activity = gobject.property(type=object,
+ getter=get_current_activity,
+ setter=set_current_activity)
+
+ def is_owner(self):
+ raise NotImplementedError
+
+
+class OwnerBuddyModel(BaseBuddyModel):
+ __gtype_name__ = 'SugarOwnerBuddyModel'
+
+ def __init__(self):
+ BaseBuddyModel.__init__(self)
+
+ #client = gconf.client_get_default()
+ #self.props.nick = client.get_string('/desktop/sugar/user/nick')
+ self.props.nick = "rgs"
+ #color = client.get_string('/desktop/sugar/user/color')
+ self.props.color = XoColor(None)
+
+ #self.props.key = get_profile().pubkey
+ self.props.key = "foobar"
+
+ self.connect('notify::nick', self.__property_changed_cb)
+ self.connect('notify::color', self.__property_changed_cb)
+ self.connect('notify::current-activity',
+ self.__current_activity_changed_cb)
+
+ bus = dbus.SessionBus()
+ bus.add_signal_receiver(
+ self.__name_owner_changed_cb,
+ signal_name='NameOwnerChanged',
+ dbus_interface='org.freedesktop.DBus')
+
+ bus_object = bus.get_object(dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH)
+ for service in bus_object.ListNames(
+ dbus_interface=dbus.BUS_DAEMON_IFACE):
+ if service.startswith(CONNECTION + '.'):
+ path = '/%s' % service.replace('.', '/')
+ Connection(service, path, bus,
+ ready_handler=self.__connection_ready_cb)
+
+ def __connection_ready_cb(self, connection):
+ self._sync_properties_on_connection(connection)
+
+ def __name_owner_changed_cb(self, name, old, new):
+ if name.startswith(CONNECTION + '.') and not old and new:
+ path = '/' + name.replace('.', '/')
+ Connection(name, path, ready_handler=self.__connection_ready_cb)
+
+ def __property_changed_cb(self, buddy, pspec):
+ self._sync_properties()
+
+ def __current_activity_changed_cb(self, buddy, pspec):
+ conn_watcher = connection_watcher.get_instance()
+ for connection in conn_watcher.get_connections():
+ if self.props.current_activity is not None:
+ activity_id = self.props.current_activity.activity_id
+ room_handle = self.props.current_activity.room_handle
+ else:
+ activity_id = ''
+ room_handle = 0
+
+ connection[CONNECTION_INTERFACE_BUDDY_INFO].SetCurrentActivity(
+ activity_id,
+ room_handle,
+ reply_handler=self.__set_current_activity_cb,
+ error_handler=self.__error_handler_cb)
+
+ def __set_current_activity_cb(self):
+ logging.debug('__set_current_activity_cb')
+
+ def _sync_properties(self):
+ conn_watcher = connection_watcher.get_instance()
+ for connection in conn_watcher.get_connections():
+ self._sync_properties_on_connection(connection)
+
+ def _sync_properties_on_connection(self, connection):
+ if CONNECTION_INTERFACE_BUDDY_INFO in connection:
+ properties = {}
+ if self.props.key is not None:
+ properties['key'] = dbus.ByteArray(self.props.key)
+ if self.props.color is not None:
+ properties['color'] = self.props.color.to_string()
+
+ logging.debug('calling SetProperties with %r', properties)
+ connection[CONNECTION_INTERFACE_BUDDY_INFO].SetProperties(
+ properties,
+ reply_handler=self.__set_properties_cb,
+ error_handler=self.__error_handler_cb)
+
+ def __set_properties_cb(self):
+ logging.debug('__set_properties_cb')
+
+ def __error_handler_cb(self, error):
+ raise RuntimeError(error)
+
+ def __connection_added_cb(self, conn_watcher, connection):
+ self._sync_properties_on_connection(connection)
+
+ def is_owner(self):
+ return True
+
+
+def get_owner_instance():
+ global _owner_instance
+ if _owner_instance is None:
+ _owner_instance = OwnerBuddyModel()
+ return _owner_instance
+
+
+class BuddyModel(BaseBuddyModel):
+ __gtype_name__ = 'SugarBuddyModel'
+
+ def __init__(self, **kwargs):
+
+ self._account = None
+ self._contact_id = None
+ self._handle = None
+
+ BaseBuddyModel.__init__(self, **kwargs)
+
+ def is_owner(self):
+ return False
+
+ def get_account(self):
+ return self._account
+
+ def set_account(self, account):
+ self._account = account
+
+ account = gobject.property(type=object, getter=get_account,
+ setter=set_account)
+
+ def get_contact_id(self):
+ return self._contact_id
+
+ def set_contact_id(self, contact_id):
+ self._contact_id = contact_id
+
+ contact_id = gobject.property(type=object, getter=get_contact_id,
+ setter=set_contact_id)
+
+ def get_handle(self):
+ return self._handle
+
+ def set_handle(self, handle):
+ self._handle = handle
+
+ handle = gobject.property(type=object, getter=get_handle,
+ setter=set_handle)
diff --git a/collaboration/connection_watcher.py b/collaboration/connection_watcher.py
new file mode 100644
index 0000000..96af1cf
--- /dev/null
+++ b/collaboration/connection_watcher.py
@@ -0,0 +1,122 @@
+# This should eventually land in telepathy-python, so has the same license:
+# Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# FIXME: this sould go upstream, in telepathy-python
+
+import logging
+
+import dbus
+import dbus.mainloop.glib
+import gobject
+
+from telepathy.client import Connection
+from telepathy.interfaces import CONN_INTERFACE
+from telepathy.constants import CONNECTION_STATUS_CONNECTED, \
+ CONNECTION_STATUS_DISCONNECTED
+
+
+_instance = None
+
+
+class ConnectionWatcher(gobject.GObject):
+ __gsignals__ = {
+ 'connection-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'connection-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ }
+
+ def __init__(self, bus=None):
+ gobject.GObject.__init__(self)
+
+ if bus is None:
+ self.bus = dbus.Bus()
+ else:
+ self.bus = bus
+
+ # D-Bus path -> Connection
+ self._connections = {}
+
+ self.bus.add_signal_receiver(self._status_changed_cb,
+ dbus_interface=CONN_INTERFACE, signal_name='StatusChanged',
+ path_keyword='path')
+
+ for conn in Connection.get_connections(bus):
+ conn.call_when_ready(self._conn_ready_cb)
+
+ def _status_changed_cb(self, *args, **kwargs):
+ path = kwargs['path']
+ if not path.startswith('/org/freedesktop/Telepathy/Connection/'):
+ return
+
+ status, reason_ = args
+ service_name = path.replace('/', '.')[1:]
+
+ if status == CONNECTION_STATUS_CONNECTED:
+ self._add_connection(service_name, path)
+ elif status == CONNECTION_STATUS_DISCONNECTED:
+ self._remove_connection(service_name, path)
+
+ def _conn_ready_cb(self, conn):
+ if conn.object_path in self._connections:
+ return
+
+ self._connections[conn.object_path] = conn
+ self.emit('connection-added', conn)
+
+ def _add_connection(self, service_name, path):
+ if path in self._connections:
+ return
+
+ try:
+ Connection(service_name, path, ready_handler=self._conn_ready_cb)
+ except dbus.exceptions.DBusException:
+ logging.debug('%s is propably already gone.', service_name)
+
+ def _remove_connection(self, service_name, path):
+ conn = self._connections.pop(path, None)
+ if conn is None:
+ return
+
+ self.emit('connection-removed', conn)
+
+ def get_connections(self):
+ return self._connections.values()
+
+
+def get_instance():
+ global _instance
+ if _instance is None:
+ _instance = ConnectionWatcher()
+ return _instance
+
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ def connection_added_cb(conn_watcher, conn):
+ print 'new connection', conn.service_name
+
+ def connection_removed_cb(conn_watcher, conn):
+ print 'removed connection', conn.service_name
+
+ watcher = ConnectionWatcher()
+ watcher.connect('connection-added', connection_added_cb)
+ watcher.connect('connection-removed', connection_removed_cb)
+
+ loop = gobject.MainLoop()
+ loop.run()
diff --git a/collaboration/connectionmanager.py b/collaboration/connectionmanager.py
new file mode 100644
index 0000000..5ae59dd
--- /dev/null
+++ b/collaboration/connectionmanager.py
@@ -0,0 +1,122 @@
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# 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.
+
+"""
+UNSTABLE. It should really be internal to the sugar.presence package.
+"""
+
+from functools import partial
+
+import dbus
+from dbus import PROPERTIES_IFACE
+from telepathy.interfaces import ACCOUNT, \
+ ACCOUNT_MANAGER
+from telepathy.constants import CONNECTION_STATUS_CONNECTED
+
+ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager'
+ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager'
+
+
+class Connection(object):
+ def __init__(self, account_path, connection):
+ self.account_path = account_path
+ self.connection = connection
+ self.connected = False
+
+
+class ConnectionManager(object):
+ """Track available telepathy connections"""
+
+ def __init__(self):
+ self._connections_per_account = {}
+
+ bus = dbus.SessionBus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
+ account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
+ account_paths = account_manager.Get(ACCOUNT_MANAGER, 'ValidAccounts',
+ dbus_interface=PROPERTIES_IFACE)
+ for account_path in account_paths:
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, account_path)
+ obj.connect_to_signal('AccountPropertyChanged',
+ partial(self.__account_property_changed_cb, account_path))
+ connection_path = obj.Get(ACCOUNT, 'Connection')
+ if connection_path != '/':
+ try:
+ self._track_connection(account_path, connection_path)
+ except:
+ pass
+
+ def __account_property_changed_cb(self, account_path, properties):
+ if 'Connection' not in properties:
+ return
+ if properties['Connection'] == '/':
+ if account_path in self._connections_per_account:
+ del self._connections_per_account[account_path]
+ else:
+ self._track_connection(account_path, properties['Connection'])
+
+ def _track_connection(self, account_path, connection_path):
+ connection_name = connection_path.replace('/', '.')[1:]
+ bus = dbus.SessionBus()
+ connection = bus.get_object(connection_name, connection_path)
+ connection.connect_to_signal('StatusChanged',
+ partial(self.__status_changed_cb, account_path))
+ self._connections_per_account[account_path] = \
+ Connection(account_path, connection)
+
+ account = bus.get_object(ACCOUNT_MANAGER_SERVICE, account_path)
+ status = account.Get(ACCOUNT, 'ConnectionStatus')
+ if status == CONNECTION_STATUS_CONNECTED:
+ self._connections_per_account[account_path].connected = True
+ else:
+ self._connections_per_account[account_path].connected = False
+
+ def __status_changed_cb(self, account_path, status, reason):
+ if status == CONNECTION_STATUS_CONNECTED:
+ self._connections_per_account[account_path].connected = True
+ else:
+ self._connections_per_account[account_path].connected = False
+
+ def get_preferred_connection(self):
+ best_connection = None, None
+ for account_path, connection in self._connections_per_account.items():
+ if 'salut' in account_path and connection.connected:
+ best_connection = account_path, connection.connection
+ elif 'gabble' in account_path and connection.connected:
+ best_connection = account_path, connection.connection
+ break
+ return best_connection
+
+ def get_connection(self, account_path):
+ return self._connections_per_account[account_path].connection
+
+ def get_connections_per_account(self):
+ return self._connections_per_account
+
+ def get_account_for_connection(self, connection_path):
+ for account_path, connection in self._connections_per_account.items():
+ if connection.connection.object_path == connection_path:
+ return account_path
+ return None
+
+
+_connection_manager = None
+def get_connection_manager():
+ global _connection_manager
+ if not _connection_manager:
+ _connection_manager = ConnectionManager()
+ return _connection_manager
diff --git a/collaboration/dispatch/Makefile.am b/collaboration/dispatch/Makefile.am
new file mode 100644
index 0000000..eb44a32
--- /dev/null
+++ b/collaboration/dispatch/Makefile.am
@@ -0,0 +1,9 @@
+sugardir = $(pythondir)/sugar/dispatch
+sugar_PYTHON = \
+ __init__.py \
+ dispatcher.py \
+ saferef.py
+
+EXTRA_DIST = \
+ license.txt
+
diff --git a/collaboration/dispatch/__init__.py b/collaboration/dispatch/__init__.py
new file mode 100644
index 0000000..9f0a092
--- /dev/null
+++ b/collaboration/dispatch/__init__.py
@@ -0,0 +1,10 @@
+"""Multi-consumer multi-producer dispatching mechanism
+
+Originally based on pydispatch (BSD)
+http://pypi.python.org/pypi/PyDispatcher/2.0.1
+See license.txt for original license.
+
+Heavily modified for Django's purposes.
+"""
+
+from dispatcher import Signal
diff --git a/collaboration/dispatch/dispatcher.py b/collaboration/dispatch/dispatcher.py
new file mode 100644
index 0000000..45c32fe
--- /dev/null
+++ b/collaboration/dispatch/dispatcher.py
@@ -0,0 +1,191 @@
+import weakref
+import saferef
+
+WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
+
+
+def _make_id(target):
+ if hasattr(target, 'im_func'):
+ return (id(target.im_self), id(target.im_func))
+ return id(target)
+
+
+class Signal(object):
+ """Base class for all signals
+
+ Internal attributes:
+ receivers -- { receriverkey (id) : weakref(receiver) }
+ """
+
+ def __init__(self, providing_args=None):
+ """providing_args -- A list of the arguments this signal can pass along
+ in a send() call.
+ """
+ self.receivers = []
+ if providing_args is None:
+ providing_args = []
+ self.providing_args = set(providing_args)
+
+ def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
+ """Connect receiver to sender for signal
+
+ receiver -- a function or an instance method which is to
+ receive signals. Receivers must be
+ hashable objects.
+
+ if weak is True, then receiver must be weak-referencable
+ (more precisely saferef.safeRef() must be able to create
+ a reference to the receiver).
+
+ Receivers must be able to accept keyword arguments.
+
+ If receivers have a dispatch_uid attribute, the receiver will
+ not be added if another receiver already exists with that
+ dispatch_uid.
+
+ sender -- the sender to which the receiver should respond
+ Must either be of type Signal, or None to receive events
+ from any sender.
+
+ weak -- whether to use weak references to the receiver
+ By default, the module will attempt to use weak
+ references to the receiver objects. If this parameter
+ is false, then strong references will be used.
+
+ dispatch_uid -- an identifier used to uniquely identify a particular
+ instance of a receiver. This will usually be a string, though it
+ may be anything hashable.
+
+ returns None
+ """
+ if dispatch_uid:
+ lookup_key = (dispatch_uid, _make_id(sender))
+ else:
+ lookup_key = (_make_id(receiver), _make_id(sender))
+
+ if weak:
+ receiver = saferef.safeRef(receiver,
+ onDelete=self._remove_receiver)
+
+ for r_key, _ in self.receivers:
+ if r_key == lookup_key:
+ break
+ else:
+ self.receivers.append((lookup_key, receiver))
+
+ def disconnect(self, receiver=None, sender=None, weak=True,
+ dispatch_uid=None):
+ """Disconnect receiver from sender for signal
+
+ receiver -- the registered receiver to disconnect. May be none if
+ dispatch_uid is specified.
+ sender -- the registered sender to disconnect
+ weak -- the weakref state to disconnect
+ dispatch_uid -- the unique identifier of the receiver to disconnect
+
+ disconnect reverses the process of connect.
+
+ If weak references are used, disconnect need not be called.
+ The receiver will be remove from dispatch automatically.
+
+ returns None
+ """
+
+ if dispatch_uid:
+ lookup_key = (dispatch_uid, _make_id(sender))
+ else:
+ lookup_key = (_make_id(receiver), _make_id(sender))
+
+ for idx, (r_key, _) in enumerate(self.receivers):
+ if r_key == lookup_key:
+ del self.receivers[idx]
+
+ def send(self, sender, **named):
+ """Send signal from sender to all connected receivers.
+
+ sender -- the sender of the signal
+ Either a specific object or None.
+
+ named -- named arguments which will be passed to receivers.
+
+ Returns a list of tuple pairs [(receiver, response), ... ].
+
+ If any receiver raises an error, the error propagates back
+ through send, terminating the dispatch loop, so it is quite
+ possible to not have all receivers called if a raises an
+ error.
+ """
+
+ responses = []
+ if not self.receivers:
+ return responses
+
+ for receiver in self._live_receivers(_make_id(sender)):
+ response = receiver(signal=self, sender=sender, **named)
+ responses.append((receiver, response))
+ return responses
+
+ def send_robust(self, sender, **named):
+ """Send signal from sender to all connected receivers catching errors
+
+ sender -- the sender of the signal
+ Can be any python object (normally one registered with
+ a connect if you actually want something to occur).
+
+ named -- named arguments which will be passed to receivers.
+ These arguments must be a subset of the argument names
+ defined in providing_args.
+
+ Return a list of tuple pairs [(receiver, response), ... ],
+ may raise DispatcherKeyError
+
+ if any receiver raises an error (specifically any subclass of
+ Exception), the error instance is returned as the result for that
+ receiver.
+ """
+
+ responses = []
+ if not self.receivers:
+ return responses
+
+ # Call each receiver with whatever arguments it can accept.
+ # Return a list of tuple pairs [(receiver, response), ... ].
+ for receiver in self._live_receivers(_make_id(sender)):
+ try:
+ response = receiver(signal=self, sender=sender, **named)
+ except Exception, err:
+ responses.append((receiver, err))
+ else:
+ responses.append((receiver, response))
+ return responses
+
+ def _live_receivers(self, senderkey):
+ """Filter sequence of receivers to get resolved, live receivers
+
+ This checks for weak references
+ and resolves them, then returning only live
+ receivers.
+ """
+ none_senderkey = _make_id(None)
+
+ for (receiverkey_, r_senderkey), receiver in self.receivers:
+ if r_senderkey == none_senderkey or r_senderkey == senderkey:
+ if isinstance(receiver, WEAKREF_TYPES):
+ # Dereference the weak reference.
+ receiver = receiver()
+ if receiver is not None:
+ yield receiver
+ else:
+ yield receiver
+
+ def _remove_receiver(self, receiver):
+ """Remove dead receivers from connections."""
+
+ to_remove = []
+ for key, connected_receiver in self.receivers:
+ if connected_receiver == receiver:
+ to_remove.append(key)
+ for key in to_remove:
+ for idx, (r_key, _) in enumerate(self.receivers):
+ if r_key == key:
+ del self.receivers[idx]
diff --git a/collaboration/dispatch/license.txt b/collaboration/dispatch/license.txt
new file mode 100644
index 0000000..0272c28
--- /dev/null
+++ b/collaboration/dispatch/license.txt
@@ -0,0 +1,66 @@
+sugar.dispatch was originally forked from django.dispatch
+
+Copyright (c) Django Software Foundation and individual contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name of Django nor the names of its contributors may be used
+ to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+django.dispatch was originally forked from PyDispatcher.
+
+PyDispatcher License:
+
+ Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+ The name of Patrick K. O'Brien, or the name of any Contributor,
+ may not be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/collaboration/dispatch/saferef.py b/collaboration/dispatch/saferef.py
new file mode 100644
index 0000000..bb73b5d
--- /dev/null
+++ b/collaboration/dispatch/saferef.py
@@ -0,0 +1,254 @@
+"""
+"Safe weakrefs", originally from pyDispatcher.
+
+Provides a way to safely weakref any function, including bound methods (which
+aren't handled by the core weakref module).
+"""
+
+import weakref
+import traceback
+
+
+def safeRef(target, onDelete=None):
+ """Return a *safe* weak reference to a callable target
+
+ target -- the object to be weakly referenced, if it's a
+ bound method reference, will create a BoundMethodWeakref,
+ otherwise creates a simple weakref.
+ onDelete -- if provided, will have a hard reference stored
+ to the callable to be called after the safe reference
+ goes out of scope with the reference object, (either a
+ weakref or a BoundMethodWeakref) as argument.
+ """
+ if hasattr(target, 'im_self'):
+ if target.im_self is not None:
+ # Turn a bound method into a BoundMethodWeakref instance.
+ # Keep track of these instances for lookup by disconnect().
+ if not hasattr(target, 'im_func'):
+ raise TypeError("safeRef target %r has im_self, but no"
+ " im_func, don't know how to create reference" %
+ (target, ))
+ reference = get_bound_method_weakref(target=target,
+ onDelete=onDelete)
+ return reference
+ if callable(onDelete):
+ return weakref.ref(target, onDelete)
+ else:
+ return weakref.ref(target)
+
+
+class BoundMethodWeakref(object):
+ """'Safe' and reusable weak references to instance methods
+
+ BoundMethodWeakref objects provide a mechanism for
+ referencing a bound method without requiring that the
+ method object itself (which is normally a transient
+ object) is kept alive. Instead, the BoundMethodWeakref
+ object keeps weak references to both the object and the
+ function which together define the instance method.
+
+ Attributes:
+ key -- the identity key for the reference, calculated
+ by the class's calculateKey method applied to the
+ target instance method
+ deletionMethods -- sequence of callable objects taking
+ single argument, a reference to this object which
+ will be called when *either* the target object or
+ target function is garbage collected (i.e. when
+ this object becomes invalid). These are specified
+ as the onDelete parameters of safeRef calls.
+ weakSelf -- weak reference to the target object
+ weakFunc -- weak reference to the target function
+
+ Class Attributes:
+ _allInstances -- class attribute pointing to all live
+ BoundMethodWeakref objects indexed by the class's
+ calculateKey(target) method applied to the target
+ objects. This weak value dictionary is used to
+ short-circuit creation so that multiple references
+ to the same (object, function) pair produce the
+ same BoundMethodWeakref instance.
+
+ """
+
+ _allInstances = weakref.WeakValueDictionary()
+
+ def __new__(cls, target, onDelete=None, *arguments, **named):
+ """Create new instance or return current instance
+
+ Basically this method of construction allows us to
+ short-circuit creation of references to already-
+ referenced instance methods. The key corresponding
+ to the target is calculated, and if there is already
+ an existing reference, that is returned, with its
+ deletionMethods attribute updated. Otherwise the
+ new instance is created and registered in the table
+ of already-referenced methods.
+ """
+ key = cls.calculateKey(target)
+ current = cls._allInstances.get(key)
+ if current is not None:
+ current.deletionMethods.append(onDelete)
+ return current
+ else:
+ base = super(BoundMethodWeakref, cls).__new__(cls)
+ cls._allInstances[key] = base
+ base.__init__(target, onDelete, *arguments, **named)
+ return base
+
+ def __init__(self, target, onDelete=None):
+ """Return a weak-reference-like instance for a bound method
+
+ target -- the instance-method target for the weak
+ reference, must have im_self and im_func attributes
+ and be reconstructable via:
+ target.im_func.__get__( target.im_self )
+ which is true of built-in instance methods.
+ onDelete -- optional callback which will be called
+ when this weak reference ceases to be valid
+ (i.e. either the object or the function is garbage
+ collected). Should take a single argument,
+ which will be passed a pointer to this object.
+ """
+ def remove(weak, self=self):
+ """Set self.isDead to true when method or instance is destroyed"""
+ methods = self.deletionMethods[:]
+ del self.deletionMethods[:]
+ try:
+ # pylint: disable=W0212
+ del self.__class__._allInstances[self.key]
+ except KeyError:
+ pass
+ for function in methods:
+ try:
+ if callable(function):
+ function(self)
+ except Exception, e:
+ try:
+ traceback.print_exc()
+ except AttributeError:
+ print ('Exception during saferef %s cleanup function'
+ ' %s: %s' % (self, function, e))
+ self.deletionMethods = [onDelete]
+ self.key = self.calculateKey(target)
+ self.weakSelf = weakref.ref(target.im_self, remove)
+ self.weakFunc = weakref.ref(target.im_func, remove)
+ self.selfName = str(target.im_self)
+ self.funcName = str(target.im_func.__name__)
+
+ def calculateKey(cls, target):
+ """Calculate the reference key for this reference
+
+ Currently this is a two-tuple of the id()'s of the
+ target object and the target function respectively.
+ """
+ return (id(target.im_self), id(target.im_func))
+ calculateKey = classmethod(calculateKey)
+
+ def __str__(self):
+ """Give a friendly representation of the object"""
+ return '%s( %s.%s )' % (self.__class__.__name__, self.selfName,
+ self.funcName)
+
+ __repr__ = __str__
+
+ def __nonzero__(self):
+ """Whether we are still a valid reference"""
+ return self() is not None
+
+ def __cmp__(self, other):
+ """Compare with another reference"""
+ if not isinstance(other, self.__class__):
+ return cmp(self.__class__, type(other))
+ return cmp(self.key, other.key)
+
+ def __call__(self):
+ """Return a strong reference to the bound method
+
+ If the target cannot be retrieved, then will
+ return None, otherwise returns a bound instance
+ method for our object and function.
+
+ Note:
+ You may call this method any number of times,
+ as it does not invalidate the reference.
+ """
+ target = self.weakSelf()
+ if target is not None:
+ function = self.weakFunc()
+ if function is not None:
+ return function.__get__(target)
+ return None
+
+
+class BoundNonDescriptorMethodWeakref(BoundMethodWeakref):
+ """A specialized BoundMethodWeakref, for platforms where instance methods
+ are not descriptors.
+
+ It assumes that the function name and the target attribute name are the
+ same, instead of assuming that the function is a descriptor. This approach
+ is equally fast, but not 100% reliable because functions can be stored on
+ an attribute named differenty than the function's name such as in:
+
+ class A: pass
+ def foo(self): return 'foo'
+ A.bar = foo
+
+ But this shouldn't be a common use case. So, on platforms where methods
+ aren't descriptors (such as Jython) this implementation has the advantage
+ of working in the most cases.
+ """
+ def __init__(self, target, onDelete=None):
+ """Return a weak-reference-like instance for a bound method
+
+ target -- the instance-method target for the weak
+ reference, must have im_self and im_func attributes
+ and be reconstructable via:
+ target.im_func.__get__( target.im_self )
+ which is true of built-in instance methods.
+ onDelete -- optional callback which will be called
+ when this weak reference ceases to be valid
+ (i.e. either the object or the function is garbage
+ collected). Should take a single argument,
+ which will be passed a pointer to this object.
+ """
+ assert getattr(target.im_self, target.__name__) == target, \
+ ("method %s isn't available as the attribute %s of %s" %
+ (target, target.__name__, target.im_self))
+ super(BoundNonDescriptorMethodWeakref, self).__init__(target, onDelete)
+
+ def __call__(self):
+ """Return a strong reference to the bound method
+
+ If the target cannot be retrieved, then will
+ return None, otherwise returns a bound instance
+ method for our object and function.
+
+ Note:
+ You may call this method any number of times,
+ as it does not invalidate the reference.
+ """
+ target = self.weakSelf()
+ if target is not None:
+ function = self.weakFunc()
+ if function is not None:
+ # Using curry() would be another option, but it erases the
+ # "signature" of the function. That is, after a function is
+ # curried, the inspect module can't be used to determine how
+ # many arguments the function expects, nor what keyword
+ # arguments it supports, and pydispatcher needs this
+ # information.
+ return getattr(target, function.__name__)
+ return None
+
+
+def get_bound_method_weakref(target, onDelete):
+ """Instantiates the appropiate BoundMethodWeakRef, depending on the details
+ of the underlying class method implementation"""
+ if hasattr(target, '__get__'):
+ # target method is a descriptor, so the default implementation works:
+ return BoundMethodWeakref(target=target, onDelete=onDelete)
+ else:
+ # no luck, use the alternative implementation:
+ return BoundNonDescriptorMethodWeakref(target=target,
+ onDelete=onDelete)
diff --git a/collaboration/neighborhood.py b/collaboration/neighborhood.py
new file mode 100755
index 0000000..192b66f
--- /dev/null
+++ b/collaboration/neighborhood.py
@@ -0,0 +1,1048 @@
+#!/usr/bin/python
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2010-11 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# 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 functools import partial
+from hashlib import sha1
+
+import traceback
+import gobject
+import gconf
+import dbus
+from dbus import PROPERTIES_IFACE
+from telepathy.interfaces import ACCOUNT, \
+ ACCOUNT_MANAGER, \
+ CHANNEL, \
+ CHANNEL_INTERFACE_GROUP, \
+ CHANNEL_TYPE_CONTACT_LIST, \
+ CHANNEL_TYPE_FILE_TRANSFER, \
+ CLIENT, \
+ CONNECTION, \
+ CONNECTION_INTERFACE_ALIASING, \
+ CONNECTION_INTERFACE_CONTACTS, \
+ CONNECTION_INTERFACE_CONTACT_CAPABILITIES, \
+ CONNECTION_INTERFACE_REQUESTS, \
+ CONNECTION_INTERFACE_SIMPLE_PRESENCE
+from telepathy.constants import HANDLE_TYPE_CONTACT, \
+ HANDLE_TYPE_LIST, \
+ CONNECTION_PRESENCE_TYPE_OFFLINE, \
+ CONNECTION_STATUS_CONNECTED, \
+ CONNECTION_STATUS_DISCONNECTED
+from telepathy.client import Connection, Channel
+
+from buddy import get_owner_instance
+from buddy import BuddyModel
+
+from xocolor import XoColor
+
+import activity
+
+from connectionmanager import get_connection_manager
+
+import signal, os, sys
+from activity import Activity
+
+ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager'
+ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager'
+CHANNEL_DISPATCHER_SERVICE = 'org.freedesktop.Telepathy.ChannelDispatcher'
+CHANNEL_DISPATCHER_PATH = '/org/freedesktop/Telepathy/ChannelDispatcher'
+SUGAR_CLIENT_SERVICE = 'org.freedesktop.Telepathy.Client.Sugar'
+SUGAR_CLIENT_PATH = '/org/freedesktop/Telepathy/Client/Sugar'
+
+CONNECTION_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
+CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \
+ 'org.laptop.Telepathy.ActivityProperties'
+
+_QUERY_DBUS_TIMEOUT = 200
+"""
+Time in seconds to wait when querying contact properties. Some jabber servers
+will be very slow in returning these queries, so just be patient.
+"""
+
+
+class ActivityModel(gobject.GObject):
+ __gsignals__ = {
+ 'current-buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'current-buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ }
+
+ def __init__(self, activity_id, room_handle):
+ gobject.GObject.__init__(self)
+
+ self.activity_id = activity_id
+ self.room_handle = room_handle
+ self._bundle = None
+ self._color = None
+ self._private = True
+ self._name = None
+ self._current_buddies = []
+ self._buddies = []
+
+ def get_color(self):
+ return self._color
+
+ def set_color(self, color):
+ self._color = color
+
+ color = gobject.property(type=object, getter=get_color, setter=set_color)
+
+ def get_bundle(self):
+ return self._bundle
+
+ def set_bundle(self, bundle):
+ self._bundle = bundle
+
+ bundle = gobject.property(type=object, getter=get_bundle,
+ setter=set_bundle)
+
+ def get_name(self):
+ return self._name
+
+ def set_name(self, name):
+ self._name = name
+
+ name = gobject.property(type=object, getter=get_name, setter=set_name)
+
+ def is_private(self):
+ return self._private
+
+ def set_private(self, private):
+ self._private = private
+
+ private = gobject.property(type=object, getter=is_private,
+ setter=set_private)
+
+ def get_buddies(self):
+ return self._buddies
+
+ def add_buddy(self, buddy):
+ self._buddies.append(buddy)
+ self.notify('buddies')
+ self.emit('buddy-added', buddy)
+
+ def remove_buddy(self, buddy):
+ self._buddies.remove(buddy)
+ self.notify('buddies')
+ self.emit('buddy-removed', buddy)
+
+ buddies = gobject.property(type=object, getter=get_buddies)
+
+ def get_current_buddies(self):
+ return self._current_buddies
+
+ def add_current_buddy(self, buddy):
+ self._current_buddies.append(buddy)
+ self.notify('current-buddies')
+ self.emit('current-buddy-added', buddy)
+
+ def remove_current_buddy(self, buddy):
+ self._current_buddies.remove(buddy)
+ self.notify('current-buddies')
+ self.emit('current-buddy-removed', buddy)
+
+ current_buddies = gobject.property(type=object, getter=get_current_buddies)
+
+
+class _Account(gobject.GObject):
+ __gsignals__ = {
+ 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object, object])),
+ 'activity-updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object, object])),
+ 'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object, object, object])),
+ 'buddy-updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object, object])),
+ 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'buddy-joined-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object, object])),
+ 'buddy-left-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object, object])),
+ 'current-activity-updated': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object, object])),
+ 'connected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'disconnected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, account_path):
+ gobject.GObject.__init__(self)
+
+ self.object_path = account_path
+
+ self._connection = None
+ self._buddy_handles = {}
+ self._activity_handles = {}
+ self._self_handle = None
+
+ self._buddies_per_activity = {}
+ self._activities_per_buddy = {}
+
+ self._start_listening()
+
+ def _start_listening(self):
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path)
+ obj.Get(ACCOUNT, 'Connection',
+ reply_handler=self.__got_connection_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Account.GetConnection'))
+ obj.connect_to_signal(
+ 'AccountPropertyChanged', self.__account_property_changed_cb)
+
+ def __error_handler_cb(self, function_name, error):
+ raise RuntimeError('Error when calling %s: %s' % (function_name,
+ error))
+
+ def __got_connection_cb(self, connection_path):
+ #print('_Account.__got_connection_cb %r', connection_path)
+
+ if connection_path == '/':
+ self._check_registration_error()
+ return
+
+ self._prepare_connection(connection_path)
+
+ def _check_registration_error(self):
+ """
+ See if a previous connection attempt failed and we need to unset
+ the register flag.
+ """
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path)
+ obj.Get(ACCOUNT, 'ConnectionError',
+ reply_handler=self.__got_connection_error_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Account.GetConnectionError'))
+
+ def __got_connection_error_cb(self, error):
+ #print('_Account.__got_connection_error_cb %r', error)
+ if error == 'org.freedesktop.Telepathy.Error.RegistrationExists':
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path)
+ obj.UpdateParameters({'register': False}, [],
+ dbus_interface=ACCOUNT)
+
+ def __account_property_changed_cb(self, properties):
+ #print('_Account.__account_property_changed_cb %r %r %r',
+ #self.object_path, properties.get('Connection', None),
+ # self._connection)
+ if 'Connection' not in properties:
+ return
+ if properties['Connection'] == '/':
+ self._check_registration_error()
+ self._connection = None
+ elif self._connection is None:
+ self._prepare_connection(properties['Connection'])
+
+ def _prepare_connection(self, connection_path):
+ connection_name = connection_path.replace('/', '.')[1:]
+ print("Preparing %s" % connection_name)
+ self._connection = Connection(connection_name, connection_path,
+ ready_handler=self.__connection_ready_cb)
+
+ def __connection_ready_cb(self, connection):
+ print('_Account.__connection_ready_cb %r', connection.object_path)
+ connection.connect_to_signal('StatusChanged',
+ self.__status_changed_cb)
+
+ connection[PROPERTIES_IFACE].Get(CONNECTION,
+ 'Status',
+ reply_handler=self.__get_status_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Connection.GetStatus'))
+
+ def __get_status_cb(self, status):
+ #print('_Account.__get_status_cb %r %r',
+ #self._connection.object_path, status)
+ self._update_status(status)
+
+ def __status_changed_cb(self, status, reason):
+ #print('_Account.__status_changed_cb %r %r', status, reason)
+ self._update_status(status)
+
+ def _update_status(self, status):
+ if status == CONNECTION_STATUS_CONNECTED:
+ self._connection[PROPERTIES_IFACE].Get(CONNECTION,
+ 'SelfHandle',
+ reply_handler=self.__get_self_handle_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Connection.GetSelfHandle'))
+ self.emit('connected')
+ else:
+ for contact_handle, contact_id in self._buddy_handles.items():
+ if contact_id is not None:
+ self.emit('buddy-removed', contact_id)
+
+ for room_handle, activity_id in self._activity_handles.items():
+ self.emit('activity-removed', activity_id)
+
+ self._buddy_handles = {}
+ self._activity_handles = {}
+ self._buddies_per_activity = {}
+ self._activities_per_buddy = {}
+
+ self.emit('disconnected')
+
+ if status == CONNECTION_STATUS_DISCONNECTED:
+ self._connection = None
+
+ def __get_self_handle_cb(self, self_handle):
+ self._self_handle = self_handle
+
+ if CONNECTION_INTERFACE_CONTACT_CAPABILITIES in self._connection:
+ interface = CONNECTION_INTERFACE_CONTACT_CAPABILITIES
+ connection = self._connection[interface]
+ client_name = CLIENT + '.Sugar.FileTransfer'
+ file_transfer_channel_class = {
+ CHANNEL + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER,
+ CHANNEL + '.TargetHandleType': HANDLE_TYPE_CONTACT}
+ capabilities = []
+ connection.UpdateCapabilities(
+ [(client_name, [file_transfer_channel_class], capabilities)],
+ reply_handler=self.__update_capabilities_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Connection.UpdateCapabilities'))
+
+ connection = self._connection[CONNECTION_INTERFACE_ALIASING]
+ connection.connect_to_signal('AliasesChanged',
+ self.__aliases_changed_cb)
+
+ connection = self._connection[CONNECTION_INTERFACE_SIMPLE_PRESENCE]
+ connection.connect_to_signal('PresencesChanged',
+ self.__presences_changed_cb)
+
+ if CONNECTION_INTERFACE_BUDDY_INFO in self._connection:
+ connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO]
+ connection.connect_to_signal('PropertiesChanged',
+ self.__buddy_info_updated_cb,
+ byte_arrays=True)
+
+ connection.connect_to_signal('ActivitiesChanged',
+ self.__buddy_activities_changed_cb)
+
+ connection.connect_to_signal('CurrentActivityChanged',
+ self.__current_activity_changed_cb)
+ else:
+ print('Connection %s does not support OLPC buddy '
+ 'properties', self._connection.object_path)
+ pass
+
+ if CONNECTION_INTERFACE_ACTIVITY_PROPERTIES in self._connection:
+ connection = self._connection[
+ CONNECTION_INTERFACE_ACTIVITY_PROPERTIES]
+ connection.connect_to_signal(
+ 'ActivityPropertiesChanged',
+ self.__activity_properties_changed_cb)
+ else:
+ print('Connection %s does not support OLPC activity '
+ 'properties', self._connection.object_path)
+ pass
+
+ properties = {
+ CHANNEL + '.ChannelType': CHANNEL_TYPE_CONTACT_LIST,
+ CHANNEL + '.TargetHandleType': HANDLE_TYPE_LIST,
+ CHANNEL + '.TargetID': 'subscribe',
+ }
+ properties = dbus.Dictionary(properties, signature='sv')
+ connection = self._connection[CONNECTION_INTERFACE_REQUESTS]
+ is_ours, channel_path, properties = \
+ connection.EnsureChannel(properties)
+
+ channel = Channel(self._connection.service_name, channel_path)
+ channel[CHANNEL_INTERFACE_GROUP].connect_to_signal(
+ 'MembersChanged', self.__members_changed_cb)
+
+ channel[PROPERTIES_IFACE].Get(CHANNEL_INTERFACE_GROUP,
+ 'Members',
+ reply_handler=self.__get_members_ready_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Connection.GetMembers'))
+
+ def __update_capabilities_cb(self):
+ pass
+
+ def __aliases_changed_cb(self, aliases):
+ #print('_Account.__aliases_changed_cb')
+ for handle, alias in aliases:
+ if handle in self._buddy_handles:
+ #print('Got handle %r with nick %r, going to update',
+ # handle, alias)
+ properties = {CONNECTION_INTERFACE_ALIASING + '/alias': alias}
+ self.emit('buddy-updated', self._buddy_handles[handle],
+ properties)
+
+ def __presences_changed_cb(self, presences):
+ #print('_Account.__presences_changed_cb %r', presences)
+ for handle, presence in presences.iteritems():
+ if handle in self._buddy_handles:
+ presence_type, status_, message_ = presence
+ if presence_type == CONNECTION_PRESENCE_TYPE_OFFLINE:
+ contact_id = self._buddy_handles[handle]
+ del self._buddy_handles[handle]
+ self.emit('buddy-removed', contact_id)
+
+ def __buddy_info_updated_cb(self, handle, properties):
+ #print('_Account.__buddy_info_updated_cb %r', handle)
+ self.emit('buddy-updated', self._buddy_handles[handle], properties)
+
+ def __current_activity_changed_cb(self, contact_handle, activity_id,
+ room_handle):
+ #print('_Account.__current_activity_changed_cb %r %r %r',
+ # contact_handle, activity_id, room_handle)
+ if contact_handle in self._buddy_handles:
+ contact_id = self._buddy_handles[contact_handle]
+ if not activity_id and room_handle:
+ activity_id = self._activity_handles.get(room_handle, '')
+ self.emit('current-activity-updated', contact_id, activity_id)
+
+ def __get_current_activity_cb(self, contact_handle, activity_id,
+ room_handle):
+ #print('_Account.__get_current_activity_cb %r %r %r',
+ # contact_handle, activity_id, room_handle)
+ contact_id = self._buddy_handles[contact_handle]
+ self.emit('current-activity-updated', contact_id, activity_id)
+
+ def __buddy_activities_changed_cb(self, buddy_handle, activities):
+ self._update_buddy_activities(buddy_handle, activities)
+
+ def _update_buddy_activities(self, buddy_handle, activities):
+ #print('_Account._update_buddy_activities')
+ if not buddy_handle in self._buddy_handles:
+ self._buddy_handles[buddy_handle] = None
+
+ if not buddy_handle in self._activities_per_buddy:
+ self._activities_per_buddy[buddy_handle] = set()
+
+ for activity_id, room_handle in activities:
+ if room_handle not in self._activity_handles:
+ self._activity_handles[room_handle] = activity_id
+ self.emit('activity-added', room_handle, activity_id)
+
+ connection = self._connection[
+ CONNECTION_INTERFACE_ACTIVITY_PROPERTIES]
+ connection.GetProperties(room_handle,
+ reply_handler=partial(self.__get_properties_cb,
+ room_handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'ActivityProperties.GetProperties'))
+
+ # Sometimes we'll get CurrentActivityChanged before we get to
+ # know about the activity so we miss the event. In that case,
+ # request again the current activity for this buddy.
+ connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO]
+ connection.GetCurrentActivity(
+ buddy_handle,
+ reply_handler=partial(self.__get_current_activity_cb,
+ buddy_handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.GetCurrentActivity'))
+
+ if not activity_id in self._buddies_per_activity:
+ self._buddies_per_activity[activity_id] = set()
+ self._buddies_per_activity[activity_id].add(buddy_handle)
+ if activity_id not in self._activities_per_buddy[buddy_handle]:
+ self._activities_per_buddy[buddy_handle].add(activity_id)
+ if self._buddy_handles[buddy_handle] is not None:
+ self.emit('buddy-joined-activity',
+ self._buddy_handles[buddy_handle],
+ activity_id)
+
+ current_activity_ids = \
+ [activity_id for activity_id, room_handle in activities]
+ for activity_id in self._activities_per_buddy[buddy_handle].copy():
+ if not activity_id in current_activity_ids:
+ self._remove_buddy_from_activity(buddy_handle, activity_id)
+
+ def __get_properties_cb(self, room_handle, properties):
+ #print('_Account.__get_properties_cb %r %r', room_handle,
+ # properties)
+ if properties:
+ self._update_activity(room_handle, properties)
+
+ def _remove_buddy_from_activity(self, buddy_handle, activity_id):
+ if buddy_handle in self._buddies_per_activity[activity_id]:
+ self._buddies_per_activity[activity_id].remove(buddy_handle)
+
+ if activity_id in self._activities_per_buddy[buddy_handle]:
+ self._activities_per_buddy[buddy_handle].remove(activity_id)
+
+ if self._buddy_handles[buddy_handle] is not None:
+ self.emit('buddy-left-activity',
+ self._buddy_handles[buddy_handle],
+ activity_id)
+
+ if not self._buddies_per_activity[activity_id]:
+ del self._buddies_per_activity[activity_id]
+
+ for room_handle in self._activity_handles.copy():
+ if self._activity_handles[room_handle] == activity_id:
+ del self._activity_handles[room_handle]
+ break
+
+ self.emit('activity-removed', activity_id)
+
+ def __activity_properties_changed_cb(self, room_handle, properties):
+ #print('_Account.__activity_properties_changed_cb %r %r',
+ # room_handle, properties)
+ self._update_activity(room_handle, properties)
+
+ def _update_activity(self, room_handle, properties):
+ if room_handle in self._activity_handles:
+ self.emit('activity-updated', self._activity_handles[room_handle],
+ properties)
+ else:
+ #print('_Account.__activity_properties_changed_cb unknown '
+ # 'activity')
+ # We don't get ActivitiesChanged for the owner of the connection,
+ # so we query for its activities in order to find out.
+ if CONNECTION_INTERFACE_BUDDY_INFO in self._connection:
+ handle = self._self_handle
+ connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO]
+ connection.GetActivities(
+ handle,
+ reply_handler=partial(self.__got_activities_cb, handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.Getactivities'))
+
+ def __members_changed_cb(self, message, added, removed, local_pending,
+ remote_pending, actor, reason):
+ self._add_buddy_handles(added)
+
+ def __get_members_ready_cb(self, handles):
+ #print('_Account.__get_members_ready_cb %r', handles)
+ if not handles:
+ return
+
+ self._add_buddy_handles(handles)
+
+ def _add_buddy_handles(self, handles):
+ #print('_Account._add_buddy_handles %r', handles)
+ interfaces = [CONNECTION, CONNECTION_INTERFACE_ALIASING]
+ self._connection[CONNECTION_INTERFACE_CONTACTS].GetContactAttributes(
+ handles, interfaces, False,
+ reply_handler=self.__get_contact_attributes_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Contacts.GetContactAttributes'))
+
+ def __got_buddy_info_cb(self, handle, nick, properties):
+ #print('_Account.__got_buddy_info_cb %r', handle)
+ self.emit('buddy-updated', self._buddy_handles[handle], properties)
+
+ def __get_contact_attributes_cb(self, attributes):
+ #print('_Account.__get_contact_attributes_cb %r',
+ # attributes.keys())
+
+ for handle in attributes.keys():
+ nick = attributes[handle][CONNECTION_INTERFACE_ALIASING + '/alias']
+
+ if handle in self._buddy_handles and \
+ not self._buddy_handles[handle] is None:
+ #print('Got handle %r with nick %r, going to update',
+ # handle, nick)
+ self.emit('buddy-updated', self._buddy_handles[handle],
+ attributes[handle])
+ else:
+ #print('Got handle %r with nick %r, going to add',
+ # handle, nick)
+
+ contact_id = attributes[handle][CONNECTION + '/contact-id']
+ self._buddy_handles[handle] = contact_id
+
+ if CONNECTION_INTERFACE_BUDDY_INFO in self._connection:
+ connection = \
+ self._connection[CONNECTION_INTERFACE_BUDDY_INFO]
+
+ connection.GetProperties(
+ handle,
+ reply_handler=partial(self.__got_buddy_info_cb, handle,
+ nick),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.GetProperties'),
+ byte_arrays=True,
+ timeout=_QUERY_DBUS_TIMEOUT)
+
+ connection.GetActivities(
+ handle,
+ reply_handler=partial(self.__got_activities_cb,
+ handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.GetActivities'),
+ timeout=_QUERY_DBUS_TIMEOUT)
+
+ connection.GetCurrentActivity(
+ handle,
+ reply_handler=partial(self.__get_current_activity_cb,
+ handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.GetCurrentActivity'),
+ timeout=_QUERY_DBUS_TIMEOUT)
+
+ self.emit('buddy-added', contact_id, nick, handle)
+
+ def __got_activities_cb(self, buddy_handle, activities):
+ #print('_Account.__got_activities_cb %r %r', buddy_handle,
+ # activities)
+ self._update_buddy_activities(buddy_handle, activities)
+
+ def enable(self):
+ #print('_Account.enable %s', self.object_path)
+ self._set_enabled(True)
+
+ def disable(self):
+ #print('_Account.disable %s', self.object_path)
+ self._set_enabled(False)
+ self._connection = None
+
+ def _set_enabled(self, value):
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path)
+ obj.Set(ACCOUNT, 'Enabled', value,
+ reply_handler=self.__set_enabled_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Account.SetEnabled'),
+ dbus_interface='org.freedesktop.DBus.Properties')
+
+ def __set_enabled_cb(self):
+ #print('_Account.__set_enabled_cb success')
+ pass
+
+
+
+class Neighborhood(gobject.GObject):
+ __gsignals__ = {
+ 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ }
+
+ def __init__(self, params = {}):
+ gobject.GObject.__init__(self)
+
+ self._buddies = {None: get_owner_instance()}
+ self._activities = {}
+ self._server_account = None
+ self._nicks = {}
+
+ #
+ # Jabber params
+ #
+ self._nickname = params["nickname"]
+ self._account_id = params["account_id"]
+ self._server = params["server"]
+ self._port = params["port"]
+ self._password = params["password"]
+ self._register = params["register"]
+
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
+ account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
+ account_manager.Get(ACCOUNT_MANAGER, 'ValidAccounts',
+ dbus_interface=PROPERTIES_IFACE,
+ reply_handler=self.__got_accounts_cb,
+ error_handler=self.__error_handler_cb)
+
+ def show_buddies(self):
+ print "\n\nBuddy list\n\n"
+ for k in self._nicks.keys():
+ try:
+ print "%s = %s" % (k, self._nicks[k])
+ except:
+ pass
+
+ print "\n\nActivities list\n\n"
+ for k in self._activities.keys():
+ try:
+ print "%s" % k
+ except:
+ pass
+
+ def __got_accounts_cb(self, account_paths):
+ self._server_account = self._ensure_server_account(account_paths)
+ self._connect_to_account(self._server_account)
+
+ def __error_handler_cb(self, error):
+ raise RuntimeError(error)
+
+
+ def _connect_to_account(self, account):
+ account.connect('buddy-added', self.__buddy_added_cb)
+ account.connect('buddy-updated', self.__buddy_updated_cb)
+ account.connect('buddy-removed', self.__buddy_removed_cb)
+ account.connect('buddy-joined-activity',
+ self.__buddy_joined_activity_cb)
+ account.connect('buddy-left-activity', self.__buddy_left_activity_cb)
+ account.connect('activity-added', self.__activity_added_cb)
+ account.connect('activity-updated', self.__activity_updated_cb)
+ account.connect('activity-removed', self.__activity_removed_cb)
+ account.connect('current-activity-updated',
+ self.__current_activity_updated_cb)
+ account.connect('connected', self.__account_connected_cb)
+ account.connect('disconnected', self.__account_disconnected_cb)
+
+ def __account_connected_cb(self, account):
+ #print('__account_connected_cb %s', account.object_path)
+ if account == self._server_account:
+ #self._link_local_account.disable()
+ pass
+
+ def __account_disconnected_cb(self, account):
+ #print('__account_disconnected_cb %s', account.object_path)
+ if account == self._server_account:
+ self._link_local_account.enable()
+
+ def _ensure_link_local_account(self, account_paths):
+ for account_path in account_paths:
+ if 'salut' in account_path:
+ #print('Already have a Salut account')
+ account = _Account(account_path)
+ account.enable()
+ return account
+
+ #print('Still dont have a Salut account, creating one')
+
+ client = gconf.client_get_default()
+ nick = client.get_string('/desktop/sugar/user/nick')
+
+ params = {
+ 'nickname': nick,
+ 'first-name': '',
+ 'last-name': '',
+ 'jid': self._get_jabber_account_id(),
+ 'published-name': nick,
+ }
+
+ properties = {
+ ACCOUNT + '.Enabled': True,
+ ACCOUNT + '.Nickname': nick,
+ ACCOUNT + '.ConnectAutomatically': True,
+ }
+
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
+ account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
+ account_path = account_manager.CreateAccount('salut', 'local-xmpp',
+ 'salut', params,
+ properties)
+ return _Account(account_path)
+
+ def _ensure_server_account(self, account_paths):
+ bus = dbus.Bus()
+
+ for account_path in account_paths:
+ if 'gabble' in account_path:
+ obj_acct_mgr = bus.get_object(ACCOUNT_MANAGER_SERVICE, account_path)
+ properties = obj_acct_mgr.Get(ACCOUNT, 'Parameters')
+ if properties.has_key("server") and properties["server"] == self._server:
+ print("Enabiling account_path = %s, server = %s", account_path, self._server)
+ account = _Account(account_path)
+ account.enable()
+ return account
+
+ params = {
+ 'account': self._get_jabber_account_id(),
+ 'password': self._password,
+ 'server': self._server,
+ 'resource': 'sugar',
+ 'require-encryption': True,
+ 'ignore-ssl-errors': True,
+ 'register': self._register,
+ 'old-ssl': True,
+ 'port': dbus.UInt32(self._port),
+ }
+
+ properties = {
+ ACCOUNT + '.Enabled': True,
+ ACCOUNT + '.Nickname': self._nickname,
+ ACCOUNT + '.ConnectAutomatically': True,
+ }
+
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
+ account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
+ account_path = account_manager.CreateAccount('gabble', 'jabber',
+ 'jabber', params,
+ properties)
+ return _Account(account_path)
+
+ def _get_jabber_account_id(self):
+ return self._account_id
+
+ def __jabber_server_changed_cb(self, client, timestamp, entry, *extra):
+ #print('__jabber_server_changed_cb')
+
+ bus = dbus.Bus()
+ account = bus.get_object(ACCOUNT_MANAGER_SERVICE,
+ self._server_account.object_path)
+
+ server = client.get_string(
+ '/desktop/sugar/collaboration/jabber_server')
+ account_id = self._get_jabber_account_id()
+ needs_reconnect = account.UpdateParameters({'server': server,
+ 'account': account_id,
+ 'register': True},
+ dbus.Array([], 's'),
+ dbus_interface=ACCOUNT)
+ if needs_reconnect:
+ account.Reconnect()
+
+ self._update_jid()
+
+ def __nick_changed_cb(self, client, timestamp, entry, *extra):
+ nick = client.get_string('/desktop/sugar/user/nick')
+ for account in self._server_account, self._link_local_account:
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, account.object_path)
+ obj.Set(ACCOUNT, 'Nickname', nick, dbus_interface=PROPERTIES_IFACE)
+
+ self._update_jid()
+
+ def _update_jid(self):
+ bus = dbus.Bus()
+ account = bus.get_object(ACCOUNT_MANAGER_SERVICE,
+ self._link_local_account.object_path)
+
+ account_id = self._get_jabber_account_id()
+ needs_reconnect = account.UpdateParameters({'jid': account_id},
+ dbus.Array([], 's'),
+ dbus_interface=ACCOUNT)
+ if needs_reconnect:
+ account.Reconnect()
+
+ def __buddy_added_cb(self, account, contact_id, nick, handle):
+
+ self._nicks[contact_id] = nick
+ if contact_id in self._buddies:
+ #print('__buddy_added_cb buddy already tracked')
+ return
+
+ buddy = BuddyModel(
+ nick=nick,
+ account=account.object_path,
+ contact_id=contact_id,
+ handle=handle)
+ self._buddies[contact_id] = buddy
+
+ def __buddy_updated_cb(self, account, contact_id, properties):
+ #print('__buddy_updated_cb %r', contact_id)
+ if contact_id is None:
+ # Don't know the contact-id yet, will get the full state later
+ return
+
+ if contact_id not in self._buddies:
+ #print('__buddy_updated_cb Unknown buddy with contact_id'
+ # ' %r', contact_id)
+ return
+
+ buddy = self._buddies[contact_id]
+
+ is_new = buddy.props.key is None and 'key' in properties
+
+ if 'color' in properties:
+ buddy.props.color = XoColor(properties['color'])
+
+ if 'key' in properties:
+ buddy.props.key = properties['key']
+
+ if 'nick' in properties:
+ buddy.props.nick = properties['nick']
+
+ if is_new:
+ self.emit('buddy-added', buddy)
+
+ def __buddy_removed_cb(self, account, contact_id):
+ #print('Neighborhood.__buddy_removed_cb %r', contact_id)
+ try:
+ self._nicks.pop(contact_id)
+ except:
+ pass
+ if contact_id not in self._buddies:
+ #print('Neighborhood.__buddy_removed_cb Unknown buddy with '
+ # 'contact_id %r', contact_id)
+ return
+
+ buddy = self._buddies[contact_id]
+ del self._buddies[contact_id]
+
+ if buddy.props.key is not None:
+ self.emit('buddy-removed', buddy)
+
+ def __activity_added_cb(self, account, room_handle, activity_id):
+ #print('__activity_added_cb %r %r', room_handle, activity_id)
+ if activity_id in self._activities:
+ #print('__activity_added_cb activity already tracked')
+ return
+
+ activity = ActivityModel(activity_id, room_handle)
+ self._activities[activity_id] = activity
+
+ def __activity_updated_cb(self, account, activity_id, properties):
+ print('__activity_updated_cb %r %r', activity_id, properties)
+ if activity_id not in self._activities:
+ print('__activity_updated_cb Unknown activity with activity_id %r', activity_id)
+ return
+
+ # we should somehow emulate this and say we only have TurtleArtActivity
+ #registry = bundleregistry.get_registry()
+ #bundle = registry.get_bundle(properties['type'])
+ #bundle = None
+ #if not bundle:
+ # print('Ignoring shared activity we don''t have')
+ # return
+
+ activity = self._activities[activity_id]
+
+ is_new = activity.props.bundle is None
+
+ if 'color' in properties:
+ activity.props.color = XoColor(properties['color'])
+ activity.props.bundle = None # FIXME: we have no access to the bundleregistry
+ if 'name' in properties:
+ activity.props.name = properties['name']
+ if 'private' in properties:
+ activity.props.private = properties['private']
+ # FIXME: this should be configurable, we only care about the activity thats using this lib
+ # i.e.: Turtle Art
+ if properties['type']:
+ activity.props.bundle = properties['type']
+
+ if is_new:
+ print "The activity is new"
+ self.emit('activity-added', activity)
+ else:
+ print "The activity is *NOT* new"
+
+ def __activity_removed_cb(self, account, activity_id):
+ if activity_id not in self._activities:
+ print('Unknown activity with id %s. Already removed?', activity_id)
+ return
+ activity = self._activities[activity_id]
+ del self._activities[activity_id]
+
+ self.emit('activity-removed', activity)
+
+ def __current_activity_updated_cb(self, account, contact_id, activity_id):
+ #print('__current_activity_updated_cb %r %r', contact_id,
+ # activity_id)
+ if contact_id not in self._buddies:
+ #print('__current_activity_updated_cb Unknown buddy with '
+ # 'contact_id %r', contact_id)
+ return
+ if activity_id and activity_id not in self._activities:
+ #print('__current_activity_updated_cb Unknown activity with'
+ # ' id %s', activity_id)
+ activity_id = ''
+
+ buddy = self._buddies[contact_id]
+ if buddy.props.current_activity is not None:
+ if buddy.props.current_activity.activity_id == activity_id:
+ return
+ buddy.props.current_activity.remove_current_buddy(buddy)
+
+ if activity_id:
+ activity = self._activities[activity_id]
+ buddy.props.current_activity = activity
+ activity.add_current_buddy(buddy)
+ else:
+ buddy.props.current_activity = None
+
+ def __buddy_joined_activity_cb(self, account, contact_id, activity_id):
+ if contact_id not in self._buddies:
+ #print('__buddy_joined_activity_cb Unknown buddy with '
+ # 'contact_id %r', contact_id)
+ return
+
+ if activity_id not in self._activities:
+ #print('__buddy_joined_activity_cb Unknown activity with '
+ # 'activity_id %r', activity_id)
+ return
+
+ self._activities[activity_id].add_buddy(self._buddies[contact_id])
+
+ def __buddy_left_activity_cb(self, account, contact_id, activity_id):
+ if contact_id not in self._buddies:
+ #print('__buddy_left_activity_cb Unknown buddy with '
+ # 'contact_id %r', contact_id)
+ return
+
+ if activity_id not in self._activities:
+ #print('__buddy_left_activity_cb Unknown activity with '
+ # 'activity_id %r', activity_id)
+ return
+
+ self._activities[activity_id].remove_buddy(self._buddies[contact_id])
+
+ def get_buddies(self):
+ return self._buddies.values()
+
+ def get_buddy_by_key(self, key):
+ for buddy in self._buddies.values():
+ if buddy.key == key:
+ return buddy
+ return None
+
+ def get_buddy_by_handle(self, contact_handle):
+ for buddy in self._buddies.values():
+ if not buddy.is_owner() and buddy.handle == contact_handle:
+ return buddy
+ return None
+
+ def get_activity(self, activity_id):
+ return self._activities.get(activity_id, None)
+
+ def get_activity_by_room(self, room_handle):
+ for activity in self._activities.values():
+ if activity.room_handle == room_handle:
+ return activity
+ return None
+
+ def get_activities(self):
+ return self._activities.values()
+
+_neighborhood = None
+def get_neighborhood(params = {}):
+ global _neighborhood
+ if _neighborhood is None:
+ _neighborhood = Neighborhood(params)
+ return _neighborhood
+
+if __name__ == "__main__":
+ params = {}
+ params["nickname"] = "test"
+ params["account_id"] = "test"
+ params["server"] = "localhost"
+ params["port"] = 5223
+ params["password"] = "test"
+ params["register"] = True
+
+ loop = gobject.MainLoop()
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+ n = get_neighborhood(params)
+ loop.run()
diff --git a/collaboration/presenceservice.py b/collaboration/presenceservice.py
new file mode 100644
index 0000000..33caadd
--- /dev/null
+++ b/collaboration/presenceservice.py
@@ -0,0 +1,271 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import logging
+
+import gobject
+import dbus
+import dbus.exceptions
+import dbus.glib
+from dbus import PROPERTIES_IFACE
+
+""" FIXME ... """
+try:
+ from sugar.presence.buddy import Buddy, Owner
+ from sugar.presence.activity import Activity
+ from sugar.presence.connectionmanager import get_connection_manager
+except:
+ pass
+
+from telepathy.interfaces import ACCOUNT, \
+ ACCOUNT_MANAGER, \
+ CONNECTION
+from telepathy.constants import HANDLE_TYPE_CONTACT
+
+
+_logger = logging.getLogger('sugar.presence.presenceservice')
+
+ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager'
+ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager'
+
+CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
+
+
+class PresenceService(gobject.GObject):
+ """Provides simplified access to the Telepathy framework to activities"""
+ __gsignals__ = {
+ 'activity-shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT])),
+ }
+
+ def __init__(self):
+ """Initialise the service and attempt to connect to events
+ """
+ gobject.GObject.__init__(self)
+
+ self._activity_cache = None
+ self._buddy_cache = {}
+
+ def get_activity(self, activity_id, warn_if_none=True):
+ """Retrieve single Activity object for the given unique id
+
+ activity_id -- unique ID for the activity
+
+ returns single Activity object or None if the activity
+ is not found using GetActivityById on the service
+ """
+ if self._activity_cache is not None:
+ if self._activity_cache.props.id != activity_id:
+ raise RuntimeError('Activities can only access their own'
+ ' shared instance')
+ return self._activity_cache
+ else:
+ connection_manager = get_connection_manager()
+ connections_per_account = \
+ connection_manager.get_connections_per_account()
+ for account_path, connection in connections_per_account.items():
+ if not connection.connected:
+ continue
+ logging.debug('Calling GetActivity on %s', account_path)
+ try:
+ room_handle = connection.connection.GetActivity(
+ activity_id,
+ dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES)
+ except dbus.exceptions.DBusException, e:
+ name = 'org.freedesktop.Telepathy.Error.NotAvailable'
+ if e.get_dbus_name() == name:
+ logging.debug("There's no shared activity with the id "
+ "%s" % (activity_id))
+ else:
+ raise
+ else:
+ activity = Activity(account_path, connection.connection,
+ room_handle=room_handle)
+ self._activity_cache = activity
+ return activity
+
+ return None
+
+ def get_activity_by_handle(self, connection_path, room_handle):
+ if self._activity_cache is not None:
+ if self._activity_cache.room_handle != room_handle:
+ raise RuntimeError('Activities can only access their own'
+ ' shared instance')
+ return self._activity_cache
+ else:
+ connection_manager = get_connection_manager()
+ account_path = \
+ connection_manager.get_account_for_connection(connection_path)
+
+ connection_name = connection_path.replace('/', '.')[1:]
+ bus = dbus.SessionBus()
+ connection = bus.get_object(connection_name, connection_path)
+ activity = Activity(account_path, connection,
+ room_handle=room_handle)
+ self._activity_cache = activity
+ return activity
+
+ def get_buddy(self, account_path, contact_id):
+ if (account_path, contact_id) in self._buddy_cache:
+ return self._buddy_cache[(account_path, contact_id)]
+
+ buddy = Buddy(account_path, contact_id)
+ self._buddy_cache[(account_path, contact_id)] = buddy
+ return buddy
+
+ # DEPRECATED
+ def get_buddy_by_telepathy_handle(self, tp_conn_name, tp_conn_path,
+ handle):
+ """Retrieve single Buddy object for the given public key
+
+ :Parameters:
+ `tp_conn_name` : str
+ The well-known bus name of a Telepathy connection
+ `tp_conn_path` : dbus.ObjectPath
+ The object path of the Telepathy connection
+ `handle` : int or long
+ The handle of a Telepathy contact on that connection,
+ of type HANDLE_TYPE_CONTACT. This may not be a
+ channel-specific handle.
+ :Returns: the Buddy object, or None if the buddy is not found
+ """
+
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
+ account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
+ account_paths = account_manager.Get(ACCOUNT_MANAGER, 'ValidAccounts',
+ dbus_interface=PROPERTIES_IFACE)
+ for account_path in account_paths:
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, account_path)
+ connection_path = obj.Get(ACCOUNT, 'Connection')
+ if connection_path == tp_conn_path:
+ connection_name = connection_path.replace('/', '.')[1:]
+ connection = bus.get_object(connection_name, connection_path)
+ contact_ids = connection.InspectHandles(HANDLE_TYPE_CONTACT,
+ [handle],
+ dbus_interface=CONNECTION)
+ return self.get_buddy(account_path, contact_ids[0])
+
+ raise ValueError('Unknown buddy in connection %s with handle %d' % \
+ (tp_conn_path, handle))
+
+ def get_owner(self):
+ """Retrieves the laptop Buddy object."""
+ #return Owner()
+ return None
+
+ def __share_activity_cb(self, activity):
+ """Finish sharing the activity
+ """
+ self.emit('activity-shared', True, activity, None)
+
+ def __share_activity_error_cb(self, activity, error):
+ """Notify with GObject event of unsuccessful sharing of activity
+ """
+ self.emit('activity-shared', False, activity, error)
+
+ def share_activity(self, activity, properties=None, private=True):
+ if properties is None:
+ properties = {}
+
+ if 'id' not in properties:
+ properties['id'] = activity.get_id()
+
+ if 'type' not in properties:
+ properties['type'] = activity.get_bundle_id()
+
+ if 'name' not in properties:
+ properties['name'] = activity.metadata.get('title', None)
+
+ if 'color' not in properties:
+ properties['color'] = activity.metadata.get('icon-color', None)
+
+ properties['private'] = private
+
+ if self._activity_cache is not None:
+ raise ValueError('Activity %s is already tracked' % \
+ (activity.get_id()))
+
+ connection_manager = get_connection_manager()
+ account_path, connection = \
+ connection_manager.get_preferred_connection()
+
+ if connection is None:
+ self.emit('activity-shared', False, None,
+ 'No active connection available')
+ return
+
+ shared_activity = Activity(account_path, connection,
+ properties=properties)
+ self._activity_cache = shared_activity
+
+ if shared_activity.props.joined:
+ raise RuntimeError('Activity %s is already shared.' % \
+ (activity.props.id))
+
+ shared_activity.share(self.__share_activity_cb,
+ self.__share_activity_error_cb)
+
+ def get_preferred_connection(self):
+ """Gets the preferred telepathy connection object that an activity
+ should use when talking directly to telepathy
+
+ returns the bus name and the object path of the Telepathy connection
+ """
+ manager = get_connection_manager()
+ account_path, connection = manager.get_preferred_connection()
+ if connection is None:
+ return None
+ else:
+ return connection.requested_bus_name, connection.object_path
+
+ # DEPRECATED
+ def get(self, object_path):
+ raise NotImplementedError()
+
+ # DEPRECATED
+ def get_activities(self):
+ raise NotImplementedError()
+
+ # DEPRECATED
+ def get_activities_async(self, reply_handler=None, error_handler=None):
+ raise NotImplementedError()
+
+ # DEPRECATED
+ def get_buddies(self):
+ raise NotImplementedError()
+
+ # DEPRECATED
+ def get_buddies_async(self, reply_handler=None, error_handler=None):
+ raise NotImplementedError()
+
+
+_ps = None
+
+
+def get_instance(allow_offline_iface=False):
+ """Retrieve this process' view of the PresenceService"""
+ global _ps
+ if not _ps:
+ _ps = PresenceService()
+ return _ps
diff --git a/collaboration/telepathyclient.py b/collaboration/telepathyclient.py
new file mode 100644
index 0000000..e00f053
--- /dev/null
+++ b/collaboration/telepathyclient.py
@@ -0,0 +1,105 @@
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import dbus
+from dbus import PROPERTIES_IFACE
+
+from telepathy.interfaces import CLIENT, \
+ CLIENT_APPROVER, \
+ CLIENT_HANDLER, \
+ CLIENT_INTERFACE_REQUESTS
+from telepathy.server import DBusProperties
+
+import dispatch
+
+
+SUGAR_CLIENT_SERVICE = 'org.freedesktop.Telepathy.Client.Sugar'
+SUGAR_CLIENT_PATH = '/org/freedesktop/Telepathy/Client/Sugar'
+
+_instance = None
+
+
+class TelepathyClient(dbus.service.Object, DBusProperties):
+
+ def __init__(self):
+ self._interfaces = set([CLIENT, CLIENT_HANDLER,
+ CLIENT_INTERFACE_REQUESTS, PROPERTIES_IFACE,
+ CLIENT_APPROVER])
+
+ bus = dbus.Bus()
+ bus_name = dbus.service.BusName(SUGAR_CLIENT_SERVICE, bus=bus)
+
+ dbus.service.Object.__init__(self, bus_name, SUGAR_CLIENT_PATH)
+ DBusProperties.__init__(self)
+
+ self._implement_property_get(CLIENT, {
+ 'Interfaces': lambda: list(self._interfaces),
+ })
+ self._implement_property_get(CLIENT_HANDLER, {
+ 'HandlerChannelFilter': self.__get_filters_cb,
+ })
+ self._implement_property_get(CLIENT_APPROVER, {
+ 'ApproverChannelFilter': self.__get_filters_cb,
+ })
+
+ self.got_channel = dispatch.Signal()
+ self.got_dispatch_operation = dispatch.Signal()
+
+ def __get_filters_cb(self):
+ logging.debug('__get_filters_cb')
+ filter_dict = dbus.Dictionary({}, signature='sv')
+ return dbus.Array([filter_dict], signature='a{sv}')
+
+ @dbus.service.method(dbus_interface=CLIENT_HANDLER,
+ in_signature='ooa(oa{sv})aota{sv}', out_signature='')
+ def HandleChannels(self, account, connection, channels, requests_satisfied,
+ user_action_time, handler_info):
+ logging.debug('HandleChannels\n%r\n%r\n%r\n%r\n%r\n%r\n', account,
+ connection, channels, requests_satisfied,
+ user_action_time, handler_info)
+ for channel in channels:
+ self.got_channel.send(self, account=account,
+ connection=connection, channel=channel)
+
+ @dbus.service.method(dbus_interface=CLIENT_INTERFACE_REQUESTS,
+ in_signature='oa{sv}', out_signature='')
+ def AddRequest(self, request, properties):
+ logging.debug('AddRequest\n%r\n%r', request, properties)
+
+ @dbus.service.method(dbus_interface=CLIENT_APPROVER,
+ in_signature='a(oa{sv})oa{sv}', out_signature='',
+ async_callbacks=('success_cb', 'error_cb_'))
+ def AddDispatchOperation(self, channels, dispatch_operation_path,
+ properties, success_cb, error_cb_):
+ success_cb()
+ try:
+ logging.debug('AddDispatchOperation\n%r\n%r\n%r', channels,
+ dispatch_operation_path, properties)
+
+ self.got_dispatch_operation.send(self, channels=channels,
+ dispatch_operation_path=dispatch_operation_path,
+ properties=properties)
+ except Exception, e:
+ logging.exception(e)
+
+
+def get_instance():
+ global _instance
+ if not _instance:
+ _instance = TelepathyClient()
+ return _instance
diff --git a/collaboration/test.py b/collaboration/test.py
new file mode 100755
index 0000000..db7be0d
--- /dev/null
+++ b/collaboration/test.py
@@ -0,0 +1,14 @@
+#!/usr/bin/python
+
+import gobject
+import dbus
+import dbus.mainloop
+import dbus.mainloop.glib
+from connectionmanager import get_connection_manager
+
+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+
+conn_manager = get_connection_manager()
+account_path, connection = conn_manager.get_preferred_connection()
+print account_path
diff --git a/collaboration/tubeconn.py b/collaboration/tubeconn.py
new file mode 100644
index 0000000..1014a46
--- /dev/null
+++ b/collaboration/tubeconn.py
@@ -0,0 +1,114 @@
+# This should eventually land in telepathy-python, so has the same license:
+
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+STABLE.
+"""
+
+__all__ = ('TubeConnection', )
+__docformat__ = 'reStructuredText'
+
+
+import logging
+
+from dbus.connection import Connection
+
+
+logger = logging.getLogger('telepathy.tubeconn')
+
+
+class TubeConnection(Connection):
+
+ def __new__(cls, conn, tubes_iface, tube_id, address=None,
+ group_iface=None, mainloop=None):
+ # pylint: disable=W0212
+ # Confused by __new__
+ if address is None:
+ address = tubes_iface.GetDBusTubeAddress(tube_id)
+ self = super(TubeConnection, cls).__new__(cls, address,
+ mainloop=mainloop)
+
+ self._tubes_iface = tubes_iface
+ self.tube_id = tube_id
+ self.participants = {}
+ self.bus_name_to_handle = {}
+ self._mapping_watches = []
+
+ if group_iface is None:
+ method = conn.GetSelfHandle
+ else:
+ method = group_iface.GetSelfHandle
+ method(reply_handler=self._on_get_self_handle_reply,
+ error_handler=self._on_get_self_handle_error)
+
+ return self
+
+ def _on_get_self_handle_reply(self, handle):
+ # pylint: disable=W0201
+ # Confused by __new__
+ self.self_handle = handle
+ match = self._tubes_iface.connect_to_signal('DBusNamesChanged',
+ self._on_dbus_names_changed)
+ self._tubes_iface.GetDBusNames(self.tube_id,
+ reply_handler=self._on_get_dbus_names_reply,
+ error_handler=self._on_get_dbus_names_error)
+ self._dbus_names_changed_match = match
+
+ def _on_get_self_handle_error(self, e):
+ logging.basicConfig()
+ logger.error('GetSelfHandle failed: %s', e)
+
+ def close(self):
+ self._dbus_names_changed_match.remove()
+ self._on_dbus_names_changed(self.tube_id, (), self.participants.keys())
+ super(TubeConnection, self).close()
+
+ def _on_get_dbus_names_reply(self, names):
+ self._on_dbus_names_changed(self.tube_id, names, ())
+
+ def _on_get_dbus_names_error(self, e):
+ logging.basicConfig()
+ logger.error('GetDBusNames failed: %s', e)
+
+ def _on_dbus_names_changed(self, tube_id, added, removed):
+ if tube_id == self.tube_id:
+ for handle, bus_name in added:
+ if handle == self.self_handle:
+ # I've just joined - set my unique name
+ self.set_unique_name(bus_name)
+ self.participants[handle] = bus_name
+ self.bus_name_to_handle[bus_name] = handle
+
+ # call the callback while the removed people are still in
+ # participants, so their bus names are available
+ for callback in self._mapping_watches:
+ callback(added, removed)
+
+ for handle in removed:
+ bus_name = self.participants.pop(handle, None)
+ self.bus_name_to_handle.pop(bus_name, None)
+
+ def watch_participants(self, callback):
+ self._mapping_watches.append(callback)
+ if self.participants:
+ # GetDBusNames already returned: fake a participant add event
+ # immediately
+ added = []
+ for k, v in self.participants.iteritems():
+ added.append((k, v))
+ callback(added, [])
diff --git a/collaboration/xocolor.py b/collaboration/xocolor.py
new file mode 100644
index 0000000..395e345
--- /dev/null
+++ b/collaboration/xocolor.py
@@ -0,0 +1,282 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import random
+import logging
+
+import gconf
+
+colors = [
+['#B20008', '#FF2B34'], \
+['#FF2B34', '#B20008'], \
+['#E6000A', '#FF2B34'], \
+['#FF2B34', '#E6000A'], \
+['#FFADCE', '#FF2B34'], \
+['#9A5200', '#FF2B34'], \
+['#FF2B34', '#9A5200'], \
+['#FF8F00', '#FF2B34'], \
+['#FF2B34', '#FF8F00'], \
+['#FFC169', '#FF2B34'], \
+['#807500', '#FF2B34'], \
+['#FF2B34', '#807500'], \
+['#BE9E00', '#FF2B34'], \
+['#FF2B34', '#BE9E00'], \
+['#F8E800', '#FF2B34'], \
+['#008009', '#FF2B34'], \
+['#FF2B34', '#008009'], \
+['#00B20D', '#FF2B34'], \
+['#FF2B34', '#00B20D'], \
+['#8BFF7A', '#FF2B34'], \
+['#00588C', '#FF2B34'], \
+['#FF2B34', '#00588C'], \
+['#005FE4', '#FF2B34'], \
+['#FF2B34', '#005FE4'], \
+['#BCCDFF', '#FF2B34'], \
+['#5E008C', '#FF2B34'], \
+['#FF2B34', '#5E008C'], \
+['#7F00BF', '#FF2B34'], \
+['#FF2B34', '#7F00BF'], \
+['#D1A3FF', '#FF2B34'], \
+['#9A5200', '#FF8F00'], \
+['#FF8F00', '#9A5200'], \
+['#C97E00', '#FF8F00'], \
+['#FF8F00', '#C97E00'], \
+['#FFC169', '#FF8F00'], \
+['#807500', '#FF8F00'], \
+['#FF8F00', '#807500'], \
+['#BE9E00', '#FF8F00'], \
+['#FF8F00', '#BE9E00'], \
+['#F8E800', '#FF8F00'], \
+['#008009', '#FF8F00'], \
+['#FF8F00', '#008009'], \
+['#00B20D', '#FF8F00'], \
+['#FF8F00', '#00B20D'], \
+['#8BFF7A', '#FF8F00'], \
+['#00588C', '#FF8F00'], \
+['#FF8F00', '#00588C'], \
+['#005FE4', '#FF8F00'], \
+['#FF8F00', '#005FE4'], \
+['#BCCDFF', '#FF8F00'], \
+['#5E008C', '#FF8F00'], \
+['#FF8F00', '#5E008C'], \
+['#A700FF', '#FF8F00'], \
+['#FF8F00', '#A700FF'], \
+['#D1A3FF', '#FF8F00'], \
+['#B20008', '#FF8F00'], \
+['#FF8F00', '#B20008'], \
+['#FF2B34', '#FF8F00'], \
+['#FF8F00', '#FF2B34'], \
+['#FFADCE', '#FF8F00'], \
+['#807500', '#F8E800'], \
+['#F8E800', '#807500'], \
+['#BE9E00', '#F8E800'], \
+['#F8E800', '#BE9E00'], \
+['#FFFA00', '#EDDE00'], \
+['#008009', '#F8E800'], \
+['#F8E800', '#008009'], \
+['#00EA11', '#F8E800'], \
+['#F8E800', '#00EA11'], \
+['#8BFF7A', '#F8E800'], \
+['#00588C', '#F8E800'], \
+['#F8E800', '#00588C'], \
+['#00A0FF', '#F8E800'], \
+['#F8E800', '#00A0FF'], \
+['#BCCEFF', '#F8E800'], \
+['#5E008C', '#F8E800'], \
+['#F8E800', '#5E008C'], \
+['#AC32FF', '#F8E800'], \
+['#F8E800', '#AC32FF'], \
+['#D1A3FF', '#F8E800'], \
+['#B20008', '#F8E800'], \
+['#F8E800', '#B20008'], \
+['#FF2B34', '#F8E800'], \
+['#F8E800', '#FF2B34'], \
+['#FFADCE', '#F8E800'], \
+['#9A5200', '#F8E800'], \
+['#F8E800', '#9A5200'], \
+['#FF8F00', '#F8E800'], \
+['#F8E800', '#FF8F00'], \
+['#FFC169', '#F8E800'], \
+['#008009', '#00EA11'], \
+['#00EA11', '#008009'], \
+['#00B20D', '#00EA11'], \
+['#00EA11', '#00B20D'], \
+['#8BFF7A', '#00EA11'], \
+['#00588C', '#00EA11'], \
+['#00EA11', '#00588C'], \
+['#005FE4', '#00EA11'], \
+['#00EA11', '#005FE4'], \
+['#BCCDFF', '#00EA11'], \
+['#5E008C', '#00EA11'], \
+['#00EA11', '#5E008C'], \
+['#7F00BF', '#00EA11'], \
+['#00EA11', '#7F00BF'], \
+['#D1A3FF', '#00EA11'], \
+['#B20008', '#00EA11'], \
+['#00EA11', '#B20008'], \
+['#FF2B34', '#00EA11'], \
+['#00EA11', '#FF2B34'], \
+['#FFADCE', '#00EA11'], \
+['#9A5200', '#00EA11'], \
+['#00EA11', '#9A5200'], \
+['#FF8F00', '#00EA11'], \
+['#00EA11', '#FF8F00'], \
+['#FFC169', '#00EA11'], \
+['#807500', '#00EA11'], \
+['#00EA11', '#807500'], \
+['#BE9E00', '#00EA11'], \
+['#00EA11', '#BE9E00'], \
+['#F8E800', '#00EA11'], \
+['#00588C', '#00A0FF'], \
+['#00A0FF', '#00588C'], \
+['#005FE4', '#00A0FF'], \
+['#00A0FF', '#005FE4'], \
+['#BCCDFF', '#00A0FF'], \
+['#5E008C', '#00A0FF'], \
+['#00A0FF', '#5E008C'], \
+['#9900E6', '#00A0FF'], \
+['#00A0FF', '#9900E6'], \
+['#D1A3FF', '#00A0FF'], \
+['#B20008', '#00A0FF'], \
+['#00A0FF', '#B20008'], \
+['#FF2B34', '#00A0FF'], \
+['#00A0FF', '#FF2B34'], \
+['#FFADCE', '#00A0FF'], \
+['#9A5200', '#00A0FF'], \
+['#00A0FF', '#9A5200'], \
+['#FF8F00', '#00A0FF'], \
+['#00A0FF', '#FF8F00'], \
+['#FFC169', '#00A0FF'], \
+['#807500', '#00A0FF'], \
+['#00A0FF', '#807500'], \
+['#BE9E00', '#00A0FF'], \
+['#00A0FF', '#BE9E00'], \
+['#F8E800', '#00A0FF'], \
+['#008009', '#00A0FF'], \
+['#00A0FF', '#008009'], \
+['#00B20D', '#00A0FF'], \
+['#00A0FF', '#00B20D'], \
+['#8BFF7A', '#00A0FF'], \
+['#5E008C', '#AC32FF'], \
+['#AC32FF', '#5E008C'], \
+['#7F00BF', '#AC32FF'], \
+['#AC32FF', '#7F00BF'], \
+['#D1A3FF', '#AC32FF'], \
+['#B20008', '#AC32FF'], \
+['#AC32FF', '#B20008'], \
+['#FF2B34', '#AC32FF'], \
+['#AC32FF', '#FF2B34'], \
+['#FFADCE', '#AC32FF'], \
+['#9A5200', '#AC32FF'], \
+['#AC32FF', '#9A5200'], \
+['#FF8F00', '#AC32FF'], \
+['#AC32FF', '#FF8F00'], \
+['#FFC169', '#AC32FF'], \
+['#807500', '#AC32FF'], \
+['#AC32FF', '#807500'], \
+['#BE9E00', '#AC32FF'], \
+['#AC32FF', '#BE9E00'], \
+['#F8E800', '#AC32FF'], \
+['#008009', '#AC32FF'], \
+['#AC32FF', '#008009'], \
+['#00B20D', '#AC32FF'], \
+['#AC32FF', '#00B20D'], \
+['#8BFF7A', '#AC32FF'], \
+['#00588C', '#AC32FF'], \
+['#AC32FF', '#00588C'], \
+['#005FE4', '#AC32FF'], \
+['#AC32FF', '#005FE4'], \
+['#BCCDFF', '#AC32FF'], \
+]
+
+
+def _parse_string(color_string):
+ if not isinstance(color_string, (str, unicode)):
+ logging.error('Invalid color string: %r', color_string)
+ return None
+
+ if color_string == 'white':
+ return ['#ffffff', '#414141']
+ elif color_string == 'insensitive':
+ return ['#ffffff', '#e2e2e2']
+
+ splitted = color_string.split(',')
+ if len(splitted) == 2:
+ return [splitted[0], splitted[1]]
+ else:
+ return None
+
+
+def is_valid(color_string):
+ return (_parse_string(color_string) != None)
+
+
+class XoColor:
+
+ def __init__(self, color_string=None):
+ if color_string == None:
+ randomize = True
+ elif not is_valid(color_string):
+ logging.debug('Color string is not valid: %s, '
+ 'fallback to default', color_string)
+ client = gconf.client_get_default()
+ color_string = client.get_string('/desktop/sugar/user/color')
+ randomize = False
+ else:
+ randomize = False
+
+ if randomize:
+ n = int(random.random() * (len(colors) - 1))
+ [self.stroke, self.fill] = colors[n]
+ else:
+ [self.stroke, self.fill] = _parse_string(color_string)
+
+ def __cmp__(self, other):
+ if isinstance(other, XoColor):
+ if self.stroke == other.stroke and self.fill == other.fill:
+ return 0
+ return -1
+
+ def get_stroke_color(self):
+ return self.stroke
+
+ def get_fill_color(self):
+ return self.fill
+
+ def to_string(self):
+ return '%s,%s' % (self.stroke, self.fill)
+
+
+if __name__ == '__main__':
+ import sys
+ import re
+
+ f = open(sys.argv[1], 'r')
+
+ print 'colors = ['
+
+ for line in f.readlines():
+ match = re.match(r'fill: ([A-Z0-9]*) stroke: ([A-Z0-9]*)', line)
+ print "['#%s', '#%s'], \\" % (match.group(2), match.group(1))
+
+ print ']'
+
+ f.close()
diff --git a/gnome_plugins/__init__.py b/gnome_plugins/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gnome_plugins/__init__.py
diff --git a/gnome_plugins/collaboration_plugin.py b/gnome_plugins/collaboration_plugin.py
new file mode 100644
index 0000000..19c084b
--- /dev/null
+++ b/gnome_plugins/collaboration_plugin.py
@@ -0,0 +1,350 @@
+#!/usr/bin/env python
+#Copyright (c) 2011 Walter Bender
+#Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+
+#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.
+
+import sys
+sys.path.append("..")
+import os.path
+
+import dbus
+from gettext import gettext as _
+import gobject
+import gtk
+
+from plugin import Plugin
+
+from util.menubuilder import MenuBuilder
+from util.configfile import ConfigFile
+from util.configwizard import ConfigWizard
+
+import telepathy
+from collaboration.neighborhood import get_neighborhood
+from collaboration.connectionmanager import get_connection_manager
+from collaboration.activity import Activity
+from collaboration import telepathyclient
+from collaboration.tubeconn import TubeConnection
+
+from TurtleArt.tacollaboration import Collaboration
+
+import traceback
+
+CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \
+ 'org.laptop.Telepathy.ActivityProperties'
+
+
+class Collaboration_plugin(Plugin):
+
+ __gsignals__ = {
+ 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ()),
+ 'shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ()),
+ }
+
+ def __init__(self, parent):
+ Plugin.__init__(self)
+
+ self._parent = parent
+ self._neighborhood = None
+ self._title = _('My Turtle Art session')
+ self._bundle_id = "org.laptop.TurtleArt"
+ # This could be hashed from the file path (if resuming)
+ self._activity_id = "1234567"
+ self._nick = ""
+ self._setup_has_been_called = False
+
+ def _setup_config_file(self, config_file_path):
+ self._config_file_path = os.path.join(config_file_path,
+ 'turtleartrc.collab')
+ self._collaboration_config_values = ConfigFile(self._config_file_path)
+ self._valid_config_values = {
+ 'nick': {'type': 'text'},
+ 'account_id': {'type': 'text'},
+ 'password': {'type': 'text'},
+ 'server': {'type': 'text'},
+ 'port': {'type': 'integer'},
+ 'register': {'type': 'boolean'},
+ 'colors': {'type': 'text'}
+ }
+
+ def _connect_cb(self, button):
+ """ Enable connection """
+ self._collaboration_config_values.set_valid_keys(
+ self._valid_config_values)
+ self._collaboration_config_values.connect(
+ 'configuration-loaded', self._connect_to_neighborhood)
+ self._collaboration_config_values.connect(
+ 'configuration-saved', self._connect_to_neighborhood)
+ self._collaboration_config_values.load()
+ self.setup()
+
+ def setup(self):
+ self._collaboration = Collaboration(self.tw, self)
+ self._collaboration.setup()
+ # Do we know if we were successful?
+ self._setup_has_been_called = True
+ # TODO:
+ # use set_sensitive to enable Share and Configuration menuitems
+
+ def set_tw(self, turtleart_window):
+ self.tw = turtleart_window
+ self.tw.nick = self._get_nick()
+ self._setup_config_file(self._parent.get_config_home())
+
+ def get_menu(self):
+ menu = gtk.Menu()
+
+ MenuBuilder.make_menu_item(menu, _('Enable collaboration'),
+ self._connect_cb)
+
+ self._activities_submenu = gtk.Menu()
+ activities_menu = MenuBuilder.make_sub_menu(self._activities_submenu,
+ _('Activities'))
+ menu.append(activities_menu)
+
+ self._buddies_submenu = gtk.Menu()
+ buddies_menu = MenuBuilder.make_sub_menu(self._buddies_submenu,
+ _('Buddies'))
+ menu.append(buddies_menu)
+
+ MenuBuilder.make_menu_item(menu, _('Share'), self._share_cb)
+ MenuBuilder.make_menu_item(menu, _('Configuration'),
+ self._config_neighborhood_cb)
+
+ neighborhood_menu = MenuBuilder.make_sub_menu(menu, _('Neighborhood'))
+
+ return neighborhood_menu
+
+ def get_colors(self):
+ return self._colors
+
+ def _get_nick(self):
+ return self._nick
+
+ def _get_activity_id(self):
+ return self._activity_id
+
+ def _get_bundle_id(self):
+ return self._bundle_id
+
+ def _get_title(self):
+ return self._title
+
+ def _connect_to_neighborhood(self, config_file_obj):
+ if self._neighborhood is not None:
+ return
+
+ params = {}
+ params['nickname'] = self._collaboration_config_values.get('nick')
+ params['account_id'] = self._collaboration_config_values.get(
+ 'account_id')
+ params['server'] = self._collaboration_config_values.get('server')
+ params['port'] = self._collaboration_config_values.get('port')
+ params['password'] = self._collaboration_config_values.get('password')
+ params['register'] = self._collaboration_config_values.get('register')
+ if params['server'] == '':
+ raise RuntimeError('Invalid server address')
+
+ self._nick = self._collaboration_config_values.get('nick')
+ # Tell the parent activity that the nick may have changed
+ self._parent.nick_changed(self._nick)
+
+ self._colors = self._collaboration_config_values.get('colors')
+ # Tell the parent activity that the colors may have changed
+ self._parent.color_changed(self._colors)
+
+ self._activities = {}
+ self._buddies = {}
+
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ self._client_handler = telepathyclient.get_instance()
+ if self._client_handler == None:
+ raise RuntimeError('Telepathy client unavailable')
+ self._neighborhood = get_neighborhood(params)
+ self._neighborhood.connect('activity-added', self._activity_added_cb)
+ self._neighborhood.connect('activity-removed',
+ self._activity_removed_cb)
+ self._neighborhood.connect('buddy-added', self._buddy_added_cb)
+ self._neighborhood.connect('buddy-removed', self._buddy_removed_cb)
+
+ # TODO:
+ # - show nick of sharer
+ # - show icon with color of sharer
+ def _activity_added_cb(self, model, activity_model):
+ self._activities[activity_model.props.name] = activity_model
+ self._recreate_available_activities_menu()
+
+ def _activity_removed_cb(self, model, activity_model):
+ try:
+ self._activities.pop(activity_model.props.name)
+ except:
+ print 'Failed to remove activity %s' % activity_model.props.name
+
+ self._recreate_available_activities_menu()
+
+ def _buddy_added_cb(self, activity, buddy):
+ self._buddies[buddy.get_key()] = buddy
+ self._recreate_available_buddies_menu()
+
+ def _buddy_removed_cb(self, activity, buddy):
+ try:
+ self._buddies.pop(buddy.get_key())
+ except:
+ print "Couldn't remove buddy %s" % buddy.get_key()
+ self._recreate_available_buddies_menu()
+
+ # TODO: we should have a list of available actions over
+ # a given buddy. I.e.: a) chat with him b) make friend
+ # c) invite to current activity
+ #
+ def _recreate_available_buddies_menu(self):
+ for child in self._buddies_submenu.get_children():
+ self._buddies_submenu.remove(child)
+
+ for buddy in self._buddies.values():
+ key = buddy.get_key()
+ if key is None:
+ key = ''
+ n = buddy.get_nick() + '|' + key[0:15]
+ MenuBuilder.make_menu_item(self._buddies_submenu, n,
+ self._buddy_actions_cb, buddy)
+
+ def _buddy_actions_cb(self, widget, buddy):
+ print 'do something with %s' % buddy.get_nick()
+
+ # TODO:
+ # we need an extra menu branch with a) 'Join' button b) List of buddies
+ def _recreate_available_activities_menu(self):
+ for child in self._activities_submenu.get_children():
+ self._activities_submenu.remove(child)
+
+ for activity in self._activities.values():
+ n = activity.props.name
+ MenuBuilder.make_menu_item(self._activities_submenu, n,
+ self._join_activity_cb, activity)
+
+ def _join_activity_cb(self, widget, activity):
+ print 'Lets try to join...'
+
+ connection_manager = get_connection_manager()
+ account_path, connection = \
+ connection_manager.get_preferred_connection()
+ if connection is None:
+ print('No active connection available')
+ return
+
+ properties = {}
+ properties['id'] = activity.activity_id
+ properties['color'] = activity.get_color()
+ print 'room handle according to activity %s' % activity.room_handle
+ properties['private'] = True
+
+ try:
+ room_handle = connection.GetActivity(activity.activity_id,
+ dbus_interface=CONNECTION_INTERFACE_ACTIVITY_PROPERTIES)
+ print('room_handle = %s' % str(room_handle))
+ self._joined_activity = Activity(
+ account_path, connection, room_handle, properties=properties)
+ # FIXME: this should be unified, no need to keep 2 references
+ self._shared_activity = self._joined_activity
+ except:
+ traceback.print_exc(file=sys.stdout)
+
+ if self._joined_activity.props.joined:
+ raise RuntimeError('Activity %s is already shared.' %
+ activity.activity_id)
+
+ _join_id = self._joined_activity.connect('joined', self.__joined_cb)
+ self._joined_activity.join()
+
+ def __joined_cb(self, activity, success, err):
+ print "We've joined an activity"
+ self.emit('joined')
+
+ def _config_neighborhood_cb(self, widget):
+ if not self._setup_has_been_called:
+ return
+ config_w = ConfigWizard(self._config_file_path)
+ config_items = [
+ {'item_label': _('Nickname'), 'item_type': 'text',
+ 'item_name': 'nick'},
+ {'item_label': _('Account ID'), 'item_type': 'text',
+ 'item_name': 'account_id'},
+ {'item_label': _('Server'), 'item_type': 'text',
+ 'item_name': 'server'},
+ {'item_label': _('Port'), 'item_type': 'text',
+ 'item_name': 'port'},
+ {'item_label': _('Password'), 'item_type': 'text',
+ 'item_name': 'password'},
+ {'item_label': _('Register'), 'item_type': 'boolean',
+ 'item_name': 'register'},
+ {'item_label': _('Colors'), 'item_type': 'text',
+ 'item_name': 'colors'}
+ ]
+ config_w.set_config_items(config_items)
+ config_w.set_config_file_obj(self._collaboration_config_values)
+ config_w.show()
+
+ def _share_cb(self, button):
+ if not self._setup_has_been_called:
+ return
+ properties = {}
+ properties['id'] = self._get_activity_id()
+ properties['type'] = self._get_bundle_id()
+ properties['name'] = self._get_title()
+ properties['color'] = self.get_colors()
+ properties['private'] = False
+
+ connection_manager = get_connection_manager()
+ account_path, connection = \
+ connection_manager.get_preferred_connection()
+
+ if connection is None:
+ print('No active connection available')
+ return
+
+ try:
+ self._parent._shared_activity = Activity(account_path,
+ connection,
+ properties=properties)
+ # FIXME: this should be unified, no need to keep 2 references
+ self._shared_activity = self._parent._shared_activity
+ except:
+ traceback.print_exc(file=sys.stdout)
+
+ if self._parent._shared_parent.props.joined:
+ raise RuntimeError('Activity %s is already shared.' %
+ self._parent._get_activity_id())
+
+ self._parent._shared_parent.share(self.__share_activity_cb,
+ self.__share_activity_error_cb)
+
+ def __share_activity_cb(self, activity):
+ """Finish sharing the activity"""
+ self.emit('shared')
+
+ def __share_activity_error_cb(self, activity, error):
+ """Notify with GObject event of unsuccessful sharing of activity"""
+ print '%s got error: %s' % (activity, error)
+
+if __name__ == '__main__':
+ print 'testing collaboration'
diff --git a/gnome_plugins/plugin.py b/gnome_plugins/plugin.py
new file mode 100644
index 0000000..590c9bc
--- /dev/null
+++ b/gnome_plugins/plugin.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+#Copyright (c) 2011 Walter Bender
+#Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+
+#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.
+
+import gobject
+
+
+class Plugin(gobject.GObject):
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ def get_menu(self):
+ raise RuntimeError("You need to define get__menu for your plugin.")
+
+ def set_tw(self, turtle_window=None):
+ raise RuntimeError("You need to define set_tw for your plugin.")
diff --git a/gnome_plugins/uploader_plugin.py b/gnome_plugins/uploader_plugin.py
new file mode 100644
index 0000000..06224fa
--- /dev/null
+++ b/gnome_plugins/uploader_plugin.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python
+#Copyright (c) 2011 Walter Bender
+#Copyright (c) 2010 Jamie Boisture
+#Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+
+#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.
+
+#!/usr/bin/python
+
+try:
+ import pycurl
+ import xmlrpclib
+ _UPLOAD_AVAILABLE = True
+except ImportError, e:
+ print "Import Error: %s. Project upload is disabled." % (e)
+ _UPLOAD_AVAILABLE = False
+
+import os
+import gtk
+
+from plugin import Plugin
+from util.menubuilder import MenuBuilder
+
+from gettext import gettext as _
+
+
+class Uploader_plugin(Plugin):
+ MAX_FILE_SIZE = 950000
+ UPLOAD_SERVER = 'http://turtleartsite.appspot.com'
+
+ def __init__(self, parent, upload_server=None, max_file_size=None):
+ self._parent = parent
+ self.uploading = False
+
+ if upload_server is None:
+ self._upload_server = self.UPLOAD_SERVER
+
+ if max_file_size is None:
+ self._max_file_size = self.MAX_FILE_SIZE
+ else:
+ self._max_file_size = max_file_size
+
+ def set_tw(self, turtleart_window):
+ self.tw = turtleart_window
+
+ def get_menu(self):
+ menu = gtk.Menu()
+ MenuBuilder.make_menu_item(menu, _('Upload to Web'),
+ self.do_upload_to_web)
+ upload_menu = MenuBuilder.make_sub_menu(menu, _('Upload'))
+ return upload_menu
+
+ def enabled(self):
+ return _UPLOAD_AVAILABLE
+
+ def do_upload_to_web(self, widget=None):
+ if self.uploading:
+ return
+
+ self.uploading = False
+ self.pop_up = gtk.Window()
+ self.pop_up.set_default_size(600, 400)
+ self.pop_up.connect('delete_event', self._stop_uploading)
+ table = gtk.Table(8, 1, False)
+ self.pop_up.add(table)
+
+ login_label = gtk.Label(_('You must have an account at \
+http://turtleartsite.sugarlabs.org to upload your project.'))
+ table.attach(login_label, 0, 1, 0, 1)
+ self.login_message = gtk.Label('')
+ table.attach(self.login_message, 0, 1, 1, 2)
+
+ self.Hbox1 = gtk.HBox()
+ table.attach(self.Hbox1, 0, 1, 2, 3, xpadding=5, ypadding=3)
+ self.username_entry = gtk.Entry()
+ username_label = gtk.Label(_('Username:') + ' ')
+ username_label.set_size_request(150, 25)
+ username_label.set_alignment(1.0, 0.5)
+ self.username_entry.set_size_request(450, 25)
+ self.Hbox1.add(username_label)
+ self.Hbox1.add(self.username_entry)
+
+ self.Hbox2 = gtk.HBox()
+ table.attach(self.Hbox2, 0, 1, 3, 4, xpadding=5, ypadding=3)
+ self.password_entry = gtk.Entry()
+ password_label = gtk.Label(_('Password:') + ' ')
+ self.password_entry.set_visibility(False)
+ password_label.set_size_request(150, 25)
+ password_label.set_alignment(1.0, 0.5)
+ self.password_entry.set_size_request(450, 25)
+ self.Hbox2.add(password_label)
+ self.Hbox2.add(self.password_entry)
+
+ self.Hbox3 = gtk.HBox()
+ table.attach(self.Hbox3, 0, 1, 4, 5, xpadding=5, ypadding=3)
+ self.title_entry = gtk.Entry()
+ title_label = gtk.Label(_('Title:') + ' ')
+ title_label.set_size_request(150, 25)
+ title_label.set_alignment(1.0, 0.5)
+ self.title_entry.set_size_request(450, 25)
+ self.Hbox3.add(title_label)
+ self.Hbox3.add(self.title_entry)
+
+ self.Hbox4 = gtk.HBox()
+ table.attach(self.Hbox4, 0, 1, 5, 6, xpadding=5, ypadding=3)
+ self.description_entry = gtk.TextView()
+ description_label = gtk.Label(_('Description:') + ' ')
+ description_label.set_size_request(150, 25)
+ description_label.set_alignment(1.0, 0.5)
+ self.description_entry.set_wrap_mode(gtk.WRAP_WORD)
+ self.description_entry.set_size_request(450, 50)
+ self.Hbox4.add(description_label)
+ self.Hbox4.add(self.description_entry)
+
+ self.Hbox5 = gtk.HBox()
+ table.attach(self.Hbox5, 0, 1, 6, 7, xpadding=5, ypadding=3)
+ self.submit_button = gtk.Button(_('Submit to Web'))
+ self.submit_button.set_size_request(300, 25)
+ self.submit_button.connect('pressed', self._do_remote_logon)
+ self.Hbox5.add(self.submit_button)
+ self.cancel_button = gtk.Button(_('Cancel'))
+ self.cancel_button.set_size_request(300, 25)
+ self.cancel_button.connect('pressed', self._stop_uploading)
+ self.Hbox5.add(self.cancel_button)
+
+ self.pop_up.show_all()
+
+ def _stop_uploading(self, widget, event=None):
+ """ Hide the popup when the upload is complte """
+ self.uploading = False
+ self.pop_up.hide()
+
+ def _do_remote_logon(self, widget):
+ """ Log into the upload server """
+ username = self.username_entry.get_text()
+ password = self.password_entry.get_text()
+ server = xmlrpclib.ServerProxy(self._upload_server + '/call/xmlrpc')
+ logged_in = server.login_remote(username, password)
+ if logged_in:
+ upload_key = logged_in
+ self._do_submit_to_web(upload_key)
+ else:
+ self.login_message.set_text(_('Login failed'))
+
+ def _do_submit_to_web(self, key):
+ """ Submit project to the server """
+ title = self.title_entry.get_text()
+ description = self.description_entry.get_buffer().get_text(
+ *self.description_entry.get_buffer().get_bounds())
+ tafile, imagefile = self.tw.save_for_upload(title)
+
+ # Set a maximum file size for image to be uploaded.
+ if int(os.path.getsize(imagefile)) > self._max_file_size:
+ import Image
+ while int(os.path.getsize(imagefile)) > self._max_file_size:
+ big_file = Image.open(imagefile)
+ smaller_file = big_file.resize(int(0.9 * big_file.size[0]),
+ int(0.9 * big_file.size[1]),
+ Image.ANTIALIAS)
+ smaller_file.save(imagefile, quality=100)
+
+ c = pycurl.Curl()
+ c.setopt(c.POST, 1)
+ c.setopt(c.FOLLOWLOCATION, 1)
+ c.setopt(c.URL, self._upload_server + '/upload')
+ c.setopt(c.HTTPHEADER, ["Expect:"])
+ c.setopt(c.HTTPPOST, [('file', (c.FORM_FILE, tafile)),
+ ('newimage', (c.FORM_FILE, imagefile)),
+ ('small_image', (c.FORM_FILE, imagefile)),
+ ('title', title),
+ ('description', description),
+ ('upload_key', key), ('_formname',
+ 'image_create')])
+ c.perform()
+ error_code = c.getinfo(c.HTTP_CODE)
+ c.close
+ os.remove(imagefile)
+ os.remove(tafile)
+ if error_code == 400:
+ self.login_message.set_text(_('Failed to upload!'))
+ else:
+ self.pop_up.hide()
+ self.uploading = False
+
+if __name__ == "__main__":
+ # TODO: create test data...
+ u = Uploader(None)
+ if u.enabled():
+ print "Uploader is enabled... trying to upload"
+ u.do_upload_to_web()
+ gtk.main()
+ else:
+ print "Uploader is not enabled... exiting"
diff --git a/icons/blocksoff.svg b/icons/blocksoff.svg
new file mode 100644
index 0000000..63efb3c
--- /dev/null
+++ b/icons/blocksoff.svg
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <g
+ id="g3789">
+ <path
+ d="m 15.719636,31.331478 0.114372,5.261133 11.437247,6.290486 L 27.5,29.387146"
+ id="path2463"
+ style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 27.728744,29.501519 0.05719,13.381578 11.723178,-7.548583 0,-4.689272 -5.947368,3.545548"
+ id="path2465"
+ style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10.001012,27.328441 5.604251,-5.146761 -5.832996,-2.973684 12.123482,-7.091093 5.718623,3.316801 5.489879,-3.088056 12.123482,7.319838 -5.832996,2.401822 5.489878,5.032388 -11.322874,7.205466 -5.947369,-5.261134 -6.290485,5.261134 -11.322875,-6.976721 z"
+ id="path2459"
+ style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 16.40587,21.952935 27.156883,15.204959 38.937247,22.067308 27.614372,28.815283 16.40587,21.952935 z"
+ id="path2461"
+ style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/icons/blockson.svg b/icons/blockson.svg
new file mode 100644
index 0000000..4435cc2
--- /dev/null
+++ b/icons/blockson.svg
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs4" />
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <g
+ id="g3788">
+ <path
+ d="m 15.719636,31.331478 0.114372,5.261133 11.437247,6.290486 L 27.5,29.387146"
+ id="path2463"
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#804000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 27.728744,29.501519 0.05719,13.381578 11.723178,-7.548583 0,-4.689272 -5.947368,3.545548"
+ id="path2465"
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#804000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10.001012,27.328441 5.604251,-5.146761 -5.832996,-2.973684 12.123482,-7.091093 5.718623,3.316801 5.489879,-3.088056 12.123482,7.319838 -5.832996,2.401822 5.489878,5.032388 -11.322874,7.205466 -5.947369,-5.261134 -6.290485,5.261134 -11.322875,-6.976721 z"
+ id="path2459"
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#804000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 16.40587,21.952935 27.156883,15.204959 38.937247,22.067308 27.614372,28.815283 16.40587,21.952935 z"
+ id="path2461"
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#804000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/icons/colorsoff.svg b/icons/colorsoff.svg
new file mode 100644
index 0000000..3991df4
--- /dev/null
+++ b/icons/colorsoff.svg
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <g
+ transform="translate(-4.9972534e-4,0)"
+ id="toolbar_x5F_colors"
+ style="display:block">
+ <g
+ id="g3067">
+ <circle
+ cx="15.63"
+ cy="39.5"
+ r="10"
+ id="circle3069"
+ style="fill:#d20000;stroke:#ffffff;stroke-width:2.25" />
+ <circle
+ cx="39.834"
+ cy="39.5"
+ r="10"
+ id="circle3071"
+ style="fill:#0010d1;stroke:#ffffff;stroke-width:2.25" />
+ <circle
+ cx="15.167"
+ cy="15.5"
+ r="10"
+ id="circle3073"
+ style="fill:#f7e100;stroke:#ffffff;stroke-width:2.25" />
+ <circle
+ cx="39.666"
+ cy="15.5"
+ r="10"
+ id="circle3075"
+ style="stroke:#ffffff;stroke-width:2.25" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/colorson.svg b/icons/colorson.svg
new file mode 100644
index 0000000..42ce7c7
--- /dev/null
+++ b/icons/colorson.svg
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata3212">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <g
+ transform="translate(-4.9972534e-4,0)"
+ id="toolbar_x5F_colors"
+ style="display:block">
+ <g
+ id="g3067">
+ <circle
+ cx="15.63"
+ cy="39.5"
+ r="10"
+ id="circle3069"
+ style="fill:#d20000;stroke:#ffffff;stroke-width:2.25" />
+ <circle
+ cx="39.834"
+ cy="39.5"
+ r="10"
+ id="circle3071"
+ style="fill:#0010d1;stroke:#ffffff;stroke-width:2.25" />
+ <circle
+ cx="15.167"
+ cy="15.5"
+ r="10"
+ id="circle3073"
+ style="fill:#f7e100;stroke:#ffffff;stroke-width:2.25" />
+ <circle
+ cx="39.666"
+ cy="15.5"
+ r="10"
+ id="circle3075"
+ style="stroke:#ffffff;stroke-width:2.25" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/contract-coordinates.svg b/icons/contract-coordinates.svg
new file mode 100644
index 0000000..1f704f3
--- /dev/null
+++ b/icons/contract-coordinates.svg
@@ -0,0 +1,79 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2384">
+ <defs
+ id="defs2386" />
+ <rect
+ width="50"
+ height="50"
+ x="2.5"
+ y="2.5"
+ id="rect2394"
+ style="fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 27.333333,1 C 28.083333,50.75 27.333333,54 27.333333,54"
+ id="path3168"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 54,27.333333 C 4.2499997,28.083333 1,27.333333 1,27.333333"
+ id="path3170"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <g
+ transform="matrix(-1,0,0,-1,72.75,37.25)"
+ id="g3161"
+ style="stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="M 28,27 L 37,18"
+ id="path2387"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 30,17 L 38,17 L 38,25"
+ id="path3159"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(-1,0,0,1,72.75,18.25)"
+ id="g3167"
+ style="stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="M 28,27 L 37,18"
+ id="path3169"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 30,17 L 38,17 L 38,25"
+ id="path3171"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(1,0,0,-1,-18.25,37.25)"
+ id="g3173"
+ style="stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="M 28,27 L 37,18"
+ id="path3175"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 30,17 L 38,17 L 38,25"
+ id="path3177"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ transform="translate(-18.25,18.25)"
+ id="g3179"
+ style="stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="M 28,27 L 37,18"
+ id="path3181"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 30,17 L 38,17 L 38,25"
+ id="path3183"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/icons/debugoff.svg b/icons/debugoff.svg
new file mode 100644
index 0000000..32ff193
--- /dev/null
+++ b/icons/debugoff.svg
@@ -0,0 +1,141 @@
+<?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.0"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs4" />
+ ` <g
+ transform="matrix(0.81818179,0,0,0.81818179,2.9325954e-7,1.8913349e-6)"
+ id="g2941">
+ <g
+ transform="matrix(1.5,0,0,1.5,-13.078338,-13.158384)"
+ id="g2418">
+ <path
+ d="m 27.377856,20.343519 c 0.943144,-1.32305 2.909066,-2.23143 3.443893,-2.052418 0.816335,0.273235 2.634387,1.285134 2.828305,1.870614 0.363444,1.097313 -0.440961,2.212871 -0.661443,3.319307"
+ id="path3252"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 31.312226,33.70644 c -3.075459,5.326852 -8.151812,8.153707 -11.338352,6.313958 -3.18654,-1.83975 -3.276587,-7.649428 -0.201128,-12.97628 3.07546,-5.326852 8.151812,-8.153707 11.338352,-6.313958 3.18654,1.83975 3.276588,7.649428 0.201128,12.97628 l 0,0 z"
+ id="path3197"
+ style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:1.51091182;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 30.640788,20.96543 c -3.356038,6.044559 -3.301174,5.949533 -3.301174,5.949533"
+ id="path3199"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 23.640267,22.485322 c 0.955579,1.892858 2.334494,3.24269 3.831245,4.497123 0.02734,0.02291 3.988592,1.409612 5.914011,1.031489"
+ id="path3201"
+ style="fill:none;stroke:#ffffff;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 22.8141,25.484531 c 2.380046,2.460708 2.406938,2.476234 2.406938,2.476234"
+ id="path3203"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 21.785315,26.831706 c 2.380046,2.460709 2.551072,2.661318 2.551072,2.661318"
+ id="path3207"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 20.834693,28.29192 c 2.380046,2.460708 2.617044,2.733363 2.617044,2.733363"
+ id="path3209"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 20.016015,29.896225 c 2.380045,2.460709 2.551071,2.661318 2.551071,2.661318"
+ id="path3211"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.370877,31.63468 c 2.380046,2.460709 2.311559,2.455123 2.311559,2.455123"
+ id="path3213"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.111902,33.697954 c 2.252745,2.556991 1.685883,1.92411 1.685883,1.92411"
+ id="path3215"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.133652,36.330772 c 0.569377,0.566421 0.779483,0.823549 0.779483,0.823549"
+ id="path3217"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 30.786085,30.087159 C 27.465026,29.256333 27.438134,29.240807 27.438134,29.240807"
+ id="path3230"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 30.133789,31.6517 C 26.81273,30.820874 26.553484,30.773066 26.553484,30.773066"
+ id="path3232"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 29.344518,33.20507 C 26.023459,32.374244 25.668834,32.305325 25.668834,32.305325"
+ id="path3234"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 28.364488,34.716219 C 25.043429,33.885393 24.784183,33.837585 24.784183,33.837585"
+ id="path3236"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 27.18151,36.144152 C 23.860451,35.313326 23.899532,35.369845 23.899532,35.369845"
+ id="path3238"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 25.524151,37.400068 c -3.340792,-0.672439 -2.50927,-0.497963 -2.50927,-0.497963"
+ id="path3240"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 23.233189,38.697641 C 22.457965,38.487756 22.130232,38.434363 22.130232,38.434363"
+ id="path3242"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 38.499043,20.363658 c -0.698249,1.209401 -2.299567,1.592097 -3.576644,0.854776 -1.277077,-0.73732 -1.746312,-2.315451 -1.048064,-3.524852 0.698249,-1.209402 2.299567,-1.592098 3.576644,-0.854777 1.277077,0.73732 1.746312,2.315451 1.048064,3.524853 z"
+ id="path3244"
+ style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.84691685;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 36.397626,17.843099 c -0.197275,0.341691 -0.649694,0.449815 -1.010506,0.2415 -0.360812,-0.208315 -0.493384,-0.654183 -0.296109,-0.995875 0.197276,-0.341691 0.649695,-0.449814 1.010507,-0.241499 0.360812,0.208315 0.493384,0.654183 0.296108,0.995874 z"
+ id="path3246"
+ style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.79813904;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 32.748117,16.754734 c -0.698248,1.209401 -2.299567,1.592097 -3.576644,0.854776 -1.277077,-0.73732 -1.746311,-2.315451 -1.048063,-3.524852 0.698249,-1.209402 2.299567,-1.592098 3.576644,-0.854777 1.277077,0.73732 1.746312,2.315451 1.048063,3.524853 z"
+ id="path3248"
+ style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.84691685;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 30.781161,14.311805 c -0.197275,0.341691 -0.649694,0.449815 -1.010506,0.2415 -0.360813,-0.208315 -0.493385,-0.654183 -0.296109,-0.995875 0.197275,-0.341691 0.649695,-0.449814 1.010507,-0.241499 0.360812,0.208315 0.493384,0.654183 0.296108,0.995874 z"
+ id="path3250"
+ style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.79813904;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 32.6771,18.762402 c 1.915624,-3.421466 5.072352,-4.541771 5.072352,-4.541771 l 0,0"
+ id="path3254"
+ style="fill:none;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 32.234792,18.337256 c 2.005265,-3.369712 1.397113,-6.663671 1.397113,-6.663671 l 0,0"
+ id="path3258"
+ style="fill:none;stroke:#ffffff;stroke-width:0.75545591;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 26.988213,20.231746 L 25.8722,15.229742 22.062204,17.274526"
+ id="path3260"
+ style="fill:none;stroke:#ffffff;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 33.361036,23.684723 l 3.584614,-0.929858 2.882681,3.70167"
+ id="path3262"
+ style="fill:none;stroke:#ffffff;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 23.851158,22.042526 l -1.097622,-3.067217 -8.164934,2.756268"
+ id="path3264"
+ style="fill:none;stroke:#ffffff;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 33.777334,27.207476 l 3.47402,-0.42778 0.497902,7.4182"
+ id="path3266"
+ style="fill:none;stroke:#ffffff;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="M 20.388485,25.668558 L 19.349188,23.00868 12.485021,29.515384"
+ id="path3268"
+ style="fill:none;stroke:#ffffff;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 32.410841,31.851473 l 2.920057,-0.91739 -2.027798,9.412169"
+ id="path3270"
+ style="fill:none;stroke:#ffffff;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/debugon.svg b/icons/debugon.svg
new file mode 100644
index 0000000..6232a99
--- /dev/null
+++ b/icons/debugon.svg
@@ -0,0 +1,146 @@
+<?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.0"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs4" />
+ <g
+ transform="matrix(0.81818179,0,0,0.81818179,2.9325954e-7,1.8913349e-6)"
+ id="g2886">
+ <path
+ d="m 12.3583,20.48583 c 0,6.948279 -1.644946,12.580972 -3.6740891,12.580972 -2.0291434,0 -3.6740891,-5.632693 -3.6740891,-12.580972 0,-6.948279 1.6449457,-12.5809723 3.6740891,-12.5809723 2.0291431,0 3.6740891,5.6326933 3.6740891,12.5809723 l 0,0 z"
+ transform="matrix(7.4848487,0,0,2.1858407,-37.500004,-17.278762)"
+ id="path3188"
+ style="fill:#00ff00;fill-opacity:1;stroke:none" />
+ <g
+ transform="matrix(1.5,0,0,1.5,-13.078338,-13.158384)"
+ id="g3188">
+ <path
+ d="m 27.377856,20.343519 c 0.943144,-1.32305 2.909066,-2.23143 3.443893,-2.052418 0.816335,0.273235 2.634387,1.285134 2.828305,1.870614 0.363444,1.097313 -0.440961,2.212871 -0.661443,3.319307"
+ id="path3252"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 31.312226,33.70644 c -3.075459,5.326852 -8.151812,8.153707 -11.338352,6.313958 -3.18654,-1.83975 -3.276587,-7.649428 -0.201128,-12.97628 3.07546,-5.326852 8.151812,-8.153707 11.338352,-6.313958 3.18654,1.83975 3.276588,7.649428 0.201128,12.97628 l 0,0 z"
+ id="path3197"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.51091182;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 30.640788,20.96543 c -3.356038,6.044559 -3.301174,5.949533 -3.301174,5.949533"
+ id="path3199"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 23.640267,22.485322 c 0.955579,1.892858 2.334494,3.24269 3.831245,4.497123 0.02734,0.02291 3.988592,1.409612 5.914011,1.031489"
+ id="path3201"
+ style="fill:none;stroke:#000000;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 22.8141,25.484531 c 2.380046,2.460708 2.406938,2.476234 2.406938,2.476234"
+ id="path3203"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 21.785315,26.831706 c 2.380046,2.460709 2.551072,2.661318 2.551072,2.661318"
+ id="path3207"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 20.834693,28.29192 c 2.380046,2.460708 2.617044,2.733363 2.617044,2.733363"
+ id="path3209"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 20.016015,29.896225 c 2.380045,2.460709 2.551071,2.661318 2.551071,2.661318"
+ id="path3211"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.370877,31.63468 c 2.380046,2.460709 2.311559,2.455123 2.311559,2.455123"
+ id="path3213"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.111902,33.697954 c 2.252745,2.556991 1.685883,1.92411 1.685883,1.92411"
+ id="path3215"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.133652,36.330772 c 0.569377,0.566421 0.779483,0.823549 0.779483,0.823549"
+ id="path3217"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 30.786085,30.087159 C 27.465026,29.256333 27.438134,29.240807 27.438134,29.240807"
+ id="path3230"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 30.133789,31.6517 C 26.81273,30.820874 26.553484,30.773066 26.553484,30.773066"
+ id="path3232"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 29.344518,33.20507 C 26.023459,32.374244 25.668834,32.305325 25.668834,32.305325"
+ id="path3234"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 28.364488,34.716219 C 25.043429,33.885393 24.784183,33.837585 24.784183,33.837585"
+ id="path3236"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 27.18151,36.144152 C 23.860451,35.313326 23.899532,35.369845 23.899532,35.369845"
+ id="path3238"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 25.524151,37.400068 c -3.340792,-0.672439 -2.50927,-0.497963 -2.50927,-0.497963"
+ id="path3240"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 23.233189,38.697641 C 22.457965,38.487756 22.130232,38.434363 22.130232,38.434363"
+ id="path3242"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.75545591;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 38.499043,20.363658 c -0.698249,1.209401 -2.299567,1.592097 -3.576644,0.854776 -1.277077,-0.73732 -1.746312,-2.315451 -1.048064,-3.524852 0.698249,-1.209402 2.299567,-1.592098 3.576644,-0.854777 1.277077,0.73732 1.746312,2.315451 1.048064,3.524853 z"
+ id="path3244"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.84691685;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 36.397626,17.843099 c -0.197275,0.341691 -0.649694,0.449815 -1.010506,0.2415 -0.360812,-0.208315 -0.493384,-0.654183 -0.296109,-0.995875 0.197276,-0.341691 0.649695,-0.449814 1.010507,-0.241499 0.360812,0.208315 0.493384,0.654183 0.296108,0.995874 z"
+ id="path3246"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.79813904;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 32.748117,16.754734 c -0.698248,1.209401 -2.299567,1.592097 -3.576644,0.854776 -1.277077,-0.73732 -1.746311,-2.315451 -1.048063,-3.524852 0.698249,-1.209402 2.299567,-1.592098 3.576644,-0.854777 1.277077,0.73732 1.746312,2.315451 1.048063,3.524853 z"
+ id="path3248"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.84691685;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 30.781161,14.311805 c -0.197275,0.341691 -0.649694,0.449815 -1.010506,0.2415 -0.360813,-0.208315 -0.493385,-0.654183 -0.296109,-0.995875 0.197275,-0.341691 0.649695,-0.449814 1.010507,-0.241499 0.360812,0.208315 0.493384,0.654183 0.296108,0.995874 z"
+ id="path3250"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.79813904;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 32.6771,18.762402 c 1.915624,-3.421466 5.072352,-4.541771 5.072352,-4.541771 l 0,0"
+ id="path3254"
+ style="fill:none;stroke:#000000;stroke-width:0.75545591;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 32.234792,18.337256 c 2.005265,-3.369712 1.397113,-6.663671 1.397113,-6.663671 l 0,0"
+ id="path3258"
+ style="fill:none;stroke:#000000;stroke-width:0.75545591;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 26.988213,20.231746 L 25.8722,15.229742 22.062204,17.274526"
+ id="path3260"
+ style="fill:none;stroke:#000000;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 33.361036,23.684723 l 3.584614,-0.929858 2.882681,3.70167"
+ id="path3262"
+ style="fill:none;stroke:#000000;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 23.851158,22.042526 l -1.097622,-3.067217 -8.164934,2.756268"
+ id="path3264"
+ style="fill:none;stroke:#000000;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 33.777334,27.207476 l 3.47402,-0.42778 0.497902,7.4182"
+ id="path3266"
+ style="fill:none;stroke:#000000;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="M 20.388485,25.668558 L 19.349188,23.00868 12.485021,29.515384"
+ id="path3268"
+ style="fill:none;stroke:#000000;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 32.410841,31.851473 l 2.920057,-0.91739 -2.027798,9.412169"
+ id="path3270"
+ style="fill:none;stroke:#000000;stroke-width:1.51091182px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/eraseroff.svg b/icons/eraseroff.svg
new file mode 100644
index 0000000..c0d739d
--- /dev/null
+++ b/icons/eraseroff.svg
@@ -0,0 +1,33 @@
+<?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.0"
+ width="50"
+ height="43"
+ id="svg2">
+ <defs
+ id="defs5" />
+` <path
+ d="m 36.61535,26.125 l 0,-8.375 12.5,-13.5 -1.5,10.5 -11,11.375 z"
+ id="path2394"
+ style="fill:#c44000;fill-opacity:1;stroke:#a00000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 15.61535,17.75 l 13.5,-13.5 20,0 -12.5,13.5 -21,0 z"
+ id="path2390"
+ style="fill:#c44000;fill-opacity:1;stroke:#a00000;stroke-width:1.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 23.416068,38.75 l 2.692998,-10 10.506284,-11 0,8.375 -13.199282,12.625 z"
+ id="path2392"
+ style="fill:#c4a000;fill-opacity:1;stroke:#a00000;stroke-width:1.5;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 4.5650808,28.75 l 11.0502692,-11 21,0 -10.86535,11 -21.1849192,0 z"
+ id="path2388"
+ style="fill:#c4a000;fill-opacity:1;stroke:#a00000;stroke-width:1.5;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 0.88464991,38.75 l 3.59066429,-10 21.1849188,0 -2.603231,10 -22.17235209,0 z"
+ id="path2384"
+ style="fill:#e0d000;fill-opacity:1;stroke:#a00000;stroke-width:1.5;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/icons/eraseron.svg b/icons/eraseron.svg
new file mode 100644
index 0000000..27a5ffd
--- /dev/null
+++ b/icons/eraseron.svg
@@ -0,0 +1,38 @@
+<?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.0"
+ width="50"
+ height="43"
+ id="svg2">
+ <defs
+ id="defs5" />
+ <g
+ transform="translate(0.61535009,0.25)"
+ id="g3589"
+ style="fill:#404040;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="M 36,25.875 L 36,17.5 48.5,4 47,14.5 36,25.875 z"
+ id="path2394"
+ style="fill:#404040;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 15,17.5 L 28.5,4 48.5,4 36,17.5 l -21,0 z"
+ id="path2390"
+ style="fill:#404040;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 22.800718,38.5 L 25.493716,28.5 36,17.5 36,25.875 22.800718,38.5 z"
+ id="path2392"
+ style="fill:#404040;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 3.9497307,28.5 L 15,17.5 l 21,0 -10.86535,11 -21.1849193,0 z"
+ id="path2388"
+ style="fill:#404040;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 0.26929982,38.5 l 3.59066428,-10 21.1849189,0 -2.603231,10 -22.17235218,0 z"
+ id="path2384"
+ style="fill:#404040;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/icons/expand-coordinates.svg b/icons/expand-coordinates.svg
new file mode 100644
index 0000000..6be761e
--- /dev/null
+++ b/icons/expand-coordinates.svg
@@ -0,0 +1,79 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2384">
+ <defs
+ id="defs2386" />
+ <rect
+ width="50"
+ height="50"
+ x="2.5"
+ y="2.5"
+ id="rect2394"
+ style="fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 27.333333,1 C 28.083333,50.75 27.333333,54 27.333333,54"
+ id="path3168"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 54,27.333333 C 4.2499997,28.083333 1,27.333333 1,27.333333"
+ id="path3170"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <g
+ transform="translate(6.75,-6.75)"
+ id="g3161"
+ style="stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="M 28,27 L 37,18"
+ id="path2387"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 30,17 L 38,17 L 38,25"
+ id="path3159"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(1,0,0,-1,6.75,62.25)"
+ id="g3167"
+ style="stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="M 28,27 L 37,18"
+ id="path3169"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 30,17 L 38,17 L 38,25"
+ id="path3171"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(-1,0,0,1,47.75,-6.75)"
+ id="g3173"
+ style="stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="M 28,27 L 37,18"
+ id="path3175"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 30,17 L 38,17 L 38,25"
+ id="path3177"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(-1,0,0,-1,47.75,62.25)"
+ id="g3179"
+ style="stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="M 28,27 L 37,18"
+ id="path3181"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 30,17 L 38,17 L 38,25"
+ id="path3183"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/icons/filesaveoff.svg b/icons/filesaveoff.svg
new file mode 100644
index 0000000..b9baa5c
--- /dev/null
+++ b/icons/filesaveoff.svg
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"
+ inkscape:version="0.48.1 r9760"
+ sodipodi:docname="filesaveoff.svg"><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1280"
+ inkscape:window-height="725"
+ id="namedview37"
+ showgrid="false"
+ inkscape:zoom="4.7090909"
+ inkscape:cx="-11.254826"
+ inkscape:cy="27.5"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" /><metadata
+ id="metadata25"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs33">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </defs><path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:2.08759975;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path11"
+ d="m 15.046106,34.391188 c -0.384814,0 -0.764757,-0.02436 -1.139133,-0.06959 l 0.965862,1.634591 0.951946,-1.60954 c -0.258167,0.02088 -0.515637,0.04454 -0.778675,0.04454 z" /><g
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="g13"
+ transform="matrix(0.69586655,0,0,0.69586655,-4.0881364,0.79544654)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path15"
+ d="m 40.16,11.726 c -2.164,0 -3.958,1.555 -4.343,3.607 1.859,1.345 3.457,3.115 4.675,5.208 2.285,-0.172 4.094,-2.061 4.094,-4.39 0,-2.444 -1.982,-4.425 -4.426,-4.425 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path17"
+ d="m 40.713,39.887 c -1.224,2.232 -2.86,4.131 -4.797,5.556 0.521,1.864 2.213,3.239 4.244,3.239 2.443,0 4.426,-1.98 4.426,-4.424 0,-2.255 -1.693,-4.096 -3.873,-4.371 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path19"
+ d="m 14.273,39.871 c -2.253,0.206 -4.024,2.079 -4.024,4.387 0,2.443 1.98,4.424 4.424,4.424 2.064,0 3.784,-1.42 4.272,-3.332 -1.883,-1.416 -3.475,-3.289 -4.672,-5.479 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path21"
+ d="m 19.026,15.437 c -0.343,-2.103 -2.154,-3.711 -4.353,-3.711 -2.444,0 -4.424,1.981 -4.424,4.424 0,2.382 1.886,4.31 4.245,4.406 1.186,-2.043 2.732,-3.784 4.532,-5.119 z" />
+ </g><path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:2.08759975;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path23"
+ d="m 15.046106,9.537618 c 1.327714,0 2.594191,0.2860012 3.770205,0.784937 0.515637,-0.7487519 0.819731,-1.6540743 0.819731,-2.6324627 0,-2.5656599 -2.079945,-4.6463009 -4.646301,-4.6463009 -2.56566,0 -4.645605,2.080641 -4.645605,4.6463009 0,0.9936975 0.314531,1.9129372 0.846174,2.6679527 1.199674,-0.5212044 2.496073,-0.820427 3.855796,-0.820427 z" /><g
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="g25"
+ transform="matrix(0.69586655,0,0,0.69586655,-4.0881364,0.79544654)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path2988"
+ d="m 43.102,30.421 c 0,4.7344 -1.6452,9.2798 -4.5706,12.6275 -2.9254,3.3478 -6.8973,5.2305 -11.0344,5.2305 -4.1371,0 -8.109,-1.8827 -11.0344,-5.2305 -2.9254,-3.3477 -4.5706,-7.8931 -4.5706,-12.6275 0,-9.7966 7.0444,-17.858 15.605,-17.858 8.5606,0 15.605,8.0614 15.605,17.858 z" />
+ </g><g
+ style="fill:#ffffff;stroke:none;stroke-width:1.50000000000000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1"
+ id="g28"
+ transform="matrix(0.69586655,0,0,0.69586655,-4.0881364,0.79544654)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:none;stroke-width:1.50000000000000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1"
+ id="path30"
+ d="m 25.875,33.75 -1.542,-4.625 3.164,-2.587 3.615,2.626 -1.487,4.669 -3.75,-0.083 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:none;stroke-width:1.50000000000000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1"
+ id="path32"
+ d="m 27.501,41.551 c -3.968,-0.16 -5.543,-2.009 -5.543,-2.009 l 3.57,-4.163 4.465,0.168 3.132,4.12 c 0,0 -2.89,1.994 -5.624,1.884 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:none;stroke-width:1.50000000000000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1"
+ id="path34"
+ d="m 18.453,33.843 c -0.849,-2.968 0.172,-6.884 0.172,-6.884 l 4,2.167 1.493,4.629 -3.582,4.233 c 0,-10e-4 -1.465,-1.99 -2.083,-4.145 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:none;stroke-width:1.50000000000000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1"
+ id="path2998"
+ d="m 19.458,25.125 c 0,0 0.5,-1.958 3.039,-3.822 2.237,-1.643 4.465,-1.72 4.465,-1.72 l -0.037,4.981 -3.521,2.75 -3.946,-2.189 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:none;stroke-width:1.50000000000000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1"
+ id="path37"
+ d="M 32.084,27.834 28.625,24.959 29,19.75 c 0,0 1.834,-0.042 3.959,1.667 2.228,1.791 3.362,4.983 3.362,4.983 l -4.237,1.434 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;stroke:none;stroke-width:1.50000000000000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1"
+ id="path3002"
+ d="m 31.292,34.042 1.313,-4.464 4.187,-1.536 c 0,0 0.677,2.663 -0.042,5.667 -0.54,2.256 -2.084,4.361 -2.084,4.361 l -3.374,-4.028 z" />
+ </g><g
+ id="g3830"><g
+ id="g4382"
+ transform="matrix(0.55205508,0,0,0.55205508,75.618464,18.235971)"><g
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"
+ id="g4308"
+ transform="translate(-80.093659,12.220029)">
+ <g
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"
+ id="g4310">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path4312"
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389" />
+ </g>
+ </g><g
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"
+ id="g4314"
+ transform="translate(-80.093659,12.220029)">
+ <g
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"
+ id="g4316">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path4318"
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z" />
+ </g>
+ </g><path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path4320"
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543" /><path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path4322"
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543" /><path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ id="path4324"
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544" /><line
+ id="line4326"
+ y2="23.725029"
+ y1="58.753029"
+ x2="-66.884659"
+ x1="-66.884659"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g><g
+ id="g4770"
+ transform="matrix(1,0,0,-1,-30.386573,49.171266)"><g
+ id="g4772"
+ transform="translate(34.0803,-1006.42)"><polyline
+ id="polyline4774"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path
+ inkscape:connector-curvature="0"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path4776"
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067" /></g></g></g></svg> \ No newline at end of file
diff --git a/icons/flowoff.svg b/icons/flowoff.svg
new file mode 100644
index 0000000..13b11c5
--- /dev/null
+++ b/icons/flowoff.svg
@@ -0,0 +1,21 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <defs
+ id="defs5" />
+ <path
+ d="M 14.85,27.1 l -0.45,13.15 4.35,-2.4 c 0,0 6.349418,4.294879 10.2,4.05 4.946317,-0.314563 11.65,-7.5 11.65,-7.5 l -2.25,-2.3 c 0,0 -5.615371,6.15399 -9.4,6.65 -2.286298,0.29964 -7.05,-2.5 -7.05,-2.5 L 26,34.1 14.85,27.1 z"
+ id="path3590"
+ style="fill:none;stroke:#ffffff;stroke-width:2.29999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 40.15,28.955044 l 0.45,-13.150001 -4.35,2.4 c 0,0 -6.349418,-4.294879 -10.2,-4.05 -4.946317,0.314563 -11.65,7.5 -11.65,7.5 l 2.25,2.3 c 0,0 5.615371,-6.15399 9.4,-6.65 2.286298,-0.29964 7.05,2.5 7.05,2.5 l -4.1,2.15 11.15,7.000001 z"
+ id="path3590-4"
+ style="fill:none;stroke:#ffffff;stroke-width:2.29999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/icons/flowon.svg b/icons/flowon.svg
new file mode 100644
index 0000000..b115fa7
--- /dev/null
+++ b/icons/flowon.svg
@@ -0,0 +1,43 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <defs
+ id="defs5" />
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <g
+ transform="translate(4.9228669,-0.04294974)"
+ id="g3163">
+ <path
+ d="M 27.560785,16.470589 l -1.511002,10.315514 -8.177996,-6.466322 9.688998,-3.849192 z"
+ transform="translate(8.1558824,1.1284313)"
+ id="path2386"
+ style="fill:#a06400;fill-opacity:1;stroke:#a06400;stroke-width:3;stroke-opacity:1" />
+ <path
+ d="M 27.560785,16.470589 l -1.511002,10.315514 -8.177996,-6.466322 9.688998,-3.849192 z"
+ transform="matrix(-0.9848078,-0.1736482,-0.1736482,0.9848078,39.742987,17.127786)"
+ id="path2388"
+ style="fill:#a06400;fill-opacity:1;stroke:#a06400;stroke-width:3;stroke-opacity:1" />
+ <path
+ d="M 14.52451,37.473529 c 0,0.101391 2.070056,1.496645 4.373345,2.252558 1.606134,0.527115 3.382089,0.517702 4.410968,0.492541 1.751835,-0.04284 3.76231,-1.081811 5.486138,-2.294795 2.373956,-1.670452 4.204281,-3.670923 4.066804,-3.854225 l 0,0 0.109804,0"
+ id="path2390"
+ style="fill:none;stroke:#a06400;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="M 30.716667,18.403553 c 0,-0.101391 -5.19216,-3.579099 -9.384313,-3.545099 -4.192153,0.034 -9.701252,5.965718 -9.552942,6.14902 0,0 0,0 0,0 -0.0366,0 -0.0732,0 -0.109804,0"
+ id="path3162"
+ style="fill:none;stroke:#a06400;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/icons/help-off.svg b/icons/help-off.svg
new file mode 100644
index 0000000..7a038b6
--- /dev/null
+++ b/icons/help-off.svg
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2">
+ <metadata
+ id="metadata11">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs9">
+
+</defs>
+ <path
+ d="m 50,27.625 c 0,12.3431 -10.1569,22.5 -22.5,22.5 C 15.1569,50.125 5,39.9681 5,27.625 5,15.2819 15.1569,5.125 27.5,5.125 c 12.3431,0 22.5,10.1569 22.5,22.5 z"
+ id="path3"
+ style="fill:#ffffff" />
+ <path
+ d="m 23.956,27.167 c 0,-1.621 1.8,-1.89 3.645,-2.386 1.8,-0.495 3.6,-1.215 3.6,-3.825 0,-1.98 -1.845,-3.42 -3.734,-3.42 -3.78,0 -4.365,4.455 -7.246,4.455 -1.62,0 -2.745,-1.26 -2.745,-3.24 0,-4.726 5.94,-7.425 9.991,-7.425 5.806,0 10.756,3.6 10.756,9.631 0,4.995 -3.15,7.921 -7.785,9.091 l 0,1.619 c 0,1.801 -1.351,3.105 -3.241,3.105 -2.025,0 -3.24,-1.305 -3.24,-3.105 l 0,-4.5 -0.001,0 z m -0.27,13.77 c 0,-1.935 1.575,-3.51 3.51,-3.51 1.936,0 3.51,1.575 3.51,3.51 0,1.936 -1.574,3.512 -3.51,3.512 -1.935,-10e-4 -3.51,-1.577 -3.51,-3.512 z"
+ id="path5"
+ style="fill:#282828;stroke:#282828;stroke-width:0.25;stroke-miterlimit:4" />
+ <g
+ transform="matrix(0.90000005,0,0,0.90000005,1.5999984,5.2999976)"
+ id="g3850">
+ <rect
+ width="12"
+ height="12"
+ x="40"
+ y="37"
+ id="rect3848"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="M 45.999999,33 C 40.475555,33 36,37.478222 36,42.998666 36,48.524 40.475555,53 45.999999,53 c 5.524889,0 10,-4.476 10,-10.001334 0,-5.520444 -4.475555,-9.998666 -10,-9.998666 z m 4.532889,12.515111 c 0.557334,0.558222 0.558667,1.462666 0,2.02 -0.279111,0.279555 -0.644889,0.419111 -1.010666,0.419111 -0.365334,0 -0.730667,-0.139556 -1.008889,-0.417778 l -2.56,-2.560444 -2.56,2.560444 c -0.278667,0.278222 -0.644,0.417778 -1.009334,0.417778 -0.365777,0 -0.732,-0.139556 -1.011111,-0.419111 -0.557333,-0.557334 -0.557333,-1.461778 0.0018,-2.02 L 43.933777,42.956 41.374666,40.396889 c -0.559111,-0.557334 -0.559111,-1.463112 -0.0018,-2.020445 0.557778,-0.557333 1.463111,-0.559555 2.020445,0 l 2.56,2.559556 2.56,-2.559556 c 0.556444,-0.559555 1.461333,-0.557333 2.019555,0 0.558667,0.557333 0.557334,1.463111 0,2.020445 l -2.559111,2.559111 2.559111,2.559111 z"
+ id="path3067"
+ style="fill:#282828;fill-opacity:1;display:inline" />
+ </g>
+</svg>
diff --git a/icons/help-on.svg b/icons/help-on.svg
new file mode 100644
index 0000000..c818f84
--- /dev/null
+++ b/icons/help-on.svg
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2">
+ <metadata
+ id="metadata3971">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs3969" />
+ <path
+ d="m 50,27.625 c 0,12.3431 -10.1569,22.5 -22.5,22.5 C 15.1569,50.125 5,39.9681 5,27.625 5,15.2819 15.1569,5.125 27.5,5.125 c 12.3431,0 22.5,10.1569 22.5,22.5 z"
+ id="path3963"
+ style="fill:#ffffff" />
+ <path
+ d="m 23.956,27.167 c 0,-1.621 1.8,-1.89 3.645,-2.386 1.8,-0.495 3.6,-1.215 3.6,-3.825 0,-1.98 -1.845,-3.42 -3.734,-3.42 -3.78,0 -4.365,4.455 -7.246,4.455 -1.62,0 -2.745,-1.26 -2.745,-3.24 0,-4.726 5.94,-7.425 9.991,-7.425 5.806,0 10.756,3.6 10.756,9.631 0,4.995 -3.15,7.921 -7.785,9.091 l 0,1.619 c 0,1.801 -1.351,3.105 -3.241,3.105 -2.025,0 -3.24,-1.305 -3.24,-3.105 l 0,-4.5 -0.001,0 z m -0.27,13.77 c 0,-1.935 1.575,-3.51 3.51,-3.51 1.936,0 3.51,1.575 3.51,3.51 0,1.936 -1.574,3.512 -3.51,3.512 -1.935,-10e-4 -3.51,-1.577 -3.51,-3.512 z"
+ id="path3965"
+ style="fill:#282828;stroke:#282828;stroke-width:0.25;stroke-miterlimit:4" />
+ <g
+ id="g4559">
+ <rect
+ width="12"
+ height="12"
+ x="37"
+ y="38"
+ id="rect3848"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <g
+ transform="matrix(0.4,0,0,0.4,32,33)"
+ id="dialog-ok"
+ style="fill:#282828;fill-opacity:1;display:block">
+ <path
+ d="M 27.498,5 C 15.071,5 5,15.071 5,27.498 5,39.923 15.071,50 27.498,50 39.927,50 50,39.923 50,27.498 50,15.071 39.927,5 27.498,5 z M 41.419,19.886 24.706,40.208 13.789,29.29 c -1.258,-1.254 -1.258,-3.292 -0.003,-4.546 1.254,-1.255 3.292,-1.255 4.547,0 L 24.241,30.648 36.452,15.8 c 1.127,-1.368 3.153,-1.568 4.525,-0.438 1.37,1.127 1.569,3.153 0.442,4.524 z"
+ id="path4021"
+ style="fill:#282828;fill-opacity:1;display:inline" />
+
+</g>
+ </g>
+</svg>
diff --git a/icons/help-toolbar.svg b/icons/help-toolbar.svg
new file mode 100644
index 0000000..787b797
--- /dev/null
+++ b/icons/help-toolbar.svg
@@ -0,0 +1,18 @@
+<?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.0"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2" >
+ <path
+ d="m 50,27.625 c 0,12.3431 -10.1569,22.5 -22.5,22.5 C 15.1569,50.125 5,39.9681 5,27.625 5,15.2819 15.1569,5.125 27.5,5.125 c 12.3431,0 22.5,10.1569 22.5,22.5 z"
+ style="fill:#FFFFFF" />
+ <path
+ d="m 23.956,27.167 c 0,-1.621 1.8,-1.89 3.645,-2.386 1.8,-0.495 3.6,-1.215 3.6,-3.825 0,-1.98 -1.845,-3.42 -3.734,-3.42 -3.78,0 -4.365,4.455 -7.246,4.455 -1.62,0 -2.745,-1.26 -2.745,-3.24 0,-4.726 5.94,-7.425 9.991,-7.425 5.806,0 10.756,3.6 10.756,9.631 0,4.995 -3.15,7.921 -7.785,9.091 l 0,1.619 c 0,1.801 -1.351,3.105 -3.241,3.105 -2.025,0 -3.24,-1.305 -3.24,-3.105 l 0,-4.5 -0.001,0 z m -0.27,13.77 c 0,-1.935 1.575,-3.51 3.51,-3.51 1.936,0 3.51,1.575 3.51,3.51 0,1.936 -1.574,3.512 -3.51,3.512 -1.935,-10e-4 -3.51,-1.577 -3.51,-3.512 z"
+ style="fill:#282828;stroke:#282828;stroke-width:0.25;stroke-miterlimit:4;" />
+</svg>
diff --git a/icons/hideshowoff.svg b/icons/hideshowoff.svg
new file mode 100644
index 0000000..3db14c9
--- /dev/null
+++ b/icons/hideshowoff.svg
@@ -0,0 +1,28 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <defs
+ id="defs6" />
+ <path
+ d="m 1.5,25.5 0,-8 a 8,8 0 0 1 8,-8 l 8,0 0,4 20,0 0,-4 8,0 a 8,8 0 0 1 8,8 l 0,8 0,8 a 8,8 0 0 1 -8,8 l -8,0 -2,0 0,4 -16,0 0,-4 -2,0 -8,0 a 8,8 0 0 1 -8,-8 l 0,-8 z"
+ id="path2882"
+ style="fill:#404040;fill-opacity:1;stroke:#ffffff;stroke-width:2.5;stroke-linecap:square;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 35.875,27.5 c 0,4.62493 -3.74962,8.374182 -8.375,8.374182 -4.62538,0 -8.375,-3.749252 -8.375,-8.374182 0,-4.624929 3.74962,-8.374182 8.375,-8.374182 4.62538,0 8.375,3.749253 8.375,8.374182 l 0,0 z"
+ id="path5"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1px;stroke-opacity:1" />
+ <rect
+ width="10"
+ height="2"
+ x="22.5"
+ y="26.5"
+ id="rect2844"
+ style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:#404040;stroke-width:1.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/icons/hideshowon.svg b/icons/hideshowon.svg
new file mode 100644
index 0000000..326b32c
--- /dev/null
+++ b/icons/hideshowon.svg
@@ -0,0 +1,39 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3689">
+ <stop
+ id="stop3691"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3693"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="19"
+ x2="54"
+ y2="19"
+ id="linearGradient3695"
+ xlink:href="#linearGradient3689"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0.5,8.5)" />
+ </defs>
+ <path
+ d="m 1.5,25.5 0,-8 a 8,8 0 0 1 8,-8 l 8,0 0,4 20,0 0,-4 8,0 a 8,8 0 0 1 8,8 l 0,8 0,8 a 8,8 0 0 1 -8,8 l -8,0 -2,0 0,4 -16,0 0,-4 -2,0 -8,0 a 8,8 0 0 1 -8,-8 l 0,-8 z"
+ id="path2882"
+ style="fill:url(#linearGradient3695);fill-opacity:1;stroke:#a08000;stroke-width:2;stroke-linecap:square;stroke-opacity:1" />
+</svg>
diff --git a/icons/htmloff.svg b/icons/htmloff.svg
new file mode 100644
index 0000000..92f1fd2
--- /dev/null
+++ b/icons/htmloff.svg
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata25"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs33">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </defs><g
+ transform="translate(-24.072147,0)"
+ id="g3014"><g
+ transform="matrix(1.1623273,0,0,1.1623273,5.8946433,-8.9787622)"
+ id="g3116"
+ style="fill:none;stroke:#ffffff;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"><polygon
+ points="39.811,18.343 39.811,38.42 18.435,38.42 18.435,10.583 32.046,10.583 "
+ id="polygon3118"
+ style="fill:none;stroke:#ffffff;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><polyline
+ id="polyline3120"
+ points="39.811,18.343 32.046,18.343 32.046,10.583 "
+ style="fill:none;stroke:#ffffff;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g
+ transform="matrix(1.1623273,0,0,1.1623273,5.8946433,-8.9787622)"
+ id="g3122"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><circle
+ cx="29.122999"
+ cy="28.4"
+ r="6.4689999"
+ id="circle3124"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1" /><g
+ id="g3126"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 29.124,21.932 c 0,0 3.596,2.971 3.596,6.492 0,3.523 -3.596,6.445 -3.596,6.445"
+ id="path3128"
+ style="fill:none;stroke:#ffffff;stroke-width:0.97500002;stroke-opacity:1" /><path
+ d="m 29.124,21.932 c 0,0 -3.642,2.728 -3.642,6.492 0,3.765 3.642,6.445 3.642,6.445"
+ id="path3130"
+ style="fill:none;stroke:#ffffff;stroke-width:0.97500002;stroke-opacity:1" /><line
+ style="fill:none;stroke:#ffffff;stroke-width:0.97500002;stroke-opacity:1"
+ x1="29.124001"
+ x2="29.124001"
+ y1="21.931999"
+ y2="34.868999"
+ id="line3132" /><line
+ style="fill:none;stroke:#ffffff;stroke-width:0.97500002;stroke-opacity:1"
+ x1="22.653999"
+ x2="35.591"
+ y1="28.4"
+ y2="28.4"
+ id="line3134" /></g></g></g><g
+ id="g3830"><g
+ transform="matrix(0.55205508,0,0,0.55205508,75.618464,18.235971)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ id="line4326"
+ y2="23.725029"
+ y1="58.753029"
+ x2="-66.884659"
+ x1="-66.884659"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g><g
+ transform="matrix(1,0,0,-1,-30.386573,49.171266)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ id="polyline4774"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg> \ No newline at end of file
diff --git a/icons/htmlon.svg b/icons/htmlon.svg
new file mode 100644
index 0000000..fe8fa71
--- /dev/null
+++ b/icons/htmlon.svg
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata25"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs33">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </defs><g
+ transform="translate(-24.072147,0)"
+ id="g4121"><g
+ transform="matrix(1.1623273,0,0,1.1623273,5.8946433,-8.9787622)"
+ id="g3116"
+ style="fill:none;stroke:#00ff00;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"><polygon
+ points="39.811,38.42 18.435,38.42 18.435,10.583 32.046,10.583 39.811,18.343 "
+ id="polygon3118"
+ style="fill:none;stroke:#00ff00;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><polyline
+ id="polyline3120"
+ points="39.811,18.343 32.046,18.343 32.046,10.583 "
+ style="fill:none;stroke:#00ff00;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g
+ transform="matrix(1.1623273,0,0,1.1623273,5.8946433,-8.9787622)"
+ id="g3122"
+ style="fill:none;stroke:#00ff00;stroke-opacity:1"><circle
+ cx="29.122999"
+ cy="28.4"
+ r="6.4689999"
+ id="circle3124"
+ style="fill:none;stroke:#00ff00;stroke-opacity:1" /><g
+ id="g3126"
+ style="fill:none;stroke:#00ff00;stroke-opacity:1"><path
+ d="m 29.124,21.932 c 0,0 3.596,2.971 3.596,6.492 0,3.523 -3.596,6.445 -3.596,6.445"
+ id="path3128"
+ style="fill:none;stroke:#00ff00;stroke-width:0.97500002;stroke-opacity:1" /><path
+ d="m 29.124,21.932 c 0,0 -3.642,2.728 -3.642,6.492 0,3.765 3.642,6.445 3.642,6.445"
+ id="path3130"
+ style="fill:none;stroke:#00ff00;stroke-width:0.97500002;stroke-opacity:1" /><line
+ style="fill:none;stroke:#00ff00;stroke-width:0.97500002;stroke-opacity:1"
+ x1="29.124001"
+ x2="29.124001"
+ y1="21.931999"
+ y2="34.868999"
+ id="line3132" /><line
+ style="fill:none;stroke:#00ff00;stroke-width:0.97500002;stroke-opacity:1"
+ x1="22.653999"
+ x2="35.591"
+ y1="28.4"
+ y2="28.4"
+ id="line3134" /></g></g></g><g
+ id="g3830"><g
+ transform="matrix(0.55205508,0,0,0.55205508,75.618464,18.235971)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ id="line4326"
+ y2="23.725029"
+ y1="58.753029"
+ x2="-66.884659"
+ x1="-66.884659"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g><g
+ transform="matrix(1,0,0,-1,-30.386573,49.171266)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ id="polyline4774"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg> \ No newline at end of file
diff --git a/icons/image-saveoff.svg b/icons/image-saveoff.svg
new file mode 100644
index 0000000..365f578
--- /dev/null
+++ b/icons/image-saveoff.svg
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata25"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs33">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </defs><g
+ transform="matrix(0.55205508,0,0,0.55205508,77.118464,18.235971)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="-66.884659"
+ x2="-66.884659"
+ y1="58.753029"
+ y2="23.725029"
+ id="line4326" /></g><g
+ transform="matrix(1.1623273,0,0,1.1623273,-14.422024,-12.63995)"
+ id="g3882"
+ style="fill:none;stroke:#ffffff;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"><g
+ id="g3884"
+ style="fill:none;stroke:#ffffff;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"><polygon
+ points="43.041,21.577 35.281,13.812 15.204,13.812 15.204,35.189 43.041,35.189 "
+ id="polygon3886"
+ style="fill:none;stroke:#ffffff;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><polyline
+ id="polyline3888"
+ points="35.281,13.812 35.281,21.577 43.041,21.577 "
+ style="fill:none;stroke:#ffffff;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g><path
+ d="m 19.426691,12.275117 c -4.727185,0 -8.666312,4.714399 -8.666312,4.714399 0,0 3.939127,4.737646 8.666312,4.735322 4.729509,-0.0046 8.668637,-4.739971 8.668637,-4.739971 0,0 -3.939128,-4.713237 -8.668637,-4.70975 z m 0,8.039818 c -1.830666,0 -3.314958,-1.484292 -3.314958,-3.31612 0,-1.827179 1.484292,-3.314958 3.314958,-3.314958 1.828341,0 3.312632,1.487779 3.312632,3.314958 0,1.831828 -1.484291,3.31612 -3.312632,3.31612 z"
+ id="path3890"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;display:inline" /><circle
+ cx="29.207001"
+ cy="25.863001"
+ r="1.294"
+ transform="matrix(1.1623273,0,0,1.1623273,-14.520241,-13.061294)"
+ id="circle3892"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;display:inline" /><g
+ transform="matrix(1,0,0,-1,-24.850339,47.707501)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ id="polyline4774" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></svg> \ No newline at end of file
diff --git a/icons/image-saveon.svg b/icons/image-saveon.svg
new file mode 100644
index 0000000..71a91ff
--- /dev/null
+++ b/icons/image-saveon.svg
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata25"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs33">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </defs><g
+ transform="matrix(0.55205508,0,0,0.55205508,77.118464,18.235971)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="-66.884659"
+ x2="-66.884659"
+ y1="58.753029"
+ y2="23.725029"
+ id="line4326" /></g><g
+ transform="matrix(1.1623273,0,0,1.1623273,-14.422024,-12.63995)"
+ id="g3882"
+ style="fill:none;stroke:#00ff00;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"><g
+ id="g3884"
+ style="fill:none;stroke:#00ff00;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"><polygon
+ points="43.041,21.577 35.281,13.812 15.204,13.812 15.204,35.189 43.041,35.189 "
+ id="polygon3886"
+ style="fill:none;stroke:#00ff00;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><polyline
+ id="polyline3888"
+ points="35.281,13.812 35.281,21.577 43.041,21.577 "
+ style="fill:none;stroke:#00ff00;stroke-width:2.15085721;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g><path
+ d="m 19.426691,12.275117 c -4.727185,0 -8.666312,4.714399 -8.666312,4.714399 0,0 3.939127,4.737646 8.666312,4.735322 4.729509,-0.0046 8.668637,-4.739971 8.668637,-4.739971 0,0 -3.939128,-4.713237 -8.668637,-4.70975 z m 0,8.039818 c -1.830666,0 -3.314958,-1.484292 -3.314958,-3.31612 0,-1.827179 1.484292,-3.314958 3.314958,-3.314958 1.828341,0 3.312632,1.487779 3.312632,3.314958 0,1.831828 -1.484291,3.31612 -3.312632,3.31612 z"
+ id="path3890"
+ style="fill:#00ff00;fill-opacity:1;stroke:none;display:inline" /><circle
+ cx="29.207001"
+ cy="25.863001"
+ r="1.294"
+ transform="matrix(1.1623273,0,0,1.1623273,-14.520241,-13.061294)"
+ id="circle3892"
+ style="fill:#00ff00;fill-opacity:1;stroke:#00ff00;stroke-opacity:1;display:inline" /><g
+ transform="matrix(1,0,0,-1,-24.850339,47.707501)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ id="polyline4774" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></svg> \ No newline at end of file
diff --git a/icons/load-from-journal.svg b/icons/load-from-journal.svg
new file mode 100644
index 0000000..d0869da
--- /dev/null
+++ b/icons/load-from-journal.svg
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata25"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs33">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+</defs><g
+ transform="matrix(0.55205508,0,0,0.55205508,44.618464,18.235971)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="-66.884659"
+ x2="-66.884659"
+ y1="58.753029"
+ y2="23.725029"
+ id="line4326" /></g><g
+ id="g4084"><path
+ d="m 40.046106,34.391197 c -0.384814,0 -0.764757,-0.02436 -1.139134,-0.06959 l 0.965863,1.634591 0.951946,-1.60954 c -0.258167,0.02088 -0.515638,0.04454 -0.778675,0.04454 z"
+ id="path11"
+ style="fill:none;stroke:#ffffff;stroke-width:2.08759975;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><g
+ transform="matrix(0.69586655,0,0,0.69586655,20.911863,0.79545554)"
+ id="g13"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"><path
+ d="m 40.16,11.726 c -2.164,0 -3.958,1.555 -4.343,3.607 1.859,1.345 3.457,3.115 4.675,5.208 2.285,-0.172 4.094,-2.061 4.094,-4.39 0,-2.444 -1.982,-4.425 -4.426,-4.425 z"
+ id="path15"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
+ d="m 40.713,39.887 c -1.224,2.232 -2.86,4.131 -4.797,5.556 0.521,1.864 2.213,3.239 4.244,3.239 2.443,0 4.426,-1.98 4.426,-4.424 0,-2.255 -1.693,-4.096 -3.873,-4.371 z"
+ id="path17"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
+ d="m 14.273,39.871 c -2.253,0.206 -4.024,2.079 -4.024,4.387 0,2.443 1.98,4.424 4.424,4.424 2.064,0 3.784,-1.42 4.272,-3.332 -1.883,-1.416 -3.475,-3.289 -4.672,-5.479 z"
+ id="path19"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
+ d="m 19.026,15.437 c -0.343,-2.103 -2.154,-3.711 -4.353,-3.711 -2.444,0 -4.424,1.981 -4.424,4.424 0,2.382 1.886,4.31 4.245,4.406 1.186,-2.043 2.732,-3.784 4.532,-5.119 z"
+ id="path21"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><path
+ d="m 40.046106,9.537627 c 1.327713,0 2.59419,0.2860012 3.770205,0.784937 0.515637,-0.7487519 0.819731,-1.6540743 0.819731,-2.6324627 0,-2.5656599 -2.079945,-4.6463009 -4.646301,-4.6463009 -2.56566,0 -4.645605,2.080641 -4.645605,4.6463009 0,0.9936975 0.314531,1.9129372 0.846173,2.6679527 1.199674,-0.5212044 2.496074,-0.820427 3.855797,-0.820427 z"
+ id="path23"
+ style="fill:none;stroke:#ffffff;stroke-width:2.08759975;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><g
+ transform="matrix(0.69586655,0,0,0.69586655,20.911863,0.79545554)"
+ id="g25"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"><path
+ d="m 43.102,30.421 c 0,4.7344 -1.6452,9.2798 -4.5706,12.6275 -2.9254,3.3478 -6.8973,5.2305 -11.0344,5.2305 -4.1371,0 -8.109,-1.8827 -11.0344,-5.2305 -2.9254,-3.3477 -4.5706,-7.8931 -4.5706,-12.6275 0,-9.7966 7.0444,-17.858 15.605,-17.858 8.5606,0 15.605,8.0614 15.605,17.858 z"
+ id="path2988"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g
+ transform="matrix(0.69586655,0,0,0.69586655,20.911863,0.79545554)"
+ id="g28"
+ style="fill:#ffffff;fill-opacity:1;stroke:none"><path
+ d="m 25.875,33.75 -1.542,-4.625 3.164,-2.587 3.615,2.626 -1.487,4.669 -3.75,-0.083 z"
+ id="path30"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /><path
+ d="m 27.501,41.551 c -3.968,-0.16 -5.543,-2.009 -5.543,-2.009 l 3.57,-4.163 4.465,0.168 3.132,4.12 c 0,0 -2.89,1.994 -5.624,1.884 z"
+ id="path32"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /><path
+ d="m 18.453,33.843 c -0.849,-2.968 0.172,-6.884 0.172,-6.884 l 4,2.167 1.493,4.629 -3.582,4.233 c 0,-10e-4 -1.465,-1.99 -2.083,-4.145 z"
+ id="path34"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /><path
+ d="m 19.458,25.125 c 0,0 0.5,-1.958 3.039,-3.822 2.237,-1.643 4.465,-1.72 4.465,-1.72 l -0.037,4.981 -3.521,2.75 -3.946,-2.189 z"
+ id="path2998"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /><path
+ d="M 32.084,27.834 28.625,24.959 29,19.75 c 0,0 1.834,-0.042 3.959,1.667 2.228,1.791 3.362,4.983 3.362,4.983 l -4.237,1.434 z"
+ id="path37"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /><path
+ d="m 31.292,34.042 1.313,-4.464 4.187,-1.536 c 0,0 0.677,2.663 -0.042,5.667 -0.54,2.256 -2.084,4.361 -2.084,4.361 l -3.374,-4.028 z"
+ id="path3002"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" /></g></g><g
+ transform="matrix(0,-1,-1,0,43.9376,89.386573)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ id="polyline4774" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></svg> \ No newline at end of file
diff --git a/icons/logo-saveoff.svg b/icons/logo-saveoff.svg
new file mode 100644
index 0000000..0954a62
--- /dev/null
+++ b/icons/logo-saveoff.svg
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata25"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs33">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+</defs><g
+ transform="matrix(0.75578519,0,0,0.75578519,-4.9396196,-1.2911009)"
+ id="clipping-text"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1;display:block">
+ <g
+ id="g3152"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1;display:inline">
+ <g
+ id="g3154"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <polygon
+ points="31.874,6.088 43.818,18.027 43.818,48.914 10.932,48.914 10.932,6.088 "
+ id="polygon3156"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-opacity:1" />
+ <polyline
+ id="polyline3158"
+ points="43.818,18.027 31.874,18.027 31.874,6.088 "
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-opacity:1" />
+ </g>
+ </g>
+ <line
+ id="line3160"
+ y2="26.25"
+ y1="26.25"
+ x2="36.875"
+ x1="17.875"
+ display="inline"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-opacity:1;display:inline" />
+ <line
+ id="line3162"
+ y2="33.25"
+ y1="33.25"
+ x2="36.875"
+ x1="17.875"
+ display="inline"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-opacity:1;display:inline" />
+ <line
+ id="line3164"
+ y2="40.25"
+ y1="40.25"
+ x2="36.875"
+ x1="17.875"
+ display="inline"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-opacity:1;display:inline" />
+</g><g
+ id="g3830"><g
+ transform="matrix(0.55205508,0,0,0.55205508,75.618464,18.235971)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ id="line4326"
+ y2="23.725029"
+ y1="58.753029"
+ x2="-66.884659"
+ x1="-66.884659"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g><g
+ transform="matrix(1,0,0,-1,-30.386573,49.171266)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ id="polyline4774"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg> \ No newline at end of file
diff --git a/icons/logo-saveon.svg b/icons/logo-saveon.svg
new file mode 100644
index 0000000..f2bfc31
--- /dev/null
+++ b/icons/logo-saveon.svg
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata25"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs33">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+</defs><g
+ transform="matrix(0.75578519,0,0,0.75578519,-4.9396196,-1.2911009)"
+ id="clipping-text"
+ style="fill:none;stroke:#00ff00;stroke-opacity:1;display:block">
+ <g
+ id="g3152"
+ style="fill:none;stroke:#00ff00;stroke-opacity:1;display:inline">
+ <g
+ id="g3154"
+ style="fill:none;stroke:#00ff00;stroke-opacity:1">
+ <polygon
+ points="43.818,18.027 43.818,48.914 10.932,48.914 10.932,6.088 31.874,6.088 "
+ id="polygon3156"
+ style="fill:none;stroke:#00ff00;stroke-width:3.5;stroke-opacity:1" />
+ <polyline
+ id="polyline3158"
+ points="43.818,18.027 31.874,18.027 31.874,6.088 "
+ style="fill:none;stroke:#00ff00;stroke-width:3.5;stroke-opacity:1" />
+ </g>
+ </g>
+ <line
+ id="line3160"
+ y2="26.25"
+ y1="26.25"
+ x2="36.875"
+ x1="17.875"
+ display="inline"
+ style="fill:none;stroke:#00ff00;stroke-width:3.5;stroke-opacity:1;display:inline" />
+ <line
+ id="line3162"
+ y2="33.25"
+ y1="33.25"
+ x2="36.875"
+ x1="17.875"
+ display="inline"
+ style="fill:none;stroke:#00ff00;stroke-width:3.5;stroke-opacity:1;display:inline" />
+ <line
+ id="line3164"
+ y2="40.25"
+ y1="40.25"
+ x2="36.875"
+ x1="17.875"
+ display="inline"
+ style="fill:none;stroke:#00ff00;stroke-width:3.5;stroke-opacity:1;display:inline" />
+</g><g
+ id="g3830"><g
+ transform="matrix(0.55205508,0,0,0.55205508,75.618464,18.235971)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ id="line4326"
+ y2="23.725029"
+ y1="58.753029"
+ x2="-66.884659"
+ x1="-66.884659"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g><g
+ transform="matrix(1,0,0,-1,-30.386573,49.171266)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ id="polyline4774"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg> \ No newline at end of file
diff --git a/icons/numbersoff.svg b/icons/numbersoff.svg
new file mode 100644
index 0000000..cffabf7
--- /dev/null
+++ b/icons/numbersoff.svg
@@ -0,0 +1,38 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <defs
+ id="defs5" />
+ <g
+ transform="translate(6.1026134,-1.6740561)"
+ id="g2392"
+ style="fill:#ffffff;fill-opacity:1">
+ <text
+ x="3.9423084"
+ y="26.866751"
+ id="text2384"
+ xml:space="preserve"
+ style="font-size:12px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"><tspan
+ x="3.9423084"
+ y="26.866751"
+ id="tspan2386"
+ style="font-size:18px;font-weight:normal;fill:#ffffff;fill-opacity:1;-inkscape-font-specification:AlArabiya">123</tspan></text>
+ <text
+ x="1.8153553"
+ y="44.840736"
+ id="text2388"
+ xml:space="preserve"
+ style="font-size:12px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"><tspan
+ x="1.8153553"
+ y="44.840736"
+ id="tspan2390"
+ style="font-size:18px;font-weight:normal;fill:#ffffff;fill-opacity:1;-inkscape-font-specification:AlArabiya">+–=</tspan></text>
+ </g>
+</svg>
diff --git a/icons/numberson.svg b/icons/numberson.svg
new file mode 100644
index 0000000..124994e
--- /dev/null
+++ b/icons/numberson.svg
@@ -0,0 +1,46 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <defs
+ id="defs5" />
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <g
+ transform="translate(6.1026134,-1.6740561)"
+ id="g2392"
+ style="fill:#a000a0;fill-opacity:1">
+ <text
+ x="4.5171318"
+ y="26.866751"
+ id="text2384"
+ xml:space="preserve"
+ style="font-size:12px;fill:#a000a0;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"><tspan
+ x="4.5171318"
+ y="26.866751"
+ id="tspan2386"
+ style="font-size:18px;fill:#a000a0;fill-opacity:1">123</tspan></text>
+ <text
+ x="1.8153553"
+ y="44.840736"
+ id="text2388"
+ xml:space="preserve"
+ style="font-size:12px;fill:#a000a0;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"><tspan
+ x="1.8153553"
+ y="44.840736"
+ id="tspan2390"
+ style="font-size:18px;fill:#a000a0;fill-opacity:1">+–=</tspan></text>
+ </g>
+</svg>
diff --git a/icons/palette.svg b/icons/palette.svg
new file mode 100644
index 0000000..48b4b36
--- /dev/null
+++ b/icons/palette.svg
@@ -0,0 +1,17 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <defs
+ id="defs6" />
+ <path
+ d="m 1.5,25.5 0,-8 a 8,8 0 0 1 8,-8 l 8,0 0,4 20,0 0,-4 8,0 a 8,8 0 0 1 8,8 l 0,8 0,8 a 8,8 0 0 1 -8,8 l -8,0 -2,0 0,4 -16,0 0,-4 -2,0 -8,0 a 8,8 0 0 1 -8,-8 l 0,-8 z"
+ id="path2882"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.5;stroke-linecap:square;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/icons/paletteoff.svg b/icons/paletteoff.svg
new file mode 100644
index 0000000..1964c0a
--- /dev/null
+++ b/icons/paletteoff.svg
@@ -0,0 +1,28 @@
+<?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.0"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs13" />
+ <path
+ d="m 0.5,36.5 c 0.25,-9 0,-27 0,-27 l 44,0 0,27 -44,0 z"
+ id="path3"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 30.875,22.999999 c 0,4.62493 -3.74962,8.374182 -8.375,8.374182 -4.62538,0 -8.375,-3.749252 -8.375,-8.374182 0,-4.624929 3.74962,-8.374182 8.375,-8.374182 4.62538,0 8.375,3.749253 8.375,8.374182 l 0,0 z"
+ id="path5"
+ style="fill:#404040;fill-opacity:1;stroke:#404040;stroke-width:1px;stroke-opacity:1" />
+ <rect
+ width="10"
+ height="2"
+ x="17.5"
+ y="21.5"
+ id="rect2844"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/icons/paletteon.svg b/icons/paletteon.svg
new file mode 100644
index 0000000..cd9f594
--- /dev/null
+++ b/icons/paletteon.svg
@@ -0,0 +1,36 @@
+<?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.0"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs13" />
+ <path
+ d="m 0.5,36.5 c 0.25,-9 0,-27 0,-27 l 44,0 0,27 -44,0 z"
+ id="path3"
+ style="fill:#ffd000;fill-opacity:1;stroke:#a08000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 30.875,22.999999 c 0,4.62493 -3.74962,8.374182 -8.375,8.374182 -4.62538,0 -8.375,-3.749252 -8.375,-8.374182 0,-4.624929 3.74962,-8.374182 8.375,-8.374182 4.62538,0 8.375,3.749253 8.375,8.374182 l 0,0 z"
+ id="path5"
+ style="fill:#00ff00;fill-opacity:1;stroke:#00ff00;stroke-width:1px;stroke-opacity:1" />
+ <rect
+ width="10"
+ height="2"
+ x="17.5"
+ y="21.5"
+ id="rect2844"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <rect
+ width="10"
+ height="2"
+ x="-27.5"
+ y="21.5"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="rect2844-4"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/icons/penoff.svg b/icons/penoff.svg
new file mode 100644
index 0000000..109c0d6
--- /dev/null
+++ b/icons/penoff.svg
@@ -0,0 +1,41 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <defs
+ id="defs5" />
+ <path
+ d="M 11.152285,41.709935 c 1.43401,0.788706 5.23977,1.402428 7.528553,1.290609 1.626167,-0.07945 3.914929,-0.479849 5.234137,-1.43401 2.238123,-1.618798 3.032695,-5.829627 5.090736,-7.671954 1.225701,-1.097229 3.231844,-2.444635 4.875634,-2.509518 2.003851,-0.07909 4.468168,1.514349 6.166244,2.581219 1.290216,0.810619 3.800127,3.369923 3.800127,3.369923"
+ id="path2471"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;" />
+ <path
+ d="M 35.566307,13.352385 l 2.437818,-2.366117 1.200984,0.84248 1.416085,1.200984 0.985882,1.272684 0.914181,1.487786 -2.366117,2.509519 -0.896257,-1.505711 -2.186866,-2.366117 -1.50571,-1.075508 z"
+ id="path3255"
+ style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 32.877538,16.112854 l 2.294417,-2.079315 1.200984,0.84248 1.416085,1.200984 0.985882,1.272684 0.914181,1.487786 -2.079315,1.864214 -0.967957,-1.147208 -2.330267,-2.222716 -1.43401,-1.218909 z"
+ id="path3253"
+ style="fill:none;stroke:#ffffff;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 13.423248,38.807621 l 0.891754,-3.80169 18.820708,-18.21056 -0.469344,-0.680549 2.252852,-2.135517 5.115854,5.115853 -2.229385,2.135517 -0.774419,-0.539747 -19.05538,18.022823 -3.70782,0.657082 -0.84482,-0.563212 z"
+ id="path2822"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;" />
+ <path
+ d="M 33.482432,17.358584 l 3.660886,3.473148"
+ id="path3596"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;" />
+ <path
+ d="M 10.935723,41.905293 l 2.769132,-2.816066"
+ id="path3598"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;" />
+ <path
+ d="M 14.690478,35.287537 c 0,0 1.594197,0.393866 2.158983,0.93869 0.574138,0.553844 1.032558,2.158984 1.032558,2.158984"
+ id="path3600"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;" />
+</svg>
diff --git a/icons/penon.svg b/icons/penon.svg
new file mode 100644
index 0000000..c717a9e
--- /dev/null
+++ b/icons/penon.svg
@@ -0,0 +1,61 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <defs
+ id="defs5" />
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <g
+ transform="translate(5.2499995,-0.75000033)"
+ id="g2824">
+ <g
+ transform="translate(-31.525379,-20.795051)"
+ id="g2431">
+ <path
+ d="M 37.427664,63.254986 c 1.43401,0.788706 5.23977,1.402428 7.528553,1.290609 1.626167,-0.07945 3.914929,-0.479849 5.234137,-1.43401 2.238123,-1.618798 3.032695,-5.829627 5.090736,-7.671954 1.225701,-1.097229 3.231844,-2.444635 4.875634,-2.509518 2.003851,-0.07909 4.468168,1.514349 6.166244,2.581219 1.290216,0.810619 3.800127,3.369923 3.800127,3.369923"
+ id="path2471"
+ style="fill:none;stroke:#0080ff;stroke-width:3;stroke-linecap:round;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 39.220176,61.820976 l 1.147208,-4.660533 1.935914,0.358503 1.218909,1.075507 0.286802,2.079315 -4.588833,1.147208 z"
+ id="path3245"
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#e0e0e0;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 37.786166,63.039885 c 1.720812,-1.864213 1.864213,-1.720812 1.864213,-1.720812"
+ id="path3243"
+ style="fill:none;stroke:#0060ff;stroke-width:2;stroke-linecap:round;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 40.869288,56.443439 59.224617,38.374911 60.658628,39.73722 42.6618,57.303845 40.869288,56.443439 z"
+ id="path3247"
+ style="fill:#a0a0ff;fill-opacity:1;stroke:#a0a0ff;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 44.095811,58.307652 61.949237,41.09953 63.383248,42.461839 44.454312,60.530368 44.095811,58.307652 z"
+ id="path3249"
+ style="fill:#8080ff;fill-opacity:1;stroke:#8080ff;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 42.231598,57.232144 l 1.649111,1.649111 18.28363,-17.781726 -1.505711,-1.792512 -18.42703,17.925127 z"
+ id="path3251"
+ style="fill:#c4c4ff;fill-opacity:1;stroke:#c4c4ff;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 61.841686,34.897436 l 2.437818,-2.366117 1.200984,0.84248 1.416085,1.200984 0.985882,1.272684 0.914181,1.487786 -2.366117,2.509519 -0.896257,-1.505711 -2.186866,-2.366117 -1.50571,-1.075508 z"
+ id="path3255"
+ style="fill:#e0a040;fill-opacity:1;stroke:#e0a040;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 59.152917,37.657905 l 2.294417,-2.079315 1.200984,0.84248 1.416085,1.200984 0.985882,1.272684 0.914181,1.487786 -2.079315,1.864214 -0.967957,-1.147208 -2.330267,-2.222716 -1.43401,-1.218909 z"
+ id="path3253"
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#c4c4c4;stroke-width:1px;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/pippy-openoff.svg b/icons/pippy-openoff.svg
new file mode 100644
index 0000000..605e62a
--- /dev/null
+++ b/icons/pippy-openoff.svg
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 54.999998 55.000001"
+ id="Icon"
+ xml:space="preserve"
+ style="overflow:visible"><metadata
+ id="metadata26"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs24">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <linearGradient
+ id="linearGradient3166-6"><stop
+ id="stop3168-5"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" /><stop
+ id="stop3170-6"
+ style="stop-color:#ff0000;stop-opacity:1"
+ offset="1" /></linearGradient><linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient3172-9"
+ xlink:href="#linearGradient3166-6"
+ gradientUnits="userSpaceOnUse" /><linearGradient
+ id="linearGradient3166"><stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" /><stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" /></linearGradient><linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+
+
+
+
+
+</defs>
+
+
+
+
+<g
+ transform="matrix(1.1181651,0,0,1.1181651,61.470731,-23.367585)"
+ id="g3348"
+ style="fill:none"><path
+ d="m -19.754174,46.744351 c 3.555069,0 8.83424,-1.56838 8.83424,-6.181226 0,-5.131219 -4.597011,-5.60538 -6.503378,-6.124857 -2.107038,-0.441556 -3.510542,-1.057744 -3.594847,-1.909356 -0.144269,-1.460061 0.687503,-2.028723 2.342736,-2.028723 0,0 3.937412,2.024856 7.282311,0.40895 0.942794,-0.454819 2.631273,-2.579702 2.631273,-4.04529 0,-1.466142 -5.450749,-3.160522 -7.104794,-3.160522 -1.655233,0 -3.062894,2.125988 -3.062894,2.125988 -3.309277,0 -6.619149,2.932283 -6.619149,5.864566 0,2.93173 3.166197,5.225166 6.950433,5.864565 1.759131,0.259187 3.230316,1.226851 2.896064,3.005231 -0.27132,1.444036 -1.778128,2.932283 -4.963917,2.932283 -2.524407,0 -7.896195,-0.121026 -8.75409,-2.254199 -0.551547,-1.373851 0.09974,-2.876467 0.927358,-2.876467 l -0.01603,-0.08842 c -0.843052,-0.08732 -3.293841,0.08842 -3.293841,3.020151 -5.94e-4,3.759026 5.428782,5.447327 12.048526,5.447327 z"
+ id="path2474"
+ style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
+ d="m -11.393706,30.909692 c -1.557272,-0.158607 -3.924943,-1.105272 -4.43315,-2.775335"
+ id="path2476"
+ style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><circle
+ cx="35.805"
+ cy="10.96"
+ r="1.676"
+ transform="matrix(0.59369893,0,0,0.5526353,-36.672813,19.93767)"
+ id="circle2478"
+ style="fill:none;stroke-width:3.42034841;stroke-miterlimit:4;stroke-dasharray:none" /></g><g
+ transform="translate(-2.2743816,2.513511)"
+ id="g3036"
+ style="overflow:visible"><path
+ d="M 9.5689397,38.952218 9.3902171,32.897964 M 25.663656,50.969805 3.6345541,51.23649 3.5243801,39.2454 l 5.851406,-6.358972 21.2578909,0 -0.110174,12.124478 z M 3.6345541,39.378743 l 21.4782339,0.266685 0.33052,10.924347 m -0.306426,-11.052011 5.531132,-6.638311"
+ id="path3806"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /><g
+ transform="matrix(0,-1,-1,0,47.3087,85.9835)"
+ id="g4770-9"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772-6"><polyline
+ id="polyline4774-2"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776-5"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg> \ No newline at end of file
diff --git a/icons/pippy-openon.svg b/icons/pippy-openon.svg
new file mode 100644
index 0000000..a3bc335
--- /dev/null
+++ b/icons/pippy-openon.svg
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 54.999998 55.000001"
+ id="Icon"
+ xml:space="preserve"
+ style="overflow:visible"><metadata
+ id="metadata26"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs24">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <linearGradient
+ id="linearGradient3166-6"><stop
+ id="stop3168-5"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" /><stop
+ id="stop3170-6"
+ style="stop-color:#ff0000;stop-opacity:1"
+ offset="1" /></linearGradient><linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient3172-9"
+ xlink:href="#linearGradient3166-6"
+ gradientUnits="userSpaceOnUse" /><linearGradient
+ id="linearGradient3166"><stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" /><stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" /></linearGradient><linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </defs>
+
+
+
+
+<g
+ transform="matrix(1.1181651,0,0,1.1181651,61.470731,-23.367585)"
+ id="g3348"
+ style="fill:none;stroke:#00ff00;stroke-opacity:1"><path
+ d="m -19.754174,46.744351 c 3.555069,0 8.83424,-1.56838 8.83424,-6.181226 0,-5.131219 -4.597011,-5.60538 -6.503378,-6.124857 -2.107038,-0.441556 -3.510542,-1.057744 -3.594847,-1.909356 -0.144269,-1.460061 0.687503,-2.028723 2.342736,-2.028723 0,0 3.937412,2.024856 7.282311,0.40895 0.942794,-0.454819 2.631273,-2.579702 2.631273,-4.04529 0,-1.466142 -5.450749,-3.160522 -7.104794,-3.160522 -1.655233,0 -3.062894,2.125988 -3.062894,2.125988 -3.309277,0 -6.619149,2.932283 -6.619149,5.864566 0,2.93173 3.166197,5.225166 6.950433,5.864565 1.759131,0.259187 3.230316,1.226851 2.896064,3.005231 -0.27132,1.444036 -1.778128,2.932283 -4.963917,2.932283 -2.524407,0 -7.896195,-0.121026 -8.75409,-2.254199 -0.551547,-1.373851 0.09974,-2.876467 0.927358,-2.876467 l -0.01603,-0.08842 c -0.843052,-0.08732 -3.293841,0.08842 -3.293841,3.020151 -5.94e-4,3.759026 5.428782,5.447327 12.048526,5.447327 z"
+ id="path2474"
+ style="fill:none;stroke:#00ff00;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
+ d="m -11.393706,30.909692 c -1.557272,-0.158607 -3.924943,-1.105272 -4.43315,-2.775335"
+ id="path2476"
+ style="fill:none;stroke:#00ff00;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><circle
+ cx="35.805"
+ cy="10.96"
+ r="1.676"
+ transform="matrix(0.59369893,0,0,0.5526353,-36.672813,19.93767)"
+ id="circle2478"
+ style="fill:none;stroke:#00ff00;stroke-width:3.42034841;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g
+ transform="translate(-2.2743816,2.513511)"
+ id="g3036"><path
+ d="M 9.5689397,38.952218 9.3902171,32.897964 M 25.663656,50.969805 3.6345541,51.23649 3.5243801,39.2454 l 5.851406,-6.358972 21.2578909,0 -0.110174,12.124478 z M 3.6345541,39.378743 l 21.4782339,0.266685 0.33052,10.924347 m -0.306426,-11.052011 5.531132,-6.638311"
+ id="path3806"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /><g
+ transform="matrix(0,-1,-1,0,47.3087,85.9835)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ id="polyline4774"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg> \ No newline at end of file
diff --git a/icons/resize+.svg b/icons/resize+.svg
new file mode 100644
index 0000000..0fae3c3
--- /dev/null
+++ b/icons/resize+.svg
@@ -0,0 +1,41 @@
+<?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="55"
+ height="54.695999"
+ viewBox="0 0 55 54.696"
+ id="svg2"
+ xml:space="preserve"><defs
+ id="defs21">
+
+
+
+
+ </defs><g
+ transform="translate(-0.4354,0)"
+ id="g7">
+ <g
+ id="g9">
+ <path
+ d="m 25.263,12.435 h 24.656 v 3.562 H 39.575 V 41.59 H 35.606 V 15.996 H 25.263 v -3.561 z"
+ id="path11"
+ style="fill:#ffffff" />
+ </g>
+ </g><g
+ transform="translate(-8.4356,0)"
+ id="g13">
+ <g
+ id="g15">
+ <path
+ d="m 13.953,24.435 h 16.656 v 3.562 H 24.265 V 41.59 H 20.296 V 27.997 h -6.344 v -3.562 z"
+ id="path17"
+ style="fill:#ffffff" />
+ </g>
+ </g><path
+ d="m 25.5774,23.677763 6,-6.000001 -4,0"
+ id="path3618"
+ style="fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></svg> \ No newline at end of file
diff --git a/icons/resize-.svg b/icons/resize-.svg
new file mode 100644
index 0000000..e3b719e
--- /dev/null
+++ b/icons/resize-.svg
@@ -0,0 +1,43 @@
+<?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="55"
+ height="54.695999"
+ viewBox="0 0 55 54.696"
+ id="svg2"
+ xml:space="preserve"><defs
+ id="defs21">
+
+
+
+
+ </defs><g
+ transform="translate(3.5644,0)"
+ id="g2821"><g
+ transform="matrix(-1,0,0,1,51.871,0)"
+ id="g7">
+ <g
+ id="g9">
+ <path
+ d="m 25.263,12.435 h 24.656 v 3.562 H 39.575 V 41.59 H 35.606 V 15.996 H 25.263 v -3.561 z"
+ id="path11"
+ style="fill:#ffffff" />
+ </g>
+ </g><g
+ transform="matrix(-1,0,0,1,59.8712,0)"
+ id="g13">
+ <g
+ id="g15">
+ <path
+ d="m 13.953,24.435 h 16.656 v 3.562 H 24.265 V 41.59 H 20.296 V 27.997 h -6.344 v -3.562 z"
+ id="path17"
+ style="fill:#ffffff" />
+ </g>
+ </g><path
+ d="m 19.8582,17.677762 6,6.000001 -4,0"
+ id="path3618"
+ style="fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></svg> \ No newline at end of file
diff --git a/icons/run-fastoff.svg b/icons/run-fastoff.svg
new file mode 100644
index 0000000..5abee4e
--- /dev/null
+++ b/icons/run-fastoff.svg
@@ -0,0 +1,36 @@
+<?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.0"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs5" />
+` <g
+ transform="translate(6.0193572e-8,-1.9999998)"
+ id="g3287">
+ <path
+ d="m 37.158177,18.217157 c 0,7.129359 -4.73972,12.908847 -10.586461,12.908847 -5.846741,0 -10.586461,-5.779488 -10.586461,-12.908847 0,-7.129359 4.73972,-12.908847 10.586461,-12.908847 5.846741,0 10.586461,5.779488 10.586461,12.908847 l 0,0 z"
+ transform="matrix(0.9002849,0,0,0.6378505,-7.1526774,12.961276)"
+ id="path3173"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 37.158177,18.217157 c 0,7.129359 -4.73972,12.908847 -10.586461,12.908847 -5.846741,0 -10.586461,-5.779488 -10.586461,-12.908847 0,-7.129359 4.73972,-12.908847 10.586461,-12.908847 5.846741,0 10.586461,5.779488 10.586461,12.908847 l 0,0 z"
+ transform="matrix(0.34904,-0.8298699,0.4328947,0.1820738,8.5363479,48.352162)"
+ id="path3177"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 37.158177,18.217157 c 0,7.129359 -4.73972,12.908847 -10.586461,12.908847 -5.846741,0 -10.586461,-5.779488 -10.586461,-12.908847 0,-7.129359 4.73972,-12.908847 10.586461,-12.908847 5.846741,0 10.586461,5.779488 10.586461,12.908847 l 0,0 z"
+ transform="matrix(0.5612942,0.3544048,-0.1085241,0.1718767,9.8942224,-2.0522731)"
+ id="path3179"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 9.2958401,27.300367 c 0,0 0.6438297,-3.994201 1.6400319,-5.238518 0.833257,-1.040794 2.516695,-1.85759 3.762426,-2.031262 0.9968,-0.138967 2.383402,0.2462 3.280063,0.74836 1.042545,0.583861 2.271254,1.791474 2.894173,2.88653 0.98159,1.725581 1.256974,4.552031 1.736504,6.52142 0.380364,1.562127 0.284039,3.94213 1.157669,5.238518 0.721744,1.071004 2.287173,2.00041 3.473008,2.245082 1.78687,0.368683 4.166897,-1.188245 5.981292,-1.069088 0.84132,0.05525 2.27015,0.05272 2.701229,0.85527 0.272959,0.50817 -0.01689,1.497122 -0.38589,1.924349 -1.388344,1.60742 -4.484134,1.551863 -6.463654,1.924357 -1.54502,0.290727 -3.77056,1.115533 -5.209513,0.427635 -2.191386,-1.047611 -3.11186,-5.248556 -5.016567,-6.842148 C 18.494794,34.59652 17.932313,34.319138 17.495998,34.249421 16.284393,34.055824 14.659189,34.94909 13.444155,34.783964 12.553171,34.662876 11.3895,34.166119 10.646456,33.607971 9.4764934,32.729136 8.0161145,31.200596 7.4628632,29.759263 6.9180297,28.339853 7.0795774,26.166629 7.1734442,24.627654 c 0.04976,-0.815832 0.186147,-1.923803 0.4823635,-2.672713 0.4740893,-1.198617 1.3821263,-2.725983 2.3153404,-3.527981 1.3038139,-1.120492 3.4728879,-2.04627 5.1130389,-2.245079 1.631802,-0.197799 3.828032,0.447353 5.402457,0.962176 1.516337,0.49583 3.246091,2.236482 4.823622,2.351988 0.932208,0.06826 2.407938,-0.152939 2.990646,-0.962177 0.30741,-0.426916 0.343158,-1.344606 0.09647,-1.817445 -0.550654,-1.055475 -2.224944,-1.472146 -3.280064,-1.817445 -1.00539,-0.329024 -2.45155,-0.166363 -3.473007,-0.427634 -0.838456,-0.214462 -1.933408,-0.638591 -2.701229,-1.069086 -1.24629,-0.698755 -2.959159,-1.702648 -3.858898,-2.886529 -0.817776,-1.0760346 -2.317397,-3.0254485 -1.736505,-4.2763406 0.06753,-0.1454133 0.336585,-0.081478 0.482363,-0.1069088 0.748264,-0.1305312 2.147702,0.4174844 2.508284,-0.3207253 0.338874,-0.6937639 -1.483172,-1.7166452 -1.061197,-2.351988 0.851763,-1.2824449 3.329342,0.4862662 4.72715,0.8552682 1.121926,0.2961731 2.62592,0.7305046 3.665953,1.2829032 1.693816,0.8996449 3.826771,2.4399257 5.209512,3.8487057 1.197868,1.2204256 1.98772,3.7643616 3.376536,4.7039756 0.62978,0.426082 1.725087,0.235405 2.411811,0.534543 0.411153,0.179097 0.884057,0.587915 1.254142,0.855267 0.854101,0.61701 2.099953,1.314356 2.797701,2.13817 0.577461,0.681796 0,0.106909 1.447086,2.672714 1.447087,2.565804 1.501279,0.32572 1.832977,2.245079 0,0 -0.32328,1.512069 -0.675307,2.031262 -0.41031,0.605152 -1.195498,1.224871 -1.832977,1.496719 -0.271455,0.11576 -0.68249,0.18483 -0.964724,0.106908 -0.405023,-0.111822 -0.753667,-0.738998 -1.157669,-0.855267 -0.70255,-0.202189 -1.686925,0.255337 -2.411811,0.213817 -0.793655,-0.04546 -1.927833,-0.07316 -2.604757,-0.534543 -0.650834,-0.443601 -1.191753,-1.493431 -1.543559,-2.245079 -0.26551,-0.567275 -0.0089,-2.128616 -0.578834,-2.031262 -0.0286,0.0049 0,0.106909 0,0.106909 m 2.604756,4.383249 c 0,0 -0.67407,3.00661 -0.964725,4.294669 -0.259371,1.14942 -0.38119,2.775651 -0.868252,3.830379 -0.654999,1.418394 -3.087119,4.062526 -3.087119,4.062526 m -0.578834,-7.37669 c 0,0 -0.762223,2.02329 -1.254142,2.779621 -0.526506,0.80951 -1.777805,1.430082 -2.122394,2.351988 -0.01108,0.02963 0,0.106908 0,0.106908 m -3.858898,1.710538 c 0,0 -2.498343,0.09087 -3.56948,0.106905 -0.694537,0.01041 -1.848045,-0.569505 -2.31534,0 -0.410734,0.500573 -0.129138,1.670316 0.192948,2.245079 0.377101,0.672936 1.341866,1.159966 2.025922,1.389809 0.803679,0.270031 1.960108,0.174507 2.797698,0.106913 0.968172,-0.07813 3.18359,-0.641452 3.18359,-0.641452 M 7.3663896,23.237843 c 0,0 -2.1337626,-2.804738 -3.2800638,-2.458896 -0.6495254,0.195963 -0.8281117,1.514613 -0.9647234,2.245079 -0.1645596,0.879906 -0.1889559,2.152344 0.096474,2.993439 0.3548095,1.045538 1.2844064,2.265735 2.1223913,2.88653 0.5024007,0.372186 1.3315344,0.544203 1.9294505,0.641451 0.028634,0.0047 0.096471,0 0.096471,0 M 29.651526,14.04371 c 0,0 -3.058348,-4.1388306 -4.72715,-5.4523336 C 23.678488,7.6107444 21.770009,6.6768097 20.293698,6.2393884 19.086245,5.8816272 16.145385,5.8117543 16.145385,5.8117543 M 35.343402,20.030587 c 0.276872,0.284129 1.079502,0.263788 1.254141,-0.106908 0.264175,-0.56075 -0.52371,-1.415829 -0.964724,-1.817446 -0.473808,-0.431478 -1.46214,-1.188454 -1.929449,-0.748359 -0.380706,0.358535 -0.198367,1.697687 0.289416,1.817445 0.344916,0.08468 0.327116,-1.002886 0.675308,-1.069086 0.245381,-0.04665 0.561385,0.289224 0.675308,0.534543 0.172161,0.370733 -0.276058,1.106519 0,1.389811 z m -0.09647,-0.106908 c 0,0 0,0 0,0 z"
+ id="path2384"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/icons/run-faston.svg b/icons/run-faston.svg
new file mode 100644
index 0000000..33e8bbd
--- /dev/null
+++ b/icons/run-faston.svg
@@ -0,0 +1,40 @@
+<?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.0"
+ width="45"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs5" />
+ <path
+ d="M 27.849263,20.597427 C 27.851349,29.203268 22.048546,36.18101 14.889706,36.18101 C 7.7308648,36.18101 1.9280624,29.203268 1.9301481,20.597427 C 1.9280624,11.991586 7.7308648,5.0138442 14.889706,5.0138442 C 22.048546,5.0138442 27.851349,11.991586 27.849263,20.597427 L 27.849263,20.597427 z"
+ transform="matrix(1.6011349,0,0,1.3319174,-1.3404274,-4.9340719)"
+ id="path2401"
+ style="opacity:1;fill:#00d000;fill-opacity:1;stroke:#00d000;stroke-width:2.39670968;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <g
+ transform="translate(6.0193572e-8,-1.9999998)"
+ id="g3287">
+ <path
+ d="M 37.158177,18.217157 C 37.158177,25.346516 32.418457,31.126004 26.571716,31.126004 C 20.724975,31.126004 15.985255,25.346516 15.985255,18.217157 C 15.985255,11.087798 20.724975,5.30831 26.571716,5.30831 C 32.418457,5.30831 37.158177,11.087798 37.158177,18.217157 L 37.158177,18.217157 z"
+ transform="matrix(0.9002849,0,0,0.6378505,-7.1526774,12.961276)"
+ id="path3173"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 37.158177,18.217157 C 37.158177,25.346516 32.418457,31.126004 26.571716,31.126004 C 20.724975,31.126004 15.985255,25.346516 15.985255,18.217157 C 15.985255,11.087798 20.724975,5.30831 26.571716,5.30831 C 32.418457,5.30831 37.158177,11.087798 37.158177,18.217157 L 37.158177,18.217157 z"
+ transform="matrix(0.34904,-0.8298699,0.4328947,0.1820738,8.5363479,48.352162)"
+ id="path3177"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 37.158177,18.217157 C 37.158177,25.346516 32.418457,31.126004 26.571716,31.126004 C 20.724975,31.126004 15.985255,25.346516 15.985255,18.217157 C 15.985255,11.087798 20.724975,5.30831 26.571716,5.30831 C 32.418457,5.30831 37.158177,11.087798 37.158177,18.217157 L 37.158177,18.217157 z"
+ transform="matrix(0.5612942,0.3544048,-0.1085241,0.1718767,9.8942224,-2.0522731)"
+ id="path3179"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3.5;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 9.2958401,27.300367 C 9.2958401,27.300367 9.9396698,23.306166 10.935872,22.061849 C 11.769129,21.021055 13.452567,20.204259 14.698298,20.030587 C 15.695098,19.89162 17.0817,20.276787 17.978361,20.778947 C 19.020906,21.362808 20.249615,22.570421 20.872534,23.665477 C 21.854124,25.391058 22.129508,28.217508 22.609038,30.186897 C 22.989402,31.749024 22.893077,34.129027 23.766707,35.425415 C 24.488451,36.496419 26.05388,37.425825 27.239715,37.670497 C 29.026585,38.03918 31.406612,36.482252 33.221007,36.601409 C 34.062327,36.656661 35.491157,36.654128 35.922236,37.456679 C 36.195195,37.964849 35.905348,38.953801 35.536346,39.381028 C 34.148002,40.988448 31.052212,40.932891 29.072692,41.305385 C 27.527672,41.596112 25.302132,42.420918 23.863179,41.73302 C 21.671793,40.685409 20.751319,36.484464 18.846612,34.890872 C 18.494794,34.59652 17.932313,34.319138 17.495998,34.249421 C 16.284393,34.055824 14.659189,34.94909 13.444155,34.783964 C 12.553171,34.662876 11.3895,34.166119 10.646456,33.607971 C 9.4764934,32.729136 8.0161145,31.200596 7.4628632,29.759263 C 6.9180297,28.339853 7.0795774,26.166629 7.1734442,24.627654 C 7.2232038,23.811822 7.3595912,22.703851 7.6558077,21.954941 C 8.129897,20.756324 9.037934,19.228958 9.9711481,18.42696 C 11.274962,17.306468 13.444036,16.38069 15.084187,16.181881 C 16.715989,15.984082 18.912219,16.629234 20.486644,17.144057 C 22.002981,17.639887 23.732735,19.380539 25.310266,19.496045 C 26.242474,19.564301 27.718204,19.343106 28.300912,18.533868 C 28.608322,18.106952 28.64407,17.189262 28.397385,16.716423 C 27.846731,15.660948 26.172441,15.244277 25.117321,14.898978 C 24.111931,14.569954 22.665771,14.732615 21.644314,14.471344 C 20.805858,14.256882 19.710906,13.832753 18.943085,13.402258 C 17.696795,12.703503 15.983926,11.69961 15.084187,10.515729 C 14.266411,9.4396944 12.76679,7.4902805 13.347682,6.2393884 C 13.41521,6.0939751 13.684267,6.1579103 13.830045,6.1324796 C 14.578309,6.0019484 15.977747,6.549964 16.338329,5.8117543 C 16.677203,5.1179904 14.855157,4.0951091 15.277132,3.4597663 C 16.128895,2.1773214 18.606474,3.9460325 20.004282,4.3150345 C 21.126208,4.6112076 22.630202,5.0455391 23.670235,5.5979377 C 25.364051,6.4975826 27.497006,8.0378634 28.879747,9.4466434 C 30.077615,10.667069 30.867467,13.211005 32.256283,14.150619 C 32.886063,14.576701 33.98137,14.386024 34.668094,14.685162 C 35.079247,14.864259 35.552151,15.273077 35.922236,15.540429 C 36.776337,16.157439 38.022189,16.854785 38.719937,17.678599 C 39.297398,18.360395 38.719937,17.785508 40.167023,20.351313 C 41.61411,22.917117 41.668302,20.677033 42,22.596392 C 42,22.596392 41.67672,24.108461 41.324693,24.627654 C 40.914383,25.232806 40.129195,25.852525 39.491716,26.124373 C 39.220261,26.240133 38.809226,26.309203 38.526992,26.231281 C 38.121969,26.119459 37.773325,25.492283 37.369323,25.376014 C 36.666773,25.173825 35.682398,25.631351 34.957512,25.589831 C 34.163857,25.544373 33.029679,25.516672 32.352755,25.055288 C 31.701921,24.611687 31.161002,23.561857 30.809196,22.810209 C 30.543686,22.242934 30.800291,20.681593 30.230362,20.778947 C 30.201757,20.783833 30.230362,20.885856 30.230362,20.885856 M 32.835118,25.269105 C 32.835118,25.269105 32.161048,28.275715 31.870393,29.563774 C 31.611022,30.713194 31.489203,32.339425 31.002141,33.394153 C 30.347142,34.812547 27.915022,37.456679 27.915022,37.456679 M 27.336188,30.079989 C 27.336188,30.079989 26.573965,32.103279 26.082046,32.85961 C 25.55554,33.66912 24.304241,34.289692 23.959652,35.211598 C 23.948576,35.241229 23.959652,35.318506 23.959652,35.318506 M 20.100754,37.029044 C 20.100754,37.029044 17.602411,37.119914 16.531274,37.135949 C 15.836737,37.146354 14.683229,36.566444 14.215934,37.135949 C 13.8052,37.636522 14.086796,38.806265 14.408882,39.381028 C 14.785983,40.053964 15.750748,40.540994 16.434804,40.770837 C 17.238483,41.040868 18.394912,40.945344 19.232502,40.87775 C 20.200674,40.799617 22.416092,40.236298 22.416092,40.236298 M 7.3663896,23.237843 C 7.3663896,23.237843 5.232627,20.433105 4.0863258,20.778947 C 3.4368004,20.97491 3.2582141,22.29356 3.1216024,23.024026 C 2.9570428,23.903932 2.9326465,25.17637 3.2180769,26.017465 C 3.5728864,27.063003 4.5024833,28.2832 5.3404682,28.903995 C 5.8428689,29.276181 6.6720026,29.448198 7.2699187,29.545446 C 7.2985527,29.550103 7.3663896,29.545446 7.3663896,29.545446 M 29.651526,14.04371 C 29.651526,14.04371 26.593178,9.9048794 24.924376,8.5913764 C 23.678488,7.6107444 21.770009,6.6768097 20.293698,6.2393884 C 19.086245,5.8816272 16.145385,5.8117543 16.145385,5.8117543 M 35.343402,20.030587 C 35.620274,20.314716 36.422904,20.294375 36.597543,19.923679 C 36.861718,19.362929 36.073833,18.50785 35.632819,18.106233 C 35.159011,17.674755 34.170679,16.917779 33.70337,17.357874 C 33.322664,17.716409 33.505003,19.055561 33.992786,19.175319 C 34.337702,19.260001 34.319902,18.172433 34.668094,18.106233 C 34.913475,18.059581 35.229479,18.395457 35.343402,18.640776 C 35.515563,19.011509 35.067344,19.747295 35.343402,20.030587 z M 35.246929,19.923679 C 35.246929,19.923679 35.246929,19.923679 35.246929,19.923679 z"
+ id="path2384"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/icons/run-slowoff.svg b/icons/run-slowoff.svg
new file mode 100644
index 0000000..4402892
--- /dev/null
+++ b/icons/run-slowoff.svg
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="45"
+ height="45"
+ id="svg2">
+ <metadata
+ id="metadata8">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs6" />
+ <path
+ d=""
+ id="path2385"
+ style="fill:#00d000;fill-opacity:1;stroke:#00d000;stroke-width:1.22727275;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="translate(0.11089571,-0.94841151)"
+ id="g3077">
+ <g
+ transform="matrix(1.2218314,-0.18194175,0.15605166,1.5079605,-13.378376,-6.429867)"
+ id="g3947-5">
+ <path
+ d="m 30.250756,30.836427 c 1.11555,0.348252 2.692035,-1.295228 2.692035,-1.295228 0,0 2.293876,-7.854931 3.487289,-9.113037 0.31287,-0.329828 0.755177,-0.501178 1.181359,-0.58803 1.01523,-0.206889 2.083898,0.322127 3.11125,0.193235 0.80574,-0.101093 1.661718,-0.246659 2.327669,-0.749722 0.438663,-0.331367 0.933055,-0.803906 0.976338,-1.401899 0.124424,-1.719054 -1.05927,-3.420325 -2.178863,-4.644042 -1.215594,-1.328645 -2.936447,-1.97793 -4.551658,-2.518425 -1.300507,-0.104493 -2.372926,0.194116 -2.868561,0.847395 -0.59567,0.767816 -1.129763,1.661019 -1.074287,2.552259 0.07463,1.198927 -0.664185,3.248385 -0.664185,3.248385 l -2.780379,6.877993 c 0,0 -0.145087,0.723462 -0.668916,1.136807 -1.096253,0.865034 -0.619332,3.445317 -0.619332,3.445317 0,0 0.514691,1.660737 1.630241,2.008992 z"
+ id="path3866-7"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 37.838255,17.606462 c 0,0 0.830992,0.285742 1.214045,0.144799 0.309562,-0.113904 0.679069,-0.727877 0.679069,-0.727877"
+ id="path3868-2"
+ style="fill:none;stroke:#000000;stroke-width:0.9375;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 39.151424,17.94393 0.713166,1.096988 1.013167,0.746988"
+ id="path3870-2"
+ style="fill:none;stroke:#000000;stroke-width:0.9375;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 42.62442,17.298048 a 0.04697844,0.16854352 9.385532 0 0 0.03495,0.227255 0.04697844,0.16854352 9.385532 1 0 -0.03495,-0.227255 z"
+ id="path3892-8"
+ style="fill:#ff0000;fill-opacity:1;stroke:#000000;stroke-width:0.63433635;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 35.475,18.575 c 0,0 0.71966,0.565523 1.15,0.725 0.894503,0.396029 0.867077,0.407999 1.75,0.475"
+ id="path3905-2"
+ style="fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 38.35,13.8 c 0,0 -0.45201,-0.593612 -0.15,-0.85 0.375408,-0.318698 0.763331,0.09062 1.1,0.45 0.230721,0.246285 0.559852,0.685707 0.35,0.95 -0.167112,0.210465 -0.5,0.1 -0.5,0.1"
+ id="path3907-5"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(1.0277554,0,0,1,-0.03770427,0)"
+ id="g4087">
+ <path
+ d="M 3.233403,33.550298 C 5.096176,29.257229 5.051022,24.714332 6.458889,21.039 c 1.407868,-3.675332 5.077225,-6.720428 10.159991,-6.730298 5.082766,-0.0099 8.441665,3.273969 10.488217,6.730173 2.046552,3.456203 2.66156,11.657273 3.461887,11.905179 2.206168,0.435816 2.113926,2.297599 0.605863,3.135182 -7.133959,0.45665 -4.920065,0.02737 -10.314422,0.475485 l -9.324283,-0.04075 C 5.6539971,36.08591 8.7574769,36.860266 2.459469,36.041947 1.811911,35.45684 1.274155,35.258079 1.35845,34.327323 c 0.126607,-1.39795 0.895175,-1.113719 2.265315,-2.10834"
+ id="path3856-8"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 30.55,34 C 30.1,31.9 30.1,31.9 30.1,31.9"
+ id="path4009"
+ style="fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ d="m 6.6596827,35.622429 c -0.471371,1.233059 0.02336,2.603048 6.31e-4,3.904339 -0.02031,1.162467 -0.890882,2.559307 -0.122396,3.486139 0.82847,0.999176 2.628998,0.796844 3.9963373,0.902395 1.213468,0.09368 2.712886,0.471707 3.643453,-0.245805 0.564612,-0.435344 0.793241,-1.334241 0.531969,-1.965542 -0.235451,-0.568908 -1.278555,-0.529254 -1.642328,-1.038668 -0.611228,-0.855945 -0.159994,-2.0855 -0.636318,-3.010187 -0.558181,-1.083595 -1.439242,-2.069797 -2.5000913,-2.773487 -0.582305,-0.38626 -1.314901,-0.892712 -2.009537,-0.720895 -0.655669,0.162182 -1.039812,0.881221 -1.26172,1.461711 z"
+ id="path3876-8"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.35621119;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 21.91196,35.199701 c -0.471371,1.295915 0.02336,2.735739 6.31e-4,4.103363 -0.02031,1.221722 -0.890882,2.689766 -0.122396,3.663844 0.82847,1.050109 2.628998,0.837463 3.996337,0.948394 1.213468,0.09845 2.712886,0.495753 3.643453,-0.258336 0.564612,-0.457536 0.793241,-1.402253 0.531969,-2.065733 -0.235451,-0.59791 -1.278555,-0.556234 -1.642328,-1.091616 -0.611228,-0.899574 -0.159994,-2.191807 -0.636318,-3.163629 -0.558181,-1.13883 -1.439242,-2.175305 -2.500091,-2.914865 -0.582305,-0.405949 -1.314901,-0.938217 -2.009537,-0.757642 -0.655669,0.170448 -1.039812,0.926141 -1.26172,1.53622 z"
+ id="path3876-8-1"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.39034784;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g3019">
+ <path
+ d="m 11.777666,17.658989 c 0,0 3.082843,-1.459186 4.783966,-1.539572 1.878187,-0.08875 5.311177,1.24513 5.311177,1.24513 l -2.902353,1.472208 -4.626941,0.12619 z"
+ id="path3005"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 10.238095,19.150806 2.75102,1.295574 -0.515985,4.436234 -4.4335433,-0.737528 c 0,0 0.4347249,-3.264183 2.1985083,-4.99428 z"
+ id="path3007"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 14.764146,20.788421 -0.294442,4.273672 5.342013,-0.04206 -0.6534,-4.399862 z"
+ id="path3009"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 20.784704,20.294891 2.529477,-1.536726 c 0,0 1.090846,1.107713 1.672719,1.843818 0.581873,0.736104 1.162427,2.281211 1.162427,2.281211 l -4.276518,1.800331 z"
+ id="path3011"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 7.6189557,25.965027 -1.5730973,6.146591 5.8608426,0.218854 0.628101,-5.585855 z"
+ id="path3013"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="M 14.466859,27.013759 14.029465,32.21551 21.289462,32.15099 20.08655,26.84835 z"
+ id="path3015"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 22.049443,26.823052 1.578789,5.089634 5.128852,-0.04791 -1.730123,-7.142032 z"
+ id="path3017"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/run-slowon.svg b/icons/run-slowon.svg
new file mode 100644
index 0000000..000f7d0
--- /dev/null
+++ b/icons/run-slowon.svg
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="45"
+ height="45"
+ id="svg2">
+ <metadata
+ id="metadata8">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs6" />
+ <path
+ d="m -22.616925,21.840856 c 0,4.547465 -4.267099,8.233914 -9.530831,8.233914 -5.263733,0 -9.530831,-3.686449 -9.530831,-8.233914 0,-4.547466 4.267098,-8.233915 9.530831,-8.233915 5.263732,0 9.530831,3.686449 9.530831,8.233915 l 0,0 z"
+ id="path3173"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m -19.525045,18.092333 c 3.086262,1.29807 3.933819,6.283714 1.893073,11.135749 -2.040747,4.852034 -6.197008,7.733092 -9.28327,6.435022 -3.086262,-1.298069 -3.933819,-6.283714 -1.893073,-11.135748 2.040746,-4.852034 6.197008,-7.733092 9.28327,-6.435023 l 0,0 z"
+ id="path3177"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m -20.143303,11.507625 c -0.773708,1.225371 -4.061299,0.538951 -7.34304,-1.533163 -3.281742,-2.072113 -5.314906,-4.745251 -4.541199,-5.970622 0.773708,-1.225371 4.061299,-0.538951 7.343041,1.533162 3.281741,2.072113 5.314905,4.745252 4.541198,5.970623 l 0,0 z"
+ id="path3179"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="M 42.75,22.5 C 42.75,33.683767 33.683766,42.75 22.5,42.75 11.316234,42.75 2.2500001,33.683767 2.2500001,22.5 2.2500001,11.316234 11.316234,2.2499999 22.5,2.2499999 c 11.183766,0 20.25,9.0662341 20.25,20.2500001 l 0,0 z"
+ id="path2387"
+ style="fill:#00d000;fill-opacity:1;stroke:#00d000;stroke-width:3.50000024;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none" />
+ <path
+ d=""
+ id="path2385"
+ style="fill:#00d000;fill-opacity:1;stroke:#00d000;stroke-width:1.22727275;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="translate(0.11089571,-0.94841151)"
+ id="g3077">
+ <g
+ transform="matrix(1.2218314,-0.18194175,0.15605166,1.5079605,-13.378376,-6.429867)"
+ id="g3947-5">
+ <path
+ d="m 30.250756,30.836427 c 1.11555,0.348252 2.692035,-1.295228 2.692035,-1.295228 0,0 2.293876,-7.854931 3.487289,-9.113037 0.31287,-0.329828 0.755177,-0.501178 1.181359,-0.58803 1.01523,-0.206889 2.083898,0.322127 3.11125,0.193235 0.80574,-0.101093 1.661718,-0.246659 2.327669,-0.749722 0.438663,-0.331367 0.933055,-0.803906 0.976338,-1.401899 0.124424,-1.719054 -1.05927,-3.420325 -2.178863,-4.644042 -1.215594,-1.328645 -2.936447,-1.97793 -4.551658,-2.518425 -1.300507,-0.104493 -2.372926,0.194116 -2.868561,0.847395 -0.59567,0.767816 -1.129763,1.661019 -1.074287,2.552259 0.07463,1.198927 -0.664185,3.248385 -0.664185,3.248385 l -2.780379,6.877993 c 0,0 -0.145087,0.723462 -0.668916,1.136807 -1.096253,0.865034 -0.619332,3.445317 -0.619332,3.445317 0,0 0.514691,1.660737 1.630241,2.008992 z"
+ id="path3866-7"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 37.838255,17.606462 c 0,0 0.830992,0.285742 1.214045,0.144799 0.309562,-0.113904 0.679069,-0.727877 0.679069,-0.727877"
+ id="path3868-2"
+ style="fill:none;stroke:#000000;stroke-width:0.9375;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 39.151424,17.94393 0.713166,1.096988 1.013167,0.746988"
+ id="path3870-2"
+ style="fill:none;stroke:#000000;stroke-width:0.9375;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 42.62442,17.298048 a 0.04697844,0.16854352 9.385532 0 0 0.03495,0.227255 0.04697844,0.16854352 9.385532 1 0 -0.03495,-0.227255 z"
+ id="path3892-8"
+ style="fill:#ff0000;fill-opacity:1;stroke:#000000;stroke-width:0.63433635;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 35.475,18.575 c 0,0 0.71966,0.565523 1.15,0.725 0.894503,0.396029 0.867077,0.407999 1.75,0.475"
+ id="path3905-2"
+ style="fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 38.35,13.8 c 0,0 -0.45201,-0.593612 -0.15,-0.85 0.375408,-0.318698 0.763331,0.09062 1.1,0.45 0.230721,0.246285 0.559852,0.685707 0.35,0.95 -0.167112,0.210465 -0.5,0.1 -0.5,0.1"
+ id="path3907-5"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(1.0277554,0,0,1,-0.03770427,0)"
+ id="g4087">
+ <path
+ d="M 3.233403,33.550298 C 5.096176,29.257229 5.051022,24.714332 6.458889,21.039 c 1.407868,-3.675332 5.077225,-6.720428 10.159991,-6.730298 5.082766,-0.0099 8.441665,3.273969 10.488217,6.730173 2.046552,3.456203 2.66156,11.657273 3.461887,11.905179 2.206168,0.435816 2.113926,2.297599 0.605863,3.135182 -7.133959,0.45665 -4.920065,0.02737 -10.314422,0.475485 l -9.324283,-0.04075 C 5.6539971,36.08591 8.7574769,36.860266 2.459469,36.041947 1.811911,35.45684 1.274155,35.258079 1.35845,34.327323 c 0.126607,-1.39795 0.895175,-1.113719 2.265315,-2.10834"
+ id="path3856-8"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 30.55,34 C 30.1,31.9 30.1,31.9 30.1,31.9"
+ id="path4009"
+ style="fill:none;stroke:#000000;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ d="m 6.6596827,35.622429 c -0.471371,1.233059 0.02336,2.603048 6.31e-4,3.904339 -0.02031,1.162467 -0.890882,2.559307 -0.122396,3.486139 0.82847,0.999176 2.628998,0.796844 3.9963373,0.902395 1.213468,0.09368 2.712886,0.471707 3.643453,-0.245805 0.564612,-0.435344 0.793241,-1.334241 0.531969,-1.965542 -0.235451,-0.568908 -1.278555,-0.529254 -1.642328,-1.038668 -0.611228,-0.855945 -0.159994,-2.0855 -0.636318,-3.010187 -0.558181,-1.083595 -1.439242,-2.069797 -2.5000913,-2.773487 -0.582305,-0.38626 -1.314901,-0.892712 -2.009537,-0.720895 -0.655669,0.162182 -1.039812,0.881221 -1.26172,1.461711 z"
+ id="path3876-8"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.35621119;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 21.91196,35.199701 c -0.471371,1.295915 0.02336,2.735739 6.31e-4,4.103363 -0.02031,1.221722 -0.890882,2.689766 -0.122396,3.663844 0.82847,1.050109 2.628998,0.837463 3.996337,0.948394 1.213468,0.09845 2.712886,0.495753 3.643453,-0.258336 0.564612,-0.457536 0.793241,-1.402253 0.531969,-2.065733 -0.235451,-0.59791 -1.278555,-0.556234 -1.642328,-1.091616 -0.611228,-0.899574 -0.159994,-2.191807 -0.636318,-3.163629 -0.558181,-1.13883 -1.439242,-2.175305 -2.500091,-2.914865 -0.582305,-0.405949 -1.314901,-0.938217 -2.009537,-0.757642 -0.655669,0.170448 -1.039812,0.926141 -1.26172,1.53622 z"
+ id="path3876-8-1"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.39034784;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ id="g3019">
+ <path
+ d="m 11.777666,17.658989 c 0,0 3.082843,-1.459186 4.783966,-1.539572 1.878187,-0.08875 5.311177,1.24513 5.311177,1.24513 l -2.902353,1.472208 -4.626941,0.12619 z"
+ id="path3005"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 10.238095,19.150806 2.75102,1.295574 -0.515985,4.436234 -4.4335433,-0.737528 c 0,0 0.4347249,-3.264183 2.1985083,-4.99428 z"
+ id="path3007"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 14.764146,20.788421 -0.294442,4.273672 5.342013,-0.04206 -0.6534,-4.399862 z"
+ id="path3009"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 20.784704,20.294891 2.529477,-1.536726 c 0,0 1.090846,1.107713 1.672719,1.843818 0.581873,0.736104 1.162427,2.281211 1.162427,2.281211 l -4.276518,1.800331 z"
+ id="path3011"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 7.6189557,25.965027 -1.5730973,6.146591 5.8608426,0.218854 0.628101,-5.585855 z"
+ id="path3013"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="M 14.466859,27.013759 14.029465,32.21551 21.289462,32.15099 20.08655,26.84835 z"
+ id="path3015"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="m 22.049443,26.823052 1.578789,5.089634 5.128852,-0.04791 -1.730123,-7.142032 z"
+ id="path3017"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/stopitoff.svg b/icons/stopitoff.svg
new file mode 100644
index 0000000..de041ea
--- /dev/null
+++ b/icons/stopitoff.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.0"
+ width="44"
+ height="44"
+ id="svg2">
+ <defs
+ id="defs5" />
+ <path
+ d="M 26.896078,11.635294 L 15.157342,23.955503 -1.8549019,24.366666 -14.175111,12.62793 -14.586274,-4.3843137 -2.8475378,-16.704523 14.164706,-17.115686 26.484915,-5.3769496 26.896078,11.635294 z"
+ transform="matrix(0.8982409,0.027775,-0.02744374,0.9090831,16.347508,18.533176)"
+ id="path3158"
+ style="fill:none;stroke:#ffffff;stroke-width:3.31833673;stroke-opacity:1" />
+ <path
+ d="m 16.987782,36.466398 0,-5.405882 c 0,0 -1.533767,-0.676356 -2.023529,-1.180393 -0.662593,-0.681907 -1.517647,-2.782352 -1.517647,-2.782352 l -0.0184,-11.631373 c 0,0 0.687852,-1.032868 1.171258,-0.977718 0.498837,0.05691 0.976374,0.977718 0.976374,0.977718 l 0,6 c 0,0 0.688624,0.580651 0.906061,0.572549 0.197418,-0.0074 0.656111,-0.572549 0.656111,-0.572549 l 0.08431,-9.75 c 0,0 0.580108,-0.90735 1.030166,-0.891176 0.375782,0.01351 0.927452,0.891176 0.927452,0.891176 l 0.168627,6.75 c 0,0 0.405694,0.699955 0.67451,0.705882 0.161921,0.0036 0.722023,-0.705882 0.722023,-0.705882 L 20.613273,8.4663984 c 0,0 0.829327,-0.9980533 1.180391,-0.9294114 0.344867,0.06743 1.011766,0.9294114 1.011766,0.9294114 l 0.08431,10.2499996 c 0,0 0.449781,0.481964 0.674511,0.455882 0.196236,-0.02277 0.761584,-0.455882 0.761584,-0.455882 l 0,-7.25 c 0,0 0.663398,-0.932951 1.111446,-0.978431 0.450161,-0.04569 1.138554,1.091102 1.138554,1.091102 l 0,13.017647 1.878611,0.308898 c 0,0 1.288603,-2.469107 2.192157,-3.203922 0.765333,-0.622407 2.030021,-1.230804 2.997738,-1.039365 0.386002,0.07636 0.92745,0.852337 0.92745,0.852337 0,0 -2.251119,2.381719 -2.941781,3.653091 -0.51581,0.949505 -0.756638,2.771202 -1.452489,3.59789 -0.54755,0.650503 -1.598008,1.07997 -2.332428,1.508447 -0.783172,0.45692 -2.388385,1.179635 -2.388385,1.179635 l -0.03756,5.012671 -8.431372,0 z"
+ id="path3162"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.5;stroke-opacity:1" />
+</svg>
diff --git a/icons/stopiton.svg b/icons/stopiton.svg
new file mode 100644
index 0000000..af01208
--- /dev/null
+++ b/icons/stopiton.svg
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ width="44"
+ height="44"
+ version="1.0"
+ sodipodi:docname="stopiton.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective9" />
+ </defs>
+ <sodipodi:namedview
+ inkscape:window-height="1000"
+ inkscape:window-width="1679"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="10"
+ inkscape:cx="24.924162"
+ inkscape:cy="3.0823126"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:current-layer="svg2" />
+ <path
+ sodipodi:type="star"
+ style="opacity:1;fill:#d00000;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-opacity:1"
+ id="path2386"
+ sodipodi:sides="8"
+ sodipodi:cx="6.154902"
+ sodipodi:cy="3.6254902"
+ sodipodi:r1="22.234058"
+ sodipodi:r2="20.541592"
+ sodipodi:arg1="0.36853512"
+ sodipodi:arg2="0.7612342"
+ inkscape:flatsided="true"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="M 26.896078,11.635294 L 15.157342,23.955503 L -1.8549019,24.366666 L -14.175111,12.62793 L -14.586274,-4.3843137 L -2.8475378,-16.704523 L 14.164706,-17.115686 L 26.484915,-5.3769496 L 26.896078,11.635294 z"
+ transform="matrix(1.0676988,3.2621148e-2,-3.2621148e-2,1.0676988,15.546686,17.928289)" />
+ <path
+ sodipodi:type="star"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:3.31833665000000000;stroke-opacity:1"
+ id="path3158"
+ sodipodi:sides="8"
+ sodipodi:cx="6.154902"
+ sodipodi:cy="3.6254902"
+ sodipodi:r1="22.234058"
+ sodipodi:r2="20.541592"
+ sodipodi:arg1="0.36853512"
+ sodipodi:arg2="0.7612342"
+ inkscape:flatsided="true"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="M 26.896078,11.635294 L 15.157342,23.955503 L -1.8549019,24.366666 L -14.175111,12.62793 L -14.586274,-4.3843137 L -2.8475378,-16.704523 L 14.164706,-17.115686 L 26.484915,-5.3769496 L 26.896078,11.635294 z"
+ transform="matrix(0.8982409,2.7775002e-2,-2.7443742e-2,0.9090831,16.347508,18.533176)" />
+ <path
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.5;stroke-opacity:1"
+ d="M 16.987782,36.466398 L 16.987782,31.060516 C 16.987782,31.060516 15.454015,30.38416 14.964253,29.880123 C 14.30166,29.198216 13.446606,27.097771 13.446606,27.097771 L 13.428206,15.466398 C 13.428206,15.466398 14.116058,14.43353 14.599464,14.48868 C 15.098301,14.545591 15.575838,15.466398 15.575838,15.466398 L 15.575838,21.466398 C 15.575838,21.466398 16.264462,22.047049 16.481899,22.038947 C 16.679317,22.031591 17.13801,21.466398 17.13801,21.466398 L 17.222322,11.716398 C 17.222322,11.716398 17.80243,10.809048 18.252488,10.825222 C 18.62827,10.838727 19.17994,11.716398 19.17994,11.716398 L 19.348567,18.466398 C 19.348567,18.466398 19.754261,19.166353 20.023077,19.17228 C 20.184998,19.17585 20.7451,18.466398 20.7451,18.466398 L 20.613273,8.4663984 C 20.613273,8.4663984 21.4426,7.4683451 21.793664,7.536987 C 22.138531,7.604417 22.80543,8.4663984 22.80543,8.4663984 L 22.889742,18.716398 C 22.889742,18.716398 23.339523,19.198362 23.564253,19.17228 C 23.760489,19.149505 24.325837,18.716398 24.325837,18.716398 L 24.325837,11.466398 C 24.325837,11.466398 24.989235,10.533447 25.437283,10.487967 C 25.887444,10.442273 26.575837,11.579069 26.575837,11.579069 L 26.575837,24.596716 L 28.454448,24.905614 C 28.454448,24.905614 29.743051,22.436507 30.646605,21.701692 C 31.411938,21.079285 32.676626,20.470888 33.644343,20.662327 C 34.030345,20.738688 34.571793,21.514664 34.571793,21.514664 C 34.571793,21.514664 32.320674,23.896383 31.630012,25.167755 C 31.114202,26.11726 30.873374,27.938957 30.177523,28.765645 C 29.629973,29.416148 28.579515,29.845615 27.845095,30.274092 C 27.061923,30.731012 25.45671,31.453727 25.45671,31.453727 L 25.419154,36.466398 L 16.987782,36.466398 z"
+ id="path3162"
+ sodipodi:nodetypes="ccsccsccsccsccsccsccsccscccsscsssccc" />
+</svg>
diff --git a/icons/ta-open.svg b/icons/ta-open.svg
new file mode 100644
index 0000000..d7677e0
--- /dev/null
+++ b/icons/ta-open.svg
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 54.999998 55.000001"
+ id="Icon"
+ xml:space="preserve"
+ style="overflow:visible"><metadata
+ id="metadata26"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs24">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </defs>
+
+
+
+
+<g
+ transform="translate(2,-2)"
+ id="g3829"><path
+ d="m 39.046105,34.391197 c -0.384814,0 -0.764757,-0.02436 -1.139134,-0.06959 l 0.965863,1.634591 0.951946,-1.60954 c -0.258167,0.02088 -0.515638,0.04454 -0.778675,0.04454 z"
+ id="path11-7"
+ style="fill:none;stroke:#ffffff;stroke-width:2.08759975;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><g
+ transform="matrix(0.69586655,0,0,0.69586655,19.911862,0.79545554)"
+ id="g13-6"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="m 40.16,11.726 c -2.164,0 -3.958,1.555 -4.343,3.607 1.859,1.345 3.457,3.115 4.675,5.208 2.285,-0.172 4.094,-2.061 4.094,-4.39 0,-2.444 -1.982,-4.425 -4.426,-4.425 z"
+ id="path15-3"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 40.713,39.887 c -1.224,2.232 -2.86,4.131 -4.797,5.556 0.521,1.864 2.213,3.239 4.244,3.239 2.443,0 4.426,-1.98 4.426,-4.424 0,-2.255 -1.693,-4.096 -3.873,-4.371 z"
+ id="path17-1"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 14.273,39.871 c -2.253,0.206 -4.024,2.079 -4.024,4.387 0,2.443 1.98,4.424 4.424,4.424 2.064,0 3.784,-1.42 4.272,-3.332 -1.883,-1.416 -3.475,-3.289 -4.672,-5.479 z"
+ id="path19-7"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.026,15.437 c -0.343,-2.103 -2.154,-3.711 -4.353,-3.711 -2.444,0 -4.424,1.981 -4.424,4.424 0,2.382 1.886,4.31 4.245,4.406 1.186,-2.043 2.732,-3.784 4.532,-5.119 z"
+ id="path21-5"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g><path
+ d="m 39.046105,9.537627 c 1.327713,0 2.59419,0.2860012 3.770205,0.784937 0.515637,-0.7487519 0.819731,-1.6540743 0.819731,-2.6324627 0,-2.5656599 -2.079945,-4.6463009 -4.646301,-4.6463009 -2.56566,0 -4.645605,2.080641 -4.645605,4.6463009 0,0.9936975 0.314531,1.9129372 0.846173,2.6679527 1.199674,-0.5212044 2.496074,-0.820427 3.855797,-0.820427 z"
+ id="path23-9"
+ style="fill:none;stroke:#ffffff;stroke-width:2.08759975;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><g
+ transform="matrix(0.69586655,0,0,0.69586655,19.911862,0.79545554)"
+ id="g25-6"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="m 43.102,30.421 c 0,4.7344 -1.6452,9.2798 -4.5706,12.6275 -2.9254,3.3478 -6.8973,5.2305 -11.0344,5.2305 -4.1371,0 -8.109,-1.8827 -11.0344,-5.2305 -2.9254,-3.3477 -4.5706,-7.8931 -4.5706,-12.6275 0,-9.7966 7.0444,-17.858 15.605,-17.858 8.5606,0 15.605,8.0614 15.605,17.858 z"
+ id="path2988-2"
+ style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g><g
+ transform="matrix(0.69586655,0,0,0.69586655,19.911862,0.79545554)"
+ id="g28-1"
+ style="fill:#ffffff;fill-opacity:1;stroke:none">
+ <path
+ d="m 25.875,33.75 -1.542,-4.625 3.164,-2.587 3.615,2.626 -1.487,4.669 -3.75,-0.083 z"
+ id="path30-7"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 27.501,41.551 c -3.968,-0.16 -5.543,-2.009 -5.543,-2.009 l 3.57,-4.163 4.465,0.168 3.132,4.12 c 0,0 -2.89,1.994 -5.624,1.884 z"
+ id="path32-8"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 18.453,33.843 c -0.849,-2.968 0.172,-6.884 0.172,-6.884 l 4,2.167 1.493,4.629 -3.582,4.233 c 0,-10e-4 -1.465,-1.99 -2.083,-4.145 z"
+ id="path34-5"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 19.458,25.125 c 0,0 0.5,-1.958 3.039,-3.822 2.237,-1.643 4.465,-1.72 4.465,-1.72 l -0.037,4.981 -3.521,2.75 -3.946,-2.189 z"
+ id="path2998-7"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="M 32.084,27.834 28.625,24.959 29,19.75 c 0,0 1.834,-0.042 3.959,1.667 2.228,1.791 3.362,4.983 3.362,4.983 l -4.237,1.434 z"
+ id="path37-4"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 31.292,34.042 1.313,-4.464 4.187,-1.536 c 0,0 0.677,2.663 -0.042,5.667 -0.54,2.256 -2.084,4.361 -2.084,4.361 l -3.374,-4.028 z"
+ id="path3002-1"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ </g></g><g
+ transform="translate(-2.2743816,2.513511)"
+ id="g3036"><path
+ d="M 9.5689397,38.952218 9.3902171,32.897964 M 25.663656,50.969805 3.6345541,51.23649 3.5243801,39.2454 l 5.851406,-6.358972 21.2578909,0 -0.110174,12.124478 z M 3.6345541,39.378743 l 21.4782339,0.266685 0.33052,10.924347 m -0.306426,-11.052011 5.531132,-6.638311"
+ id="path3806"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /><g
+ transform="matrix(0,-1,-1,0,47.3087,85.9835)"
+ id="g4770-9"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772-6"><polyline
+ id="polyline4774-3"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776-8"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg> \ No newline at end of file
diff --git a/icons/trashoff.svg b/icons/trashoff.svg
new file mode 100644
index 0000000..8ebfb39
--- /dev/null
+++ b/icons/trashoff.svg
@@ -0,0 +1,49 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <defs
+ id="defs5" />
+ <path
+ d="m 38.999052,40.01547 a 11.011069,3.4902595 0 1 1 -22.022137,0 11.011069,3.4902595 0 1 1 22.022137,0 z"
+ id="path2822"
+ style="fill:none;stroke:#ffffff;stroke-width:2.29999995;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 13.75483,14.61245 c 3.56733,24.489728 3.430201,24.354426 3.430201,24.354426 l 0,0"
+ id="path2820"
+ style="fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 41.24517,14.793462 c -2.625017,24.01617 -2.55771,24.314197 -2.55771,24.314197"
+ id="path2820-4"
+ style="fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 15.951887,16.457318 c 3.836562,25.030939 3.656235,25.621512 3.656235,25.621512"
+ id="path3620"
+ style="fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 39.328864,16.340019 c -2.902587,25.706467 -2.96102,25.806464 -2.96102,25.806464"
+ id="path3622"
+ style="fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 20.66094,17.470259 c 2.288476,25.03094 2.24528,25.62334 2.24528,25.62334"
+ id="path3624"
+ style="fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 34.281292,17.792054 C 31.790893,43.567158 32.084236,43.133828 32.084236,43.133828"
+ id="path3626"
+ style="fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 26.831688,18.009641 c 0.06731,24.963289 0.180327,25.396617 0.180327,25.396617"
+ id="path3628"
+ style="fill:none;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 41.281336,14.477147 a 13.76451,3.1119547 0 0 1 -27.529019,0 13.76451,3.1119547 0 1 1 27.529019,0 z"
+ id="path2818"
+ style="fill:none;stroke:#ffffff;stroke-width:2.29999995;stroke-linecap:square;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/icons/trashon.svg b/icons/trashon.svg
new file mode 100644
index 0000000..01e9cd2
--- /dev/null
+++ b/icons/trashon.svg
@@ -0,0 +1,57 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <defs
+ id="defs5" />
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <path
+ d="m 38.982225,40.079903 a 11.011069,3.4902595 0 1 1 -22.022137,0 11.011069,3.4902595 0 1 1 22.022137,0 z"
+ id="path2822-6"
+ style="fill:#808080;fill-opacity:1;stroke:#000000;stroke-width:2.29999995;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 13.738004,14.676883 c 3.567329,24.489728 3.4302,24.354426 3.4302,24.354426 l 0,0"
+ id="path2820-5"
+ style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 41.228343,14.857895 c -2.625016,24.01617 -2.55771,24.314197 -2.55771,24.314197"
+ id="path2820-4-6"
+ style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 15.93506,16.521751 c 3.836562,25.030939 3.656235,25.621512 3.656235,25.621512"
+ id="path3620-9"
+ style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 39.312038,16.404452 c -2.902587,25.706467 -2.96102,25.806464 -2.96102,25.806464"
+ id="path3622-3"
+ style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 20.644113,17.534692 c 2.288476,25.03094 2.24528,25.62334 2.24528,25.62334"
+ id="path3624-7"
+ style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 34.264465,17.856487 C 31.774066,43.631591 32.067409,43.198261 32.067409,43.198261"
+ id="path3626-4"
+ style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 26.814861,18.074074 c 0.06731,24.963289 0.180327,25.396617 0.180327,25.396617"
+ id="path3628-5"
+ style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 41.26451,14.54158 a 13.76451,3.1119547 0 0 1 -27.529019,0 13.76451,3.1119547 0 1 1 27.529019,0 z"
+ id="path2818-2"
+ style="fill:none;stroke:#000000;stroke-width:2.29999995;stroke-linecap:square;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/icons/turtleoff.svg b/icons/turtleoff.svg
new file mode 100644
index 0000000..f3d721f
--- /dev/null
+++ b/icons/turtleoff.svg
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata3044">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <path
+ d="m 27.567493,45.252146 c -0.46948,0 -0.933016,-0.02903 -1.389761,-0.08296 l 1.178368,1.948634 1.161389,-1.918769 c -0.314968,0.02489 -0.629086,0.05309 -0.949996,0.05309 z"
+ id="path3003"
+ style="fill:none;stroke:#ffffff;stroke-width:1.20000005;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 38.317981,14.929279 c -1.837168,0 -3.360217,1.289964 -3.68707,2.992219 1.578232,1.115757 2.934884,2.584076 3.968928,4.320343 1.939893,-0.142684 3.475677,-1.709721 3.475677,-3.641764 0,-2.027442 -1.682656,-3.670798 -3.757535,-3.670798 z"
+ id="path3007"
+ style="fill:#686868;fill-opacity:1;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 38.787461,38.290488 c -1.039138,1.851575 -2.42805,3.426908 -4.072502,4.609029 0.442312,1.546298 1.878767,2.686942 3.603022,2.686942 2.07403,0 3.757535,-1.642527 3.757535,-3.669969 0,-1.870656 -1.437304,-3.397874 -3.288055,-3.626002 z"
+ id="path3009"
+ style="fill:#686868;fill-opacity:1;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 16.340734,38.277215 c -1.912727,0.170889 -3.41625,1.724653 -3.41625,3.639275 0,2.026612 1.680958,3.669969 3.755837,3.669969 1.752271,0 3.212497,-1.177974 3.626793,-2.764091 -1.598607,-1.174655 -2.950165,-2.728419 -3.96638,-4.545153 z"
+ id="path3011"
+ style="fill:#686868;fill-opacity:1;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 20.375881,18.007772 c -0.291196,-1.744563 -1.828678,-3.078493 -3.69556,-3.078493 -2.074879,0 -3.755837,1.643356 -3.755837,3.669968 0,1.97601 1.601155,3.575399 3.603872,3.655037 1.006876,-1.694789 2.319381,-3.139051 3.847525,-4.246512 z"
+ id="path3013"
+ style="fill:#686868;fill-opacity:1;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 27.567493,15.62362 c 1.619832,0 3.164955,0.340948 4.599711,0.935742 0.629086,-0.892605 1.000085,-1.971862 1.000085,-3.138221 0,-3.058584 -2.537567,-5.5389654 -5.668563,-5.5389654 -3.130146,0 -5.667713,2.4803814 -5.667713,5.5389654 0,1.18461 0.383734,2.280457 1.032345,3.180529 1.463622,-0.62134 3.04525,-0.97805 4.704135,-0.97805 z"
+ id="path3015"
+ style="fill:#686868;fill-opacity:1;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(0.8489685,0,0,0.82955893,4.2234061,5.2018707)"
+ id="g3017"
+ style="fill:#686868;fill-opacity:1;stroke:#ffffff;stroke-width:1.42992032;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="m 43.102,30.421 c 0,4.7344 -1.6452,9.2798 -4.5706,12.6275 -2.9254,3.3478 -6.8973,5.2305 -11.0344,5.2305 -4.1371,0 -8.109,-1.8827 -11.0344,-5.2305 -2.9254,-3.3477 -4.5706,-7.8931 -4.5706,-12.6275 0,-9.7966 7.0444,-17.858 15.605,-17.858 8.5606,0 15.605,8.0614 15.605,17.858 z"
+ id="path3019"
+ style="fill:#686868;fill-opacity:1;stroke:#ffffff;stroke-width:1.42992032;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.8489685,0,0,0.82955893,4.2234061,5.2018707)"
+ id="g3021"
+ style="fill:#ffffff;fill-opacity:1;stroke:none">
+ <path
+ d="m 25.875,33.75 -1.542,-4.625 3.164,-2.587 3.615,2.626 -1.487,4.669 z"
+ id="path3023"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 27.501,41.551 c -3.968,-0.16 -5.543,-2.009 -5.543,-2.009 l 3.57,-4.163 4.465,0.168 3.132,4.12 c 0,0 -2.89,1.994 -5.624,1.884 z"
+ id="path3025"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 18.453,33.843 c -0.849,-2.968 0.172,-6.884 0.172,-6.884 l 4,2.167 1.493,4.629 -3.582,4.233 c 0,-10e-4 -1.465,-1.99 -2.083,-4.145 z"
+ id="path3027"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 19.458,25.125 c 0,0 0.5,-1.958 3.039,-3.822 2.237,-1.643 4.465,-1.72 4.465,-1.72 l -0.037,4.981 -3.521,2.75 -3.946,-2.189 z"
+ id="path3029"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="M 32.084,27.834 28.625,24.959 29,19.75 c 0,0 1.834,-0.042 3.959,1.667 2.228,1.791 3.362,4.983 3.362,4.983 l -4.237,1.434 z"
+ id="path3031"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ <path
+ d="m 31.292,34.042 1.313,-4.464 4.187,-1.536 c 0,0 0.677,2.663 -0.042,5.667 -0.54,2.256 -2.084,4.361 -2.084,4.361 l -3.374,-4.028 z"
+ id="path3033"
+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
+ </g>
+</svg>
diff --git a/icons/turtleon.svg b/icons/turtleon.svg
new file mode 100644
index 0000000..4d955de
--- /dev/null
+++ b/icons/turtleon.svg
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata3068">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <g
+ id="g3854">
+ <path
+ d="m 27.567493,45.252146 c -0.46948,0 -0.933016,-0.02903 -1.389761,-0.08296 l 1.178368,1.948634 1.161389,-1.918769 c -0.314968,0.02489 -0.629086,0.05309 -0.949996,0.05309 z"
+ id="path3003"
+ style="fill:none;stroke:#00ff00;stroke-width:1.20000005;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(0.8489685,0,0,0.82955893,4.2234061,5.2018707)"
+ id="g3005"
+ style="fill:#00d000;fill-opacity:1;stroke:#00ff00;stroke-width:1.42992032;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="m 40.16,11.726 c -2.164,0 -3.958,1.555 -4.343,3.607 1.859,1.345 3.457,3.115 4.675,5.208 2.285,-0.172 4.094,-2.061 4.094,-4.39 0,-2.444 -1.982,-4.425 -4.426,-4.425 z"
+ id="path3007"
+ style="fill:#00d000;fill-opacity:1;stroke:#00ff00;stroke-width:1.42992032;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 40.713,39.887 c -1.224,2.232 -2.86,4.131 -4.797,5.556 0.521,1.864 2.213,3.239 4.244,3.239 2.443,0 4.426,-1.98 4.426,-4.424 0,-2.255 -1.693,-4.096 -3.873,-4.371 z"
+ id="path3009"
+ style="fill:#00d000;fill-opacity:1;stroke:#00ff00;stroke-width:1.42992032;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 14.273,39.871 c -2.253,0.206 -4.024,2.079 -4.024,4.387 0,2.443 1.98,4.424 4.424,4.424 2.064,0 3.784,-1.42 4.272,-3.332 -1.883,-1.416 -3.475,-3.289 -4.672,-5.479 z"
+ id="path3011"
+ style="fill:#00d000;fill-opacity:1;stroke:#00ff00;stroke-width:1.42992032;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 19.026,15.437 c -0.343,-2.103 -2.154,-3.711 -4.353,-3.711 -2.444,0 -4.424,1.981 -4.424,4.424 0,2.382 1.886,4.31 4.245,4.406 1.186,-2.043 2.732,-3.784 4.532,-5.119 z"
+ id="path3013"
+ style="fill:#00d000;fill-opacity:1;stroke:#00ff00;stroke-width:1.42992032;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <path
+ d="m 27.567493,15.62362 c 1.619832,0 3.164955,0.340948 4.599711,0.935742 0.629086,-0.892605 1.000085,-1.971862 1.000085,-3.138221 0,-3.058584 -2.537567,-5.5389654 -5.668563,-5.5389654 -3.130146,0 -5.667713,2.4803814 -5.667713,5.5389654 0,1.18461 0.383734,2.280457 1.032345,3.180529 1.463622,-0.62134 3.04525,-0.97805 4.704135,-0.97805 z"
+ id="path3015"
+ style="fill:#00d000;fill-opacity:1;stroke:#00ff00;stroke-width:1.20000005;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(0.8489685,0,0,0.82955893,4.2234061,5.2018707)"
+ id="g3017"
+ style="fill:#00d000;fill-opacity:1;stroke:#00ff00;stroke-width:1.42992032;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+ <path
+ d="m 43.102,30.421 c 0,4.7344 -1.6452,9.2798 -4.5706,12.6275 -2.9254,3.3478 -6.8973,5.2305 -11.0344,5.2305 -4.1371,0 -8.109,-1.8827 -11.0344,-5.2305 -2.9254,-3.3477 -4.5706,-7.8931 -4.5706,-12.6275 0,-9.7966 7.0444,-17.858 15.605,-17.858 8.5606,0 15.605,8.0614 15.605,17.858 z"
+ id="path3019"
+ style="fill:#00d000;fill-opacity:1;stroke:#00ff00;stroke-width:1.42992032;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.8489685,0,0,0.82955893,4.2234061,5.2018707)"
+ id="g3021"
+ style="fill:#00ff00;fill-opacity:1;stroke:none">
+ <path
+ d="m 25.875,33.75 -1.542,-4.625 3.164,-2.587 3.615,2.626 -1.487,4.669 z"
+ id="path3023"
+ style="fill:#00ff00;fill-opacity:1;stroke:none" />
+ <path
+ d="m 27.501,41.551 c -3.968,-0.16 -5.543,-2.009 -5.543,-2.009 l 3.57,-4.163 4.465,0.168 3.132,4.12 c 0,0 -2.89,1.994 -5.624,1.884 z"
+ id="path3025"
+ style="fill:#00ff00;fill-opacity:1;stroke:none" />
+ <path
+ d="m 18.453,33.843 c -0.849,-2.968 0.172,-6.884 0.172,-6.884 l 4,2.167 1.493,4.629 -3.582,4.233 c 0,-10e-4 -1.465,-1.99 -2.083,-4.145 z"
+ id="path3027"
+ style="fill:#00ff00;fill-opacity:1;stroke:none" />
+ <path
+ d="m 19.458,25.125 c 0,0 0.5,-1.958 3.039,-3.822 2.237,-1.643 4.465,-1.72 4.465,-1.72 l -0.037,4.981 -3.521,2.75 -3.946,-2.189 z"
+ id="path3029"
+ style="fill:#00ff00;fill-opacity:1;stroke:none" />
+ <path
+ d="M 32.084,27.834 28.625,24.959 29,19.75 c 0,0 1.834,-0.042 3.959,1.667 2.228,1.791 3.362,4.983 3.362,4.983 l -4.237,1.434 z"
+ id="path3031"
+ style="fill:#00ff00;fill-opacity:1;stroke:none" />
+ <path
+ d="m 31.292,34.042 1.313,-4.464 4.187,-1.536 c 0,0 0.677,2.663 -0.042,5.667 -0.54,2.256 -2.084,4.361 -2.084,4.361 l -3.374,-4.028 z"
+ id="path3033"
+ style="fill:#00ff00;fill-opacity:1;stroke:none" />
+ </g>
+ </g>
+</svg>
diff --git a/icons/view-Cartesian.svg b/icons/view-Cartesian.svg
new file mode 100644
index 0000000..3d0e210
--- /dev/null
+++ b/icons/view-Cartesian.svg
@@ -0,0 +1,30 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2384">
+ <defs
+ id="defs2386" />
+ <g
+ id="layer1">
+ <rect
+ width="50"
+ height="50"
+ x="2.5"
+ y="2.5"
+ id="rect2394"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 27.333333,1 C 28.083333,50.75 27.333333,54 27.333333,54"
+ id="path3168"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 54,27.333333 C 4.2499997,28.083333 1,27.333333 1,27.333333"
+ id="path3170"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/icons/view-metric.svg b/icons/view-metric.svg
new file mode 100644
index 0000000..c91675d
--- /dev/null
+++ b/icons/view-metric.svg
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2384">
+ <rect
+ width="50"
+ height="50"
+ x="2.5"
+ y="2.5"
+ id="rect2394"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 27.5,40.877276 0,12.716411"
+ id="path3168"
+ style="fill:none;stroke:#ffffff;stroke-width:2.81262541;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 9.8582187,27.5 -8.4776076,0"
+ id="path3170"
+ style="fill:none;stroke:#ffffff;stroke-width:2.76122212;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ x="9.2539062"
+ y="36.236328"
+ id="text2989"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="9.2539062"
+ y="36.236328"
+ id="tspan2991"
+ style="font-size:24px;fill:#ffffff">CM</tspan></text>
+ <path
+ d="m 27.5,1.4062879 0,12.7164111"
+ id="path3168-0"
+ style="fill:none;stroke:#ffffff;stroke-width:2.81257582;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 53.729275,27.5 -8.477607,0"
+ id="path3170-7"
+ style="fill:none;stroke:#ffffff;stroke-width:2.54144907;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/icons/view-polar.svg b/icons/view-polar.svg
new file mode 100644
index 0000000..85fcb12
--- /dev/null
+++ b/icons/view-polar.svg
@@ -0,0 +1,25 @@
+<?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.0"
+ width="55"
+ height="55"
+ id="svg2384">
+ <defs
+ id="defs2386" />
+ <path
+ d="M 50.991903,28.836033 A 22.935223,21.821863 0 1 1 5.1214581,28.836033 A 22.935223,21.821863 0 1 1 50.991903,28.836033 z"
+ transform="matrix(0.9819585,0,0,1.0009824,-5.0496075e-2,-1.3643614)"
+ id="path3157"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:3.53027272;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 27.333333,1 C 28.083333,50.75 27.333333,54 27.333333,54"
+ id="path3168"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 54,27.333333 C 4.2499997,28.083333 1,27.333333 1,27.333333"
+ id="path3170"
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+</svg>
diff --git a/images/1x1.svg b/images/1x1.svg
new file mode 100644
index 0000000..3742c13
--- /dev/null
+++ b/images/1x1.svg
@@ -0,0 +1,107 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="36.914501"
+ height="20.12108"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172-9"
+ xlink:href="#linearGradient3166-6"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ id="linearGradient3166-6">
+ <stop
+ id="stop3168-5"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170-6"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="matrix(0.44910476,0,0,0.43207674,-2.5452997,-1.8215706)"
+ id="activity-journal"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ id="line2460"
+ y2="48.881001"
+ y1="6.1209998"
+ x2="21.341"
+ x1="21.341"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(0.4489,0,0,0.44175824,21.433726,0.01485409)"
+ id="g2823">
+ <path
+ d="m 1.75,43.75 l 0,-42 21,0 10,11 0,31 -31,0 z"
+ id="path3199"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#00000f;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none" />
+ <path
+ d="m 10,26.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3163"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,32.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3165"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,20.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3167"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 21.5,2.5000001 l 0,10.2499999 9.5,0"
+ id="path3201"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/1x1a.svg b/images/1x1a.svg
new file mode 100644
index 0000000..5b93089
--- /dev/null
+++ b/images/1x1a.svg
@@ -0,0 +1,63 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="28.128611"
+ height="31.155001"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ </defs>
+ <g
+ transform="matrix(0.67,0,0,0.67,-3.77478,-2.8475)"
+ id="activity-journal"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ id="line2460"
+ y2="48.881001"
+ y1="6.1209998"
+ x2="21.341"
+ x1="21.341"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/images/1x2.svg b/images/1x2.svg
new file mode 100644
index 0000000..c4358b3
--- /dev/null
+++ b/images/1x2.svg
@@ -0,0 +1,163 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="36.914501"
+ height="41.561081"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172-9"
+ xlink:href="#linearGradient3166-6"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ id="linearGradient3166-6">
+ <stop
+ id="stop3168-5"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170-6"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="scale(0.67,0.67)"
+ id="g2846">
+ <g
+ transform="matrix(0.67030561,0,0,0.64489065,-3.7989548,-2.7187621)"
+ id="activity-journal"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="21.341"
+ x2="21.341"
+ y1="6.1209998"
+ y2="48.881001"
+ id="line2460" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(0.67,0,0,0.65934066,31.990636,0.02217029)"
+ id="g2823">
+ <path
+ d="m 1.75,43.75 l 0,-42 21,0 10,11 0,31 -31,0 z"
+ id="path3199"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#00000f;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none" />
+ <path
+ d="m 10,26.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3163"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,32.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3165"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,20.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3167"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 21.5,2.5000001 l 0,10.2499999 9.5,0"
+ id="path3201"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.67030561,0,0,0.64489065,-3.7989548,29.281238)"
+ id="activity-journal-4"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458-7"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="21.341"
+ x2="21.341"
+ y1="6.1209998"
+ y2="48.881001"
+ id="line2460-6" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462-5"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464-6"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466-9"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(0.67,0,0,0.65934066,31.990636,32.02217)"
+ id="g2823-3">
+ <path
+ d="m 1.75,43.75 l 0,-42 21,0 10,11 0,31 -31,0 z"
+ id="path3199-7"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#00000f;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none" />
+ <path
+ d="m 10,26.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3163-4"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,32.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3165-5"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,20.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3167-2"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 21.5,2.5000001 l 0,10.2499999 9.5,0"
+ id="path3201-5"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+</svg>
diff --git a/images/2x1.svg b/images/2x1.svg
new file mode 100644
index 0000000..c7d92c4
--- /dev/null
+++ b/images/2x1.svg
@@ -0,0 +1,159 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="40.264679"
+ height="41.561081"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172-9"
+ xlink:href="#linearGradient3166-6"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ id="linearGradient3166-6">
+ <stop
+ id="stop3168-5"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170-6"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="matrix(0.44910476,0,0,0.43207674,-2.5452997,-1.8215706)"
+ id="activity-journal"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ id="line2460"
+ y2="48.881001"
+ y1="6.1209998"
+ x2="21.341"
+ x1="21.341"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(0.4489,0,0,0.44175824,1.6688141,21.454857)"
+ id="g2823">
+ <path
+ d="m 1.75,43.75 l 0,-42 21,0 10,11 0,31 -31,0 z"
+ id="path3199"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#00000f;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none" />
+ <path
+ d="m 10,26.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3163"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,32.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3165"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,20.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3167"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 21.5,2.5000001 l 0,10.2499999 9.5,0"
+ id="path3201"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ transform="matrix(0.44910476,0,0,0.43207674,18.8947,-1.8215706)"
+ id="activity-journal-4"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458-7"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ id="line2460-6"
+ y2="48.881001"
+ y1="6.1209998"
+ x2="21.341"
+ x1="21.341"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462-5"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464-6"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466-9"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(0.4489,0,0,0.44175824,23.108814,21.454854)"
+ id="g2823-3">
+ <path
+ d="m 1.75,43.75 l 0,-42 21,0 10,11 0,31 -31,0 z"
+ id="path3199-7"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#00000f;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none" />
+ <path
+ d="m 10,26.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3163-4"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,32.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3165-5"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,20.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3167-2"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 21.5,2.5000001 l 0,10.2499999 9.5,0"
+ id="path3201-5"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/2x2.svg b/images/2x2.svg
new file mode 100644
index 0000000..93a8b82
--- /dev/null
+++ b/images/2x2.svg
@@ -0,0 +1,171 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="38.924679"
+ height="41.561081"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172-9"
+ xlink:href="#linearGradient3166-6"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ id="linearGradient3166-6">
+ <stop
+ id="stop3168-5"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170-6"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="scale(0.67,0.67)"
+ id="g2846">
+ <g
+ transform="matrix(0.67030561,0,0,0.64489065,-3.7989548,-2.7187603)"
+ id="activity-journal"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="21.341"
+ x2="21.341"
+ y1="6.1209998"
+ y2="48.881001"
+ id="line2460" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(0.67030561,0,0,0.64489065,26.201045,-2.7187621)"
+ id="activity-journal-7"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458-4"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="21.341"
+ x2="21.341"
+ y1="6.1209998"
+ y2="48.881001"
+ id="line2460-5" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462-2"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464-5"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466-4"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(0.67030561,0,0,0.64489065,-3.7989548,29.28124)"
+ id="activity-journal-8"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458-6"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="21.341"
+ x2="21.341"
+ y1="6.1209998"
+ y2="48.881001"
+ id="line2460-8" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462-8"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464-4"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466-3"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(0.67030561,0,0,0.64489065,26.201045,29.281238)"
+ id="activity-journal-7-1"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458-4-4"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="21.341"
+ x2="21.341"
+ y1="6.1209998"
+ y2="48.881001"
+ id="line2460-5-9" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462-2-2"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464-5-0"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466-4-6"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/images/Cartesian.svg b/images/Cartesian.svg
new file mode 100644
index 0000000..7a29e22
--- /dev/null
+++ b/images/Cartesian.svg
@@ -0,0 +1,211 @@
+<?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.0"
+ width="1200"
+ height="900"
+ id="svg2">
+ <defs
+ id="defs4" />
+ <g
+ id="layer1">
+ <path
+ d="M 0,850 L 1200,850"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 0,750 L 1200,750"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 0,650 L 1200,650"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 0,550 L 1200,550"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 0,350 L 1200,350"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 0,250 L 1200,250"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 0,150 L 1200,150"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 0,50 L 1200,50"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 1100,0 L 1100,900"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 1000,0 L 1000,900"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 900,0 L 900,900"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 800,0 L 800,900"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 700,0 L 700,900"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 500,0 L 500,900"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 400,0 L 400,900"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 300,0 L 300,900"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 200,0 L 200,900"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 100,0 L 100,900"
+ style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 590,850 L 610,850"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 590,750 L 610,750"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 590,650 L 610,650"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 590,550 L 610,550"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 0,450 L 1200,450"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 590,350 L 610,350"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 590,250 L 610,250"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 590,150 L 610,150"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 590,50 L 610,50"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 1100,440 L 1100,460"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 1000,440 L 1000,460"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 900,440 L 900,460"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 800,440 L 800,460"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 700,440 L 700,460"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 600,0 L 600,900"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 500,440 L 500,460"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 400,440 L 400,460"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 300,440 L 300,460"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 200,440 L 200,460"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 100,440 L 100,460"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+<!--
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
+ x="95"
+ y="480">–500</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
+ x="195"
+ y="480">–400</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
+ x="295"
+ y="480">–300</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
+ x="395"
+ y="480">–200</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
+ x="495"
+ y="480">–100</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
+ x="650"
+ y="480">x</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
+ x="700"
+ y="480">100</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
+ x="800"
+ y="480">200</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
+ x="900"
+ y="480">300</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
+ x="1000"
+ y="480">400</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
+ x="1100"
+ y="480">500</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
+ x="590"
+ y="58">400</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
+ x="590"
+ y="158">300</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
+ x="590"
+ y="258">200</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
+ x="590"
+ y="358">100</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
+ x="590"
+ y="408">y</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
+ x="590"
+ y="558">–100</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
+ x="590"
+ y="658">–200</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
+ x="590"
+ y="758">–300</tspan></text>
+ <text
+ style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
+ x="590"
+ y="858">–400</tspan></text>
+-->
+ </g>
+</svg>
diff --git a/images/Cartesian_labeled.svg b/images/Cartesian_labeled.svg
new file mode 100644
index 0000000..e70bb9b
--- /dev/null
+++ b/images/Cartesian_labeled.svg
@@ -0,0 +1,375 @@
+<?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.0"
+ width="1200"
+ height="900"
+ id="svg2">
+ <defs
+ id="defs4" />
+ <g
+ id="layer1">
+ <path
+ d="m 0,850 1200,0"
+ id="path5"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,750 1200,0"
+ id="path7"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,650 1200,0"
+ id="path9"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,550 1200,0"
+ id="path11"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,350 1200,0"
+ id="path13"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,250 1200,0"
+ id="path15"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,150 1200,0"
+ id="path17"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,50 1200,0"
+ id="path19"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 1100,0 0,900"
+ id="path21"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 1000,0 0,900"
+ id="path23"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 900,0 0,900"
+ id="path25"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 800,0 0,900"
+ id="path27"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 700,0 0,900"
+ id="path29"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 500,0 0,900"
+ id="path31"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 400,0 0,900"
+ id="path33"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 300,0 0,900"
+ id="path35"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 200,0 0,900"
+ id="path37"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 100,0 0,900"
+ id="path39"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,850 20,0"
+ id="path41"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,750 20,0"
+ id="path43"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,650 20,0"
+ id="path45"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,550 20,0"
+ id="path47"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,450 1200,0"
+ id="path49"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,350 20,0"
+ id="path51"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,250 20,0"
+ id="path53"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,150 20,0"
+ id="path55"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,50 20,0"
+ id="path57"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 1100,440 0,20"
+ id="path59"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 1000,440 0,20"
+ id="path61"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 900,440 0,20"
+ id="path63"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 800,440 0,20"
+ id="path65"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 700,440 0,20"
+ id="path67"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 600,0 0,900"
+ id="path69"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 500,440 0,20"
+ id="path71"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 400,440 0,20"
+ id="path73"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 300,440 0,20"
+ id="path75"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 200,440 0,20"
+ id="path77"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 100,440 0,20"
+ id="path79"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ </g>
+ <text
+ x="603.90137"
+ y="344.8584"
+ id="text3218"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="603.90137"
+ y="344.8584"
+ id="tspan3220">100</tspan></text>
+ <text
+ x="604.26758"
+ y="244.8584"
+ id="text3218-8"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="604.26758"
+ y="244.8584"
+ id="tspan3220-7">200</tspan></text>
+ <text
+ x="604.23828"
+ y="144.8584"
+ id="text3218-7"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="604.23828"
+ y="144.8584"
+ id="tspan3220-2">300</tspan></text>
+ <text
+ x="604.51172"
+ y="44.858398"
+ id="text3218-2"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="604.51172"
+ y="44.858398"
+ id="tspan3220-26">400</tspan></text>
+ <text
+ x="1104.2285"
+ y="444.8584"
+ id="text3218-6"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="1104.2285"
+ y="444.8584"
+ id="tspan3220-1">500</tspan></text>
+ <text
+ x="1004.5117"
+ y="444.8584"
+ id="text3218-9"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="1004.5117"
+ y="444.8584"
+ id="tspan3220-4">400</tspan></text>
+ <text
+ x="904.23828"
+ y="444.8584"
+ id="text3218-0"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="904.23828"
+ y="444.8584"
+ id="tspan3220-9">300</tspan></text>
+ <text
+ x="804.26758"
+ y="444.8584"
+ id="text3218-71"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="804.26758"
+ y="444.8584"
+ id="tspan3220-15">200</tspan></text>
+ <text
+ x="703.90137"
+ y="444.8584"
+ id="text3218-76"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="703.90137"
+ y="444.8584"
+ id="tspan3220-73">100</tspan></text>
+ <text
+ x="469.51172"
+ y="444.8584"
+ id="text3218-5"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="469.51172"
+ y="444.8584"
+ id="tspan3220-6">–100</tspan></text>
+ <text
+ x="369.51172"
+ y="444.8584"
+ id="text3218-94"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="369.51172"
+ y="444.8584"
+ id="tspan3220-8">–200</tspan></text>
+ <text
+ x="269.51172"
+ y="444.8584"
+ id="text3218-93"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="269.51172"
+ y="444.8584"
+ id="tspan3220-90">–300</tspan></text>
+ <text
+ x="169.51172"
+ y="444.8584"
+ id="text3218-85"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="169.51172"
+ y="444.8584"
+ id="tspan3220-0">–400</tspan></text>
+ <text
+ x="69.511719"
+ y="444.8584"
+ id="text3218-3"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="69.511719"
+ y="444.8584"
+ id="tspan3220-85">–500</tspan></text>
+ <text
+ x="571.51172"
+ y="544.8584"
+ id="text3218-1"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="571.51172"
+ y="544.8584"
+ id="tspan3220-5">–100</tspan></text>
+ <text
+ x="571.51172"
+ y="644.8584"
+ id="text3218-4"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="571.51172"
+ y="644.8584"
+ id="tspan3220-81">–200</tspan></text>
+ <text
+ x="571.51172"
+ y="744.8584"
+ id="text3218-30"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="571.51172"
+ y="744.8584"
+ id="tspan3220-44">–300</tspan></text>
+ <text
+ x="571.51172"
+ y="844.8584"
+ id="text3218-47"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="571.51172"
+ y="844.8584"
+ id="tspan3220-63">–400</tspan></text>
+ <text
+ x="604.34082"
+ y="444.8584"
+ id="text3218-96"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="604.34082"
+ y="444.8584"
+ id="tspan3220-21">0</tspan></text>
+ <text
+ x="69.140625"
+ y="43.681641"
+ id="text3218-2-7"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="69.140625"
+ y="43.681641"
+ id="tspan3220-26-8">(–500, 400)</tspan></text>
+ <text
+ x="1074.1406"
+ y="43.681641"
+ id="text3218-2-7-5"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="1074.1406"
+ y="43.681641"
+ id="tspan3220-26-8-7">(500, 400)</tspan></text>
+ <text
+ x="1074.1406"
+ y="843.68164"
+ id="text3218-2-7-1"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="1074.1406"
+ y="843.68164"
+ id="tspan3220-26-8-8">(500, –400)</tspan></text>
+ <text
+ x="69.140625"
+ y="843.68164"
+ id="text3218-2-7-1-5"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="69.140625"
+ y="843.68164"
+ id="tspan3220-26-8-8-9">(–500, –400)</tspan></text>
+</svg>
diff --git a/images/amazonas-0.jpg b/images/amazonas-0.jpg
new file mode 100644
index 0000000..1b093ab
--- /dev/null
+++ b/images/amazonas-0.jpg
Binary files differ
diff --git a/images/amazonas-1.jpg b/images/amazonas-1.jpg
new file mode 100644
index 0000000..a05f9d8
--- /dev/null
+++ b/images/amazonas-1.jpg
Binary files differ
diff --git a/images/amazonas-2.jpg b/images/amazonas-2.jpg
new file mode 100644
index 0000000..31968cc
--- /dev/null
+++ b/images/amazonas-2.jpg
Binary files differ
diff --git a/images/amazonas-3.jpg b/images/amazonas-3.jpg
new file mode 100644
index 0000000..5a5c72c
--- /dev/null
+++ b/images/amazonas-3.jpg
Binary files differ
diff --git a/images/audiooff.svg b/images/audiooff.svg
new file mode 100644
index 0000000..ca89538
--- /dev/null
+++ b/images/audiooff.svg
@@ -0,0 +1,71 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="60"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="94.577461"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7750554,0,0,0.9997979,0.8784441,32.623865)" />
+ </defs>
+ <g
+ transform="translate(2.625,-5.001)"
+ id="clipping-audio"
+ style="display:block">
+ <g
+ id="g2758"
+ style="display:inline">
+ <g
+ id="g2760">
+ <polygon
+ points="31.874,6.088 43.818,18.027 43.818,48.914 10.932,48.914 10.932,6.088 10.932,6.088 "
+ id="polygon2762"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5" />
+ <polyline
+ id="polyline2764"
+ points="43.818,18.027 31.874,18.027 31.874,6.088 "
+ style="fill:none;stroke:#010101;stroke-width:3.5" />
+ </g>
+ </g>
+ <path
+ d="m 28.325,39.697 c -0.511,-1.457 -3.21,-1.073 -4.41,-0.07 -2.4,2.009 -0.424,4.396 2.324,3.277 1.564,-0.638 2.596,-1.748 2.086,-3.207 z"
+ id="path2766"
+ style="fill:#010101;stroke:#010101;stroke-width:3.5;display:inline" />
+ <line
+ id="line2768"
+ y2="26.966999"
+ y1="39.806"
+ x2="28.941"
+ x1="28.941"
+ display="inline"
+ style="fill:none;stroke:#010101;stroke-width:2.25;display:inline" />
+ <polygon
+ points="35.047,21.166 35.047,25.036 35.047,25.036 27.838,28.595 27.838,24.728 "
+ id="polygon2770"
+ style="fill:#010101;display:inline" />
+ </g>
+</svg>
diff --git a/images/audioon.svg b/images/audioon.svg
new file mode 100644
index 0000000..f71bd06
--- /dev/null
+++ b/images/audioon.svg
@@ -0,0 +1,80 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="60"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="94.577461"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7750554,0,0,0.9997979,0.8784441,32.623865)" />
+ </defs>
+ <rect
+ width="32.5"
+ height="42.499996"
+ x="13.75"
+ y="1.2500019"
+ id="rect2426"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="translate(-9.372241,-9.2260522)"
+ id="g3201">
+ <path
+ d="m 36.843,39.696 c -0.511,-1.457 -3.21,-1.073 -4.41,-0.07 -2.4,2.009 -0.424,4.396 2.324,3.277 1.564,-0.638 2.596,-1.748 2.086,-3.207 z"
+ id="path2766"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.5;stroke-opacity:1;display:inline" />
+ <line
+ id="line2768"
+ y2="26.966"
+ y1="39.805"
+ x2="37.459"
+ x1="37.459"
+ display="inline"
+ style="fill:none;stroke:#ff0000;stroke-width:2.25;stroke-opacity:1;display:inline" />
+ <polygon
+ points="35.047,21.166 35.047,25.036 35.047,25.036 27.838,28.595 27.838,24.728 "
+ transform="translate(8.518,-0.001)"
+ id="polygon2770"
+ style="fill:#ff0000;fill-opacity:1;display:inline" />
+ <path
+ d="m 47.25801,34.943896 c -0.511,-1.457 -3.21,-1.073 -4.41,-0.07 -2.4,2.009 -0.424,4.396 2.324,3.277 1.564,-0.638 2.596,-1.748 2.086,-3.207 z"
+ id="path2412"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.5;stroke-opacity:1;display:inline" />
+ <line
+ id="line2414"
+ y2="21.713896"
+ y1="34.552895"
+ x2="47.674007"
+ x1="47.674007"
+ display="inline"
+ style="fill:none;stroke:#ff0000;stroke-width:2.25;stroke-opacity:1;display:inline" />
+ <polygon
+ points="35.047,21.166 35.047,25.036 35.047,25.036 27.838,28.595 27.838,24.728 "
+ transform="translate(13.73301,-2.653104)"
+ id="polygon2416"
+ style="fill:#ff0000;fill-opacity:1;display:inline" />
+ </g>
+</svg>
diff --git a/images/audiosmall.svg b/images/audiosmall.svg
new file mode 100644
index 0000000..f922aac
--- /dev/null
+++ b/images/audiosmall.svg
@@ -0,0 +1,71 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="24.37862"
+ height="31.03842"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="94.577461"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7750554,0,0,0.9997979,0.8784441,32.623865)" />
+ </defs>
+ <g
+ transform="matrix(0.67,0,0,0.67,-6.15194,-2.90646)"
+ id="clipping-audio"
+ style="display:block">
+ <g
+ id="g2758"
+ style="display:inline">
+ <g
+ id="g2760">
+ <polygon
+ points="43.818,48.914 10.932,48.914 10.932,6.088 10.932,6.088 31.874,6.088 43.818,18.027 "
+ id="polygon2762"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5" />
+ <polyline
+ id="polyline2764"
+ points="43.818,18.027 31.874,18.027 31.874,6.088 "
+ style="fill:none;stroke:#010101;stroke-width:3.5" />
+ </g>
+ </g>
+ <path
+ d="m 28.325,39.697 c -0.511,-1.457 -3.21,-1.073 -4.41,-0.07 -2.4,2.009 -0.424,4.396 2.324,3.277 1.564,-0.638 2.596,-1.748 2.086,-3.207 z"
+ id="path2766"
+ style="fill:#010101;stroke:#010101;stroke-width:3.5;display:inline" />
+ <line
+ id="line2768"
+ y2="26.966999"
+ y1="39.806"
+ x2="28.941"
+ x1="28.941"
+ display="inline"
+ style="fill:none;stroke:#010101;stroke-width:2.25;display:inline" />
+ <polygon
+ points="35.047,25.036 35.047,25.036 27.838,28.595 27.838,24.728 35.047,21.166 "
+ id="polygon2770"
+ style="fill:#010101;display:inline" />
+ </g>
+</svg>
diff --git a/images/cameraoff.svg b/images/cameraoff.svg
new file mode 100644
index 0000000..54d9c86
--- /dev/null
+++ b/images/cameraoff.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="camera-external">
+ <g display="inline">
+ <g>
+ <polygon fill="&fill_color;" points="38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 48.209,14.52 "/>
+ </g>
+ <g>
+ <polygon fill="none" points="38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 48.209,14.52 " stroke="&stroke_color;" stroke-width="3.5"/>
+ </g>
+ </g>
+ <path d="M20.601,26.441c0,3.67,2.979,6.648,6.65,6.648 c3.667,0,6.646-2.979,6.646-6.648c0-3.668-2.979-6.652-6.646-6.652C23.581,19.789,20.601,22.775,20.601,26.441z" display="inline" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/>
+ <rect display="inline" fill="&stroke_color;" height="4.193" width="6.287" x="38.099" y="18.418"/>
+</g></svg> \ No newline at end of file
diff --git a/images/camerasmall.svg b/images/camerasmall.svg
new file mode 100644
index 0000000..690e940
--- /dev/null
+++ b/images/camerasmall.svg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="26"
+ height="18.668091"
+ viewBox="0 0 26 18.668091"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata29"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs27">
+
+
+
+
+
+
+
+
+
+ </defs><g
+ transform="matrix(0.57244766,0,0,0.57244766,-2.5989125,-4.6436954)"
+ id="camera-external"
+ style="display:block"><g
+ id="g5"
+ style="display:inline"><g
+ id="g7"><polygon
+ points="48.209,14.52 38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 "
+ id="polygon9"
+ style="fill:#ffffff" /></g><g
+ id="g11"><polygon
+ points="48.209,14.52 38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 "
+ id="polygon13"
+ style="fill:none;stroke:#010101;stroke-width:3.5" /></g></g><path
+ d="m 20.601,26.441 c 0,3.67 2.979,6.648 6.65,6.648 3.667,0 6.646,-2.979 6.646,-6.648 0,-3.668 -2.979,-6.652 -6.646,-6.652 -3.67,0 -6.65,2.986 -6.65,6.652 z"
+ id="path15"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5;display:inline" /><rect
+ width="6.2870002"
+ height="4.1929998"
+ x="38.098999"
+ y="18.417999"
+ id="rect17"
+ style="fill:#010101;display:inline" /></g></svg> \ No newline at end of file
diff --git a/images/descriptionoff.svg b/images/descriptionoff.svg
new file mode 100644
index 0000000..4966738
--- /dev/null
+++ b/images/descriptionoff.svg
@@ -0,0 +1,59 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="60"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ </defs>
+ <g
+ transform="translate(12.75,0.25)"
+ id="g3040">
+ <path
+ d="m 1.75,43.25 l 0,-42 21,0 10,11 0,31 -31,0 z"
+ id="path3199"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#00000f;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none" />
+ <path
+ d="m 10,26.25 c 14.5,0 14.5,0 14.5,0"
+ id="path3163"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,32.25 c 14.5,0 14.5,0 14.5,0"
+ id="path3165"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,20.25 c 14.5,0 14.5,0 14.5,0"
+ id="path3167"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 21.5,2.0000001 l 0,10.2499999 9.5,0"
+ id="path3201"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/descriptionon.svg b/images/descriptionon.svg
new file mode 100644
index 0000000..07edcdd
--- /dev/null
+++ b/images/descriptionon.svg
@@ -0,0 +1,58 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="60"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ </defs>
+ <g
+ transform="translate(12.750001,-0.25)"
+ id="g3149">
+ <rect
+ width="30.999998"
+ height="42"
+ x="1.75"
+ y="1.75"
+ id="rect2391"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,26.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3163"
+ style="fill:none;stroke:#ff0000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,32.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3165"
+ style="fill:none;stroke:#ff0000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,20.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3167"
+ style="fill:none;stroke:#ff0000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/descriptionsmall.svg b/images/descriptionsmall.svg
new file mode 100644
index 0000000..93c52d8
--- /dev/null
+++ b/images/descriptionsmall.svg
@@ -0,0 +1,59 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="23.115"
+ height="30.485001"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ </defs>
+ <g
+ transform="scale(0.67,0.67)"
+ id="g2823">
+ <path
+ d="m 1.75,43.75 l 0,-42 21,0 10,11 0,31 -31,0 z"
+ id="path3199"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#00000f;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none" />
+ <path
+ d="m 10,26.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3163"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,32.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3165"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10,20.75 c 14.5,0 14.5,0 14.5,0"
+ id="path3167"
+ style="fill:none;stroke:#000000;stroke-width:3.49999976;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 21.5,2.5000001 l 0,10.2499999 9.5,0"
+ id="path3201"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/dupstack.svg b/images/dupstack.svg
new file mode 100644
index 0000000..6a0f3fd
--- /dev/null
+++ b/images/dupstack.svg
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2467">
+ <metadata
+ id="metadata20">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs2493" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 736.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <path
+ d="m 222.97818,18.999999 0,0 -26,15.000001 0,0 0,3 -13.50001,0 0,-3 c 0,0 -26,-15.000001 -26,-15.000001 0,0 33.00001,-17.9999992 33.00001,-17.9999992 0,0 32.5,17.9999992 32.5,17.9999992 z"
+ id="path2490-6"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:#c0a000;stroke-width:1.33340001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ x="6.1187973"
+ y="4.1250014"
+ id="text2474"
+ style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="176.1188"
+ y="32.125"
+ id="tspan2476"
+ style="font-size:48px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">x</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g2478">
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-4,20)"
+ id="path2480"
+ style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(1,17)"
+ id="path2482"
+ style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-1,18)"
+ id="path2484"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <text
+ x="6"
+ id="text2486"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan2488"
+ style="font-size:24px">!</tspan>
+ </text>
+ <path
+ d="m 148.5,19 0,0 -26,15 0,0 0,3 -13.5,0 0,-3 c 0,0 -26,-15 -26,-15 0,0 33,-18 33,-18 0,0 32.5,18 32.5,18 z"
+ id="path2490"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:#c0a000;stroke-width:1.33340001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/images/emptybox.svg b/images/emptybox.svg
new file mode 100644
index 0000000..323cff9
--- /dev/null
+++ b/images/emptybox.svg
@@ -0,0 +1,356 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <defs
+ id="defs32">
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="64"
+ y2="0"
+ id="linearGradient2540"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(34.404412,58.027574)" />
+ <linearGradient
+ id="linearGradient2504">
+ <stop
+ id="stop2506"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2508"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="64"
+ y2="0"
+ id="linearGradient2510"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,4.96875)" />
+ <linearGradient
+ x1="210"
+ y1="10.5"
+ x2="0"
+ y2="10.5"
+ id="linearGradient2512"
+ xlink:href="#linearGradient3886"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2514">
+ <stop
+ id="stop2516"
+ style="stop-color:#0000ff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2518"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="210"
+ y1="10.5"
+ x2="0"
+ y2="10.5"
+ id="linearGradient2520"
+ xlink:href="#linearGradient3886"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="64"
+ y2="0"
+ id="linearGradient2522"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,4.96875)" />
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="104"
+ y2="21"
+ id="linearGradient2524"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,5.4999997)" />
+ <linearGradient
+ id="linearGradient2492">
+ <stop
+ id="stop2494"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2496"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="104"
+ y2="21"
+ id="linearGradient2493"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2486">
+ <stop
+ id="stop2488"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2490"
+ style="stop-color:#0000ff;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2509">
+ <stop
+ id="stop2511"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2513"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2515"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="128.68382"
+ y2="22"
+ id="linearGradient2608"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2595">
+ <stop
+ id="stop2597"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2599"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2601"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2473">
+ <stop
+ id="stop2475"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2477"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="64"
+ y2="0"
+ id="linearGradient4238"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,4.96875)" />
+ <linearGradient
+ x1="210"
+ y1="10.5"
+ x2="0"
+ y2="10.5"
+ id="linearGradient4211"
+ xlink:href="#linearGradient3886"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3886">
+ <stop
+ id="stop3888"
+ style="stop-color:#0000ff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3890"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="210"
+ y1="10.5"
+ x2="0"
+ y2="10.5"
+ id="linearGradient2496"
+ xlink:href="#linearGradient3886"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="64"
+ y2="0"
+ id="linearGradient2501"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,4.96875)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="128.68382"
+ y2="22"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="104"
+ y2="21"
+ id="linearGradient2539"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2533">
+ <stop
+ id="stop2535"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2537"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="128.68382"
+ y2="22"
+ id="linearGradient2543"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.75,0,0,0.75,160.75,11.125)" />
+ </defs>
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 C 3,30 0.5,26.5 0.5,23 L 0.5,15 C 0.5,11.75 3,6.5 5.5,4.5 C 8,2.5 11.5,0.5 15,0.5 L 751.5,0.5 C 754,0.5 758.5,1.5 762,4.5 C 765.5,7.25 766.5,12 766.5,15 L 766.5,23 C 766.5,26.5 764.5,30 762,32.5 C 759.5,35 755.5,37.5 751.5,37.5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="M 79.5,438.5 C 79.5,443 75.75,446.5 71,446.5 C 66.5,446.5 62.75,443 62.75,438.5 C 62.75,434 66.5,430.25 71,430.25 C 75.75,430.25 79.5,434 79.5,438.5 L 79.5,438.5 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <g
+ transform="translate(6,0)"
+ id="g17">
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-4,20)"
+ id="path19"
+ style="opacity:1;fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(1,17)"
+ id="path21"
+ style="opacity:1;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-1,18)"
+ id="path23"
+ style="opacity:1;fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <text
+ x="6"
+ id="text25"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan27"
+ style="font-size:24px">!</tspan>
+ </text>
+ <g
+ transform="matrix(0.75,0,0,0.75,63.573764,3.5)"
+ id="g2611">
+ <g
+ transform="translate(59.878677,45.67647)"
+ id="g2533">
+ <path
+ d="M 64.871323,-38.92647 L 81.371323,-38.92647 L 81.371323,-32.92647 L 77.371323,-32.92647 L 77.371323,-35.92647 L 66.871323,-35.92647"
+ id="path2493"
+ style="fill:#e0e000;fill-opacity:1;stroke:#a08000;stroke-width:1.5;stroke-opacity:1" />
+ <path
+ d="M 64.871323,-11.42647 L 81.371323,-11.42647 L 81.371323,-17.42647 L 77.371323,-17.42647 L 77.371323,-14.42647 L 66.871323,-14.42647"
+ id="path2495"
+ style="fill:#e0e000;fill-opacity:1;stroke:#908000;stroke-width:1.5;stroke-opacity:1" />
+ </g>
+ <path
+ d="M 47,1 C 63,1 120,1 120,1 C 120,1 124.1318,3.4865526 125.5,5 C 126.89747,6.5458243 129,11 129,11 L 129,30 C 129,30 126.78295,33.693654 125.5,35 C 124.07044,36.455629 120,39 120,39 L 47,39 L 47,39 L 47,43 L 27,43 L 27,39 L 10,39 C 10,39 5.9295605,36.455629 4.5,35 C 3.2170498,33.693654 1,30 1,30 L 1,11 C 1,11 3.1025283,6.5458243 4.5,5 C 5.8682021,3.4865526 10,1 10,1 L 27,1 L 27,6 L 47,6 L 47,1 z"
+ id="path10"
+ style="fill:url(#linearGradient3172);fill-opacity:1;stroke:#c0a000;stroke-width:2;stroke-opacity:1" />
+ <path
+ d="M 13,12 L 18,12 L 18,16 L 24.999999,16 L 24.999999,12 L 116.50001,12 L 116.50001,32 L 24.999999,32 L 24.999999,28 L 18,28 L 18,32 L 13,32 L 13,12 z"
+ id="path9"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-opacity:1" />
+ </g>
+ <text
+ x="95.277344"
+ y="29"
+ id="text2618"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"><tspan
+ x="95.277344"
+ y="29"
+ id="tspan2620"
+ style="font-size:24px;fill:#ff0000;fill-opacity:1">???</tspan></text>
+ <path
+ d="M 161.5,11.5 L 165.25,11.5 L 165.25,14.5 L 170.5,14.5 L 170.5,11.5 L 239.125,11.5 L 239.125,26.5 L 170.5,26.5 L 170.5,23.5 L 165.25,23.5 L 165.25,26.5 L 161.5,26.5 L 161.5,11.5 z"
+ id="path2541"
+ style="fill:url(#linearGradient2543);fill-opacity:1;stroke:none;stroke-width:2;stroke-opacity:1" />
+</svg>
diff --git a/images/emptyheap.svg b/images/emptyheap.svg
new file mode 100644
index 0000000..8ad93ef
--- /dev/null
+++ b/images/emptyheap.svg
@@ -0,0 +1,73 @@
+<?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.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <defs
+ id="defs32" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 C 3,30 0.5,26.5 0.5,23 L 0.5,15 C 0.5,11.75 3,6.5 5.5,4.5 C 8,2.5 11.5,0.5 15,0.5 L 751.5,0.5 C 754,0.5 758.5,1.5 762,4.5 C 765.5,7.25 766.5,12 766.5,15 L 766.5,23 C 766.5,26.5 764.5,30 762,32.5 C 759.5,35 755.5,37.5 751.5,37.5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="M 79.5,438.5 C 79.5,443 75.75,446.5 71,446.5 C 66.5,446.5 62.75,443 62.75,438.5 C 62.75,434 66.5,430.25 71,430.25 C 75.75,430.25 79.5,434 79.5,438.5 L 79.5,438.5 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <text
+ x="-108"
+ id="text13"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="62"
+ y="28"
+ id="tspan15"
+ style="font-size:24px">[]</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g17">
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-4,20)"
+ id="path19"
+ style="opacity:1;fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(1,17)"
+ id="path21"
+ style="opacity:1;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-1,18)"
+ id="path23"
+ style="opacity:1;fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <text
+ x="6"
+ id="text25"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan27"
+ style="font-size:24px">!</tspan>
+ </text>
+</svg>
diff --git a/images/fileoff.svg b/images/fileoff.svg
new file mode 100644
index 0000000..d19b61e
--- /dev/null
+++ b/images/fileoff.svg
@@ -0,0 +1,67 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="60"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172-9"
+ xlink:href="#linearGradient3166-6"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ id="linearGradient3166-6">
+ <stop
+ id="stop3168-5"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170-6"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="matrix(1.33,0,0,1.33,11.38,0.3488486)"
+ id="g3675">
+ <path
+ d="m 27,27.08073 c 0,1.68237 -1.12132,3.074271 -3,3.074271 l -23,0 0,-26.9999999 8.8039216,0 1.6960784,2 15.5,0 0,21.9257289 0,0 z"
+ id="path2458-4"
+ style="fill:#ffffff;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 27,27.08073 c 0,1.68237 -1.12132,3.074271 -3,3.074271 l -23,0 0,-17 8.4852943,0 L 11.5,9.1550006 l 15.5,0 0,17.9257294 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/fileon.svg b/images/fileon.svg
new file mode 100644
index 0000000..f718dbc
--- /dev/null
+++ b/images/fileon.svg
@@ -0,0 +1,63 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="60"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172-9"
+ xlink:href="#linearGradient3166-6"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ id="linearGradient3166-6">
+ <stop
+ id="stop3168-5"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170-6"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <path
+ d="m 43.29,36.366219 c 0,2.237553 -1.491356,4.088781 -3.99,4.088781 l -30.5900003,0 0,-35.9099999 11.7092153,0 2.255785,2.66 20.615,0 0,29.1612189 0,0 z"
+ id="path2458-4"
+ style="fill:#ff0000;fill-opacity:1;stroke:#000000;stroke-width:2.66000009;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 43.29,36.366219 c 0,2.237553 -1.491356,4.088781 -3.99,4.088781 l -30.5900003,0 8.0000003,-18.61 11.285441,0 2.679559,-5.320001 20.615,0 -8,19.84122 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2.66000009;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/images/filesmall.svg b/images/filesmall.svg
new file mode 100644
index 0000000..a259ac0
--- /dev/null
+++ b/images/filesmall.svg
@@ -0,0 +1,43 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="28.128611"
+ height="31.155001"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ </defs>
+ <path
+ d="m 27,27.08073 c 0,1.68237 -1.12132,3.074271 -3,3.074271 l -23,0 0,-26.9999999 8.8039216,0 1.6960784,2 15.5,0 0,21.9257289 0,0 z"
+ id="path2458-4"
+ style="fill:#ffffff;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 27,27.08073 c 0,1.68237 -1.12132,3.074271 -3,3.074271 l -23,0 0,-17 8.4852943,0 L 11.5,9.1550006 l 15.5,0 0,17.9257294 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/images/help.svg b/images/help.svg
new file mode 100644
index 0000000..3281351
--- /dev/null
+++ b/images/help.svg
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <metadata
+ id="metadata17">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs27" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 736.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#e0e0e0;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+</svg>
diff --git a/images/help1200.svg b/images/help1200.svg
new file mode 100644
index 0000000..a284973
--- /dev/null
+++ b/images/help1200.svg
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="1167"
+ height="38"
+ id="svg2">
+ <metadata
+ id="metadata17">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs27" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 1136.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 z"
+ id="path4"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#e0e0e0;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(1053.75,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+</svg>
diff --git a/images/incompatible.svg b/images/incompatible.svg
new file mode 100644
index 0000000..433bf4d
--- /dev/null
+++ b/images/incompatible.svg
@@ -0,0 +1,381 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <metadata
+ id="metadata73">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs32">
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="64"
+ y2="0"
+ id="linearGradient2540"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(34.404412,58.027574)" />
+ <linearGradient
+ id="linearGradient2504">
+ <stop
+ id="stop2506"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2508"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="64"
+ y2="0"
+ id="linearGradient2510"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,4.96875)" />
+ <linearGradient
+ x1="210"
+ y1="10.5"
+ x2="0"
+ y2="10.5"
+ id="linearGradient2512"
+ xlink:href="#linearGradient3886"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2514">
+ <stop
+ id="stop2516"
+ style="stop-color:#0000ff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2518"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="210"
+ y1="10.5"
+ x2="0"
+ y2="10.5"
+ id="linearGradient2520"
+ xlink:href="#linearGradient3886"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="64"
+ y2="0"
+ id="linearGradient2522"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,4.96875)" />
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="104"
+ y2="21"
+ id="linearGradient2524"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,5.4999997)" />
+ <linearGradient
+ id="linearGradient2492">
+ <stop
+ id="stop2494"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2496"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="104"
+ y2="21"
+ id="linearGradient2493"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2486">
+ <stop
+ id="stop2488"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2490"
+ style="stop-color:#0000ff;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2509">
+ <stop
+ id="stop2511"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2513"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2515"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="128.68382"
+ y2="22"
+ id="linearGradient2608"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2595">
+ <stop
+ id="stop2597"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2599"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2601"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2473">
+ <stop
+ id="stop2475"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2477"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="64"
+ y2="0"
+ id="linearGradient4238"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,4.96875)" />
+ <linearGradient
+ x1="210"
+ y1="10.5"
+ x2="0"
+ y2="10.5"
+ id="linearGradient4211"
+ xlink:href="#linearGradient3886"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3886">
+ <stop
+ id="stop3888"
+ style="stop-color:#0000ff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3890"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="210"
+ y1="10.5"
+ x2="0"
+ y2="10.5"
+ id="linearGradient2496"
+ xlink:href="#linearGradient3886"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="64"
+ y2="0"
+ id="linearGradient2501"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,4.96875)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="128.68382"
+ y2="22"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ff0000;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="104"
+ y2="21"
+ id="linearGradient2539"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient2533">
+ <stop
+ id="stop2535"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2537"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="128.68382"
+ y2="22"
+ id="linearGradient2543"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.75,0,0,0.75,79.98708,11.125)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="128.68382"
+ y2="22"
+ id="linearGradient2543-4"
+ xlink:href="#linearGradient3166-4"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.75,0,0,0.75,160.75,11.125)" />
+ <linearGradient
+ id="linearGradient3166-4">
+ <stop
+ id="stop3168-3"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170-0"
+ style="stop-color:#ff0000;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="128.68382"
+ y2="22"
+ id="linearGradient3833"
+ xlink:href="#linearGradient3166-4"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.75,0,0,0.75,207.68104,11.125001)" />
+ </defs>
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 736.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <g
+ transform="translate(6,0)"
+ id="g17">
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-4,20)"
+ id="path19"
+ style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(1,17)"
+ id="path21"
+ style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-1,18)"
+ id="path23"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <text
+ x="6"
+ id="text25"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan27"
+ style="font-size:24px">!</tspan>
+ </text>
+ <text
+ x="176.34187"
+ y="25.5625"
+ id="text2618"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"><tspan
+ x="176.34187"
+ y="25.5625"
+ id="tspan2620"
+ style="font-size:24px;fill:#ff0000;fill-opacity:1">x</tspan></text>
+ <path
+ d="m 80.73708,11.5 3.75,0 0,3 5.25,0 0,-3 68.625,0 0,15 -68.625,0 0,-3 -5.25,0 0,3 -3.75,0 0,-15 z"
+ id="path2541"
+ style="fill:url(#linearGradient2543);fill-opacity:1;stroke:#ff0000;stroke-width:2;stroke-opacity:1" />
+ <path
+ d="m 208.43104,11.500001 3.75,0 0,2.999999 5.25,0 0,-2.999999 68.625,0 0,14.999999 -68.625,0 0,-3 -5.25,0 0,3 -3.75,0 0,-14.999999 z"
+ id="path2541-7"
+ style="fill:url(#linearGradient3833);fill-opacity:1;stroke:#ff0000;stroke-width:2;stroke-opacity:1" />
+</svg>
diff --git a/images/info.svg b/images/info.svg
new file mode 100644
index 0000000..3b32b93
--- /dev/null
+++ b/images/info.svg
@@ -0,0 +1,35 @@
+<?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.0"
+ width="240"
+ height="360"
+ id="svg2">
+ <defs
+ id="defs27" />
+ <path
+ d="m 1.0120134,358.98799 c 0,0 0,-357.9759766 0,-357.9759766 l 237.9759766,0 c 0,0 0,357.9759766 0,357.9759766 l -237.9759766,0 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:2.02402687px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(129.51998,60.625002)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+</svg>
diff --git a/images/journaloff.svg b/images/journaloff.svg
new file mode 100644
index 0000000..7123f98
--- /dev/null
+++ b/images/journaloff.svg
@@ -0,0 +1,63 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="60"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ </defs>
+ <g
+ transform="translate(3.3745,-5)"
+ id="activity-journal"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ id="line2460"
+ y2="48.881001"
+ y1="6.1209998"
+ x2="21.341"
+ x1="21.341"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/images/journalon.svg b/images/journalon.svg
new file mode 100644
index 0000000..1cac534
--- /dev/null
+++ b/images/journalon.svg
@@ -0,0 +1,62 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="60"
+ height="45"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ </defs>
+ <path
+ d="m 17.066502,43.917067 24.519997,0 c 2.225,0 3.439,-1.447 3.439,-3.441 l 0,-27.28 c 0,-1.73 -1.732,-3.4410005 -3.439,-3.4410005 l -4.388997,0"
+ id="path2999"
+ style="fill:#ff0000;fill-opacity:1;stroke:#010101;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 37.197502,33.507067 c 0,1.836 -1.345,3.201 -3.441,4.047 l -16.69,6.363 0,-34.1620005 16.69,-8.5990001 c 2.228,-0.39399999 3.441,0.8400001 3.441,2.8340001 l 0,29.5170005 z"
+ id="path3005"
+ style="fill:#ffffff;fill-opacity:1;stroke:#010101;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 19.754502,37.522067 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path3007"
+ style="fill:none;stroke:#010101;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 19.754502,26.921067 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path3009"
+ style="fill:none;stroke:#010101;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 19.754502,16.593067 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path3011"
+ style="fill:none;stroke:#010101;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round" />
+ <line
+ id="line3013"
+ y2="6.4200654"
+ y1="41.448067"
+ x2="23.539497"
+ x1="23.539497"
+ style="fill:none;stroke:#010101;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round" />
+</svg>
diff --git a/images/journalsmall.svg b/images/journalsmall.svg
new file mode 100644
index 0000000..5b93089
--- /dev/null
+++ b/images/journalsmall.svg
@@ -0,0 +1,63 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="28.128611"
+ height="31.155001"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ </defs>
+ <g
+ transform="matrix(0.67,0,0,0.67,-3.77478,-2.8475)"
+ id="activity-journal"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ id="line2460"
+ y2="48.881001"
+ y1="6.1209998"
+ x2="21.341"
+ x1="21.341"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/images/list.svg b/images/list.svg
new file mode 100644
index 0000000..f6cd83b
--- /dev/null
+++ b/images/list.svg
@@ -0,0 +1,66 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="23.115"
+ height="30.485001"
+ id="svg2">
+ <defs
+ id="defs5">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ </defs>
+ <path
+ d="m 1.1725,29.3125 0,-28.14 20.77,-4.994e-4 0,28.1404994 -20.77,0 z"
+ id="path3199"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#00000f;stroke-width:2.34500003;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none" />
+ <path
+ d="m 10.163613,13.9225 c 6.445974,0 6.445974,0 6.445974,0"
+ id="path3163"
+ style="fill:none;stroke:#000000;stroke-width:1.95582581;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10.163613,18.137087 c 6.445974,0 6.445974,0 6.445974,0"
+ id="path3165"
+ style="fill:none;stroke:#000000;stroke-width:1.95582581;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 10.163613,9.707913 c 6.445974,0 6.445974,0 6.445974,0"
+ id="path3167"
+ style="fill:none;stroke:#000000;stroke-width:1.95582581;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -8.0732694,14.864066 a 1.0091586,0.90403795 0 1 1 -2.0183176,0 1.0091586,0.90403795 0 1 1 2.0183176,0 z"
+ transform="matrix(0.44960373,0,0,0.50188322,10.685073,2.2478876)"
+ id="path2850"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:2.29999995;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -8.0732694,14.864066 a 1.0091586,0.90403795 0 1 1 -2.0183176,0 1.0091586,0.90403795 0 1 1 2.0183176,0 z"
+ transform="matrix(0.44960373,0,0,0.50188322,10.685073,10.677062)"
+ id="path2850-6"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:2.29999995;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m -8.0732694,14.864066 a 1.0091586,0.90403795 0 1 1 -2.0183176,0 1.0091586,0.90403795 0 1 1 2.0183176,0 z"
+ transform="matrix(0.44960373,0,0,0.50188322,10.685073,6.462475)"
+ id="path2850-69"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:2.29999995;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/images/metric.svg b/images/metric.svg
new file mode 100644
index 0000000..33f6d3d
--- /dev/null
+++ b/images/metric.svg
@@ -0,0 +1,489 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="1200"
+ height="900"
+ id="svg2">
+ <metadata
+ id="metadata91">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs4" />
+ <path
+ d="m 0,764.8 1200,0"
+ id="path5"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,686.1 1200,0"
+ id="path7"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,607.4 1200,0"
+ id="path9"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,528.7 1200,0"
+ id="path11"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,371.3 1200,0"
+ id="path13"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,292.6 1200,0"
+ id="path15"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,213.9 1200,0"
+ id="path17"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,135.2 1200,0"
+ id="path19"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 993.5,0 0,900"
+ id="path21"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 914.8,0 0,900"
+ id="path23"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 836.1,0 0,900"
+ id="path25"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 757.4,0 0,900"
+ id="path27"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 678.7,0 0,900"
+ id="path29"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 521.3,0 0,900"
+ id="path31"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 442.6,0 0,900"
+ id="path33"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 363.9,0 0,900"
+ id="path35"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 285.2,0 0,900"
+ id="path37"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 206.5,0 0,900"
+ id="path39"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,764.8 20,0"
+ id="path41"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,686.1 20,0"
+ id="path43"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,607.4 20,0"
+ id="path45"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,528.7 20,0"
+ id="path47"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,450 1200,0"
+ id="path49"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,371.3 20,0"
+ id="path51"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,292.6 20,0"
+ id="path53"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,213.9 20,0"
+ id="path55"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,56.5 1200,0"
+ id="path19-8"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,56.5 20,0"
+ id="path57"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 600,0 0,900"
+ id="path69"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="603.90137"
+ y="365.79999"
+ id="text3218"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="603.90137"
+ y="365.79999"
+ id="tspan3220">1</tspan></text>
+ <text
+ x="604.26758"
+ y="287.10001"
+ id="text3218-8"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="604.26758"
+ y="287.10001"
+ id="tspan3220-7">2</tspan></text>
+ <text
+ x="604.23828"
+ y="208.25839"
+ id="text3218-7"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="604.23828"
+ y="208.25839"
+ id="tspan3220-2">3</tspan></text>
+ <text
+ x="604.51172"
+ y="51"
+ id="text3218-2"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="604.51172"
+ y="51"
+ id="tspan3220-26">5</tspan></text>
+ <path
+ d="m 993.5,440 0,20"
+ id="path59"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="997.72852"
+ y="444.8584"
+ id="text3218-6"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="997.72852"
+ y="444.8584"
+ id="tspan3220-1">5</tspan></text>
+ <path
+ d="m 914.8,440 0,20"
+ id="path61"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="919.31171"
+ y="444.8584"
+ id="text3218-9"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="919.31171"
+ y="444.8584"
+ id="tspan3220-4">4</tspan></text>
+ <path
+ d="m 836.1,440 0,20"
+ id="path63"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="840.33826"
+ y="444.8584"
+ id="text3218-0"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="840.33826"
+ y="444.8584"
+ id="tspan3220-9">3</tspan></text>
+ <path
+ d="m 757.4,440 0,20"
+ id="path65"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="761.6676"
+ y="444.8584"
+ id="text3218-71"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="761.6676"
+ y="444.8584"
+ id="tspan3220-15">2</tspan></text>
+ <path
+ d="m 678.7,440 0,20"
+ id="path67"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="682.60138"
+ y="444.8584"
+ id="text3218-76"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="682.60138"
+ y="444.8584"
+ id="tspan3220-73">1</tspan></text>
+ <path
+ d="m 521.3,440 0,20"
+ id="path71"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="506.81171"
+ y="444.8584"
+ id="text3218-5"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="506.81171"
+ y="444.8584"
+ id="tspan3220-6">–1</tspan></text>
+ <path
+ d="m 442.6,440 0,20"
+ id="path73"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="428.11172"
+ y="444.8584"
+ id="text3218-94"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="428.11172"
+ y="444.8584"
+ id="tspan3220-8">–2</tspan></text>
+ <path
+ d="m 363.9,440 0,20"
+ id="path75"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="349.41171"
+ y="444.8584"
+ id="text3218-93"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="349.41171"
+ y="444.8584"
+ id="tspan3220-90">–3</tspan></text>
+ <path
+ d="m 285.2,440 0,20"
+ id="path77"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="270.71173"
+ y="444.8584"
+ id="text3218-85"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="270.71173"
+ y="444.8584"
+ id="tspan3220-0">–4</tspan></text>
+ <text
+ x="583.51172"
+ y="540.8584"
+ id="text3218-1"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="583.51172"
+ y="540.8584"
+ id="tspan3220-5">–1</tspan></text>
+ <text
+ x="583.51172"
+ y="618.8584"
+ id="text3218-4"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="583.51172"
+ y="618.8584"
+ id="tspan3220-81">–2</tspan></text>
+ <text
+ x="583.51172"
+ y="698.8584"
+ id="text3218-30"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="583.51172"
+ y="698.8584"
+ id="tspan3220-44">–3</tspan></text>
+ <text
+ x="583.51172"
+ y="776.8584"
+ id="text3218-47"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="583.51172"
+ y="776.8584"
+ id="tspan3220-63">–4</tspan></text>
+ <text
+ x="604.34082"
+ y="444.8584"
+ id="text3218-96"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="604.34082"
+ y="444.8584"
+ id="tspan3220-21">0</tspan></text>
+ <text
+ x="15.140625"
+ y="51.681641"
+ id="text3218-2-7"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="15.140625"
+ y="51.681641"
+ id="tspan3220-26-8">(–7, 5)</tspan></text>
+ <text
+ x="1154.1406"
+ y="51.681641"
+ id="text3218-2-7-5"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="1154.1406"
+ y="51.681641"
+ id="tspan3220-26-8-7">(7, 5)</tspan></text>
+ <text
+ x="1154.1406"
+ y="839.68164"
+ id="text3218-2-7-1"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="1154.1406"
+ y="839.68164"
+ id="tspan3220-26-8-8">(7, –5)</tspan></text>
+ <text
+ x="9.140625"
+ y="853.68164"
+ id="text3218-2-7-1-5"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="9.140625"
+ y="853.68164"
+ id="tspan3220-26-8-8-9">(–7, –5)</tspan></text>
+ <path
+ d="m 0,843.5 1200,0"
+ id="path5-7"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,135.2 20,0"
+ id="path57-8"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="604.51172"
+ y="130.0584"
+ id="text3218-2-5"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="604.51172"
+ y="130.0584"
+ id="tspan3220-26-3">4</tspan></text>
+ <path
+ d="m 590,843.5 20,0"
+ id="path41-6"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="583.51172"
+ y="856.74347"
+ id="text3218-47-2"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="583.51172"
+ y="856.74347"
+ id="tspan3220-63-0">–5</tspan></text>
+ <path
+ d="m 1072.2,0 0,900"
+ id="path21-1"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 1150.9,0 0,900"
+ id="path21-1-1"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 127.8,0 0,900"
+ id="path39-3"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 127.8,440 0,20"
+ id="path79"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="113.31172"
+ y="444.8584"
+ id="text3218-3"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="113.31172"
+ y="444.8584"
+ id="tspan3220-85">–6</tspan></text>
+ <path
+ d="m 49.1,0 0,900"
+ id="path39-3-8"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 1072.2,440 0,20"
+ id="path59-9"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="1076.4286"
+ y="444.8584"
+ id="text3218-6-6"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="1076.4286"
+ y="444.8584"
+ id="tspan3220-1-5">6</tspan></text>
+ <path
+ d="m 1150.9,439.99999 0,20"
+ id="path59-5"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="1155.1285"
+ y="444.8584"
+ id="text3218-6-3"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="1155.1285"
+ y="444.8584"
+ id="tspan3220-1-8">7</tspan></text>
+ <path
+ d="m 206.5,440 0,20"
+ id="path79-4"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="192.01172"
+ y="444.8584"
+ id="text3218-3-1"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="192.01172"
+ y="444.8584"
+ id="tspan3220-85-9">–5</tspan></text>
+ <path
+ d="m 49.1,440.00001 0,20"
+ id="path79-3"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ x="34.611721"
+ y="444.8584"
+ id="text3218-3-6"
+ xml:space="preserve"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"><tspan
+ x="34.611721"
+ y="444.8584"
+ id="tspan3220-85-4">–7</tspan></text>
+</svg>
diff --git a/images/negroot.svg b/images/negroot.svg
new file mode 100644
index 0000000..43b6eb1
--- /dev/null
+++ b/images/negroot.svg
@@ -0,0 +1,74 @@
+<?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.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <defs
+ id="defs32" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 736.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <text
+ x="-108"
+ id="text13"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="62"
+ y="28"
+ id="tspan15"
+ style="font-size:24px">√–1</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g17">
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-4,20)"
+ id="path19"
+ style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(1,17)"
+ id="path21"
+ style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-1,18)"
+ id="path23"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <text
+ x="6"
+ id="text25"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan27"
+ style="font-size:24px">!</tspan>
+ </text>
+</svg>
diff --git a/images/nocode.svg b/images/nocode.svg
new file mode 100644
index 0000000..6c71f30
--- /dev/null
+++ b/images/nocode.svg
@@ -0,0 +1,125 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <defs
+ id="defs41">
+ <linearGradient
+ id="linearGradient2492">
+ <stop
+ id="stop2494"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop2496"
+ style="stop-color:#ff0000;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient2508"
+ xlink:href="#linearGradient2492"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.4746037,0,0,0.6708419,62.671562,26.937823)" />
+ </defs>
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 C 3,30 0.5,26.5 0.5,23 L 0.5,15 C 0.5,11.75 3,6.5 5.5,4.5 C 8,2.5 11.5,0.5 15,0.5 L 751.5,0.5 C 754,0.5 758.5,1.5 762,4.5 C 765.5,7.25 766.5,12 766.5,15 L 766.5,23 C 766.5,26.5 764.5,30 762,32.5 C 759.5,35 755.5,37.5 751.5,37.5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="M 79.5,438.5 C 79.5,443 75.75,446.5 71,446.5 C 66.5,446.5 62.75,443 62.75,438.5 C 62.75,434 66.5,430.25 71,430.25 C 75.75,430.25 79.5,434 79.5,438.5 L 79.5,438.5 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <text
+ id="text13"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="128"
+ y="28"
+ id="tspan26"
+ style="font-size:24px">???</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g28">
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-4,20)"
+ id="path30"
+ style="opacity:1;fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(1,17)"
+ id="path32"
+ style="opacity:1;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-1,18)"
+ id="path34"
+ style="opacity:1;fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <text
+ x="6"
+ id="text20"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan22"
+ style="font-size:24px">!</tspan>
+ </text>
+ <path
+ d="M 109.48191,9.094338 L 120.53691,9.094338 L 120.53691,13.114338 L 117.85691,13.114338 L 117.85691,11.104338 L 110.82191,11.104338"
+ id="path10"
+ style="fill:#e00000;fill-opacity:1;stroke:#800000;stroke-width:1.5;stroke-opacity:1" />
+ <path
+ d="M 109.48191,27.519338 L 120.53691,27.519338 L 120.53691,23.499338 L 117.85691,23.499338 L 117.85691,25.509338 L 110.82191,25.509338"
+ id="path12"
+ style="fill:#e00000;fill-opacity:1;stroke:#800000;stroke-width:1.5;stroke-opacity:1" />
+ <path
+ d="M 94.74191,5.744338 C 105.46191,5.744338 105.46191,5.744338 105.46191,5.744338 C 105.46191,5.744338 108.23021,7.4103282 109.14691,8.424338 C 110.08322,9.4600403 111.49191,12.444338 111.49191,12.444338 L 111.49191,25.174338 C 111.49191,25.174338 110.00649,27.649086 109.14691,28.524338 C 108.1891,29.499609 105.46191,31.204338 105.46191,31.204338 L 94.07191,31.204338 L 94.07191,31.204338 L 94.07191,33.884338 L 80.67191,33.884338 L 80.67191,31.204338 L 69.28191,31.204338 C 69.28191,31.204338 66.554716,29.499609 65.59691,28.524338 C 64.737333,27.649086 63.25191,25.174338 63.25191,25.174338 L 63.25191,12.444338 C 63.25191,12.444338 64.660604,9.4600403 65.59691,8.424338 C 66.513605,7.4103282 69.28191,5.744338 69.28191,5.744338 L 80.00191,5.744338 L 80.00191,9.094338 L 94.74191,9.094338 L 94.74191,5.744338 z"
+ id="path14"
+ style="fill:url(#linearGradient2508);fill-opacity:1;stroke:#a00000;stroke-width:1.99999988;stroke-opacity:1" />
+ <g
+ transform="matrix(0.75,0,0,0.75,60.994782,-2.8023809)"
+ id="g2503">
+ <path
+ d="M 36.900421,42.139076 C 40.57847,42.139076 46.040263,40.559298 46.040263,35.912927 C 46.040263,30.744416 41.284228,30.266809 39.311914,29.743556 C 37.131988,29.298791 35.679932,28.678125 35.592711,27.820324 C 35.443451,26.349651 36.303997,25.776856 38.016489,25.776856 C 38.016489,25.776856 42.090107,27.816428 45.550716,26.188778 C 46.526124,25.730654 48.273013,23.590328 48.273013,22.114088 C 48.273013,20.637291 42.633706,18.930597 40.922443,18.930597 C 39.209951,18.930597 37.753595,21.072036 37.753595,21.072036 C 34.32984,21.072036 30.90547,24.02563 30.90547,26.979224 C 30.90547,29.932261 34.181195,32.242365 38.096339,32.886411 C 39.916323,33.147481 41.438401,34.122178 41.092586,35.913483 C 40.81188,37.368014 39.252947,38.867077 35.956953,38.867077 C 33.345219,38.867077 27.787605,38.745171 26.900033,36.596495 C 26.329407,35.212659 27.003225,33.699123 27.859471,33.699123 L 27.842886,33.610059 C 26.97067,33.522108 24.435101,33.699123 24.435101,36.65216 C 24.434487,40.438505 30.051681,42.139076 36.900421,42.139076 z"
+ id="path2474"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 45.550102,26.189335 C 43.938959,26.029576 41.489383,25.076031 40.963596,23.39383"
+ id="path3615"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <circle
+ cx="35.805"
+ cy="10.96"
+ r="1.676"
+ transform="matrix(0.6142367,0,0,0.5566517,19.396517,15.137572)"
+ id="circle2478"
+ style="fill:#000000;fill-opacity:1;stroke-width:3.42034841;stroke-miterlimit:4;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/nofile.svg b/images/nofile.svg
new file mode 100644
index 0000000..e46c576
--- /dev/null
+++ b/images/nofile.svg
@@ -0,0 +1,108 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2467">
+ <defs
+ id="defs2493">
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 736.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <text
+ x="-48"
+ id="text2474"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="122"
+ y="28"
+ id="tspan2476"
+ style="font-size:24px">???</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g2478">
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-4,20)"
+ id="path2480"
+ style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(1,17)"
+ id="path2482"
+ style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-1,18)"
+ id="path2484"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <text
+ x="6"
+ id="text2486"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan2488"
+ style="font-size:24px">!</tspan>
+ </text>
+ <g
+ transform="translate(73.5,2.344999)"
+ id="g3009">
+ <path
+ d="m 27,27.08073 c 0,1.68237 -1.12132,3.074271 -3,3.074271 l -23,0 0,-26.9999999 8.8039216,0 1.6960784,2 15.5,0 0,21.9257289 0,0 z"
+ id="path2458-4"
+ style="fill:#ffffff;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 27,27.08073 c 0,1.68237 -1.12132,3.074271 -3,3.074271 l -23,0 0,-17 8.4852943,0 L 11.5,9.1550006 l 15.5,0 0,17.9257294 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/noinput.svg b/images/noinput.svg
new file mode 100644
index 0000000..eee29e2
--- /dev/null
+++ b/images/noinput.svg
@@ -0,0 +1,93 @@
+<?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.0"
+ width="767"
+ height="38"
+ id="svg2393">
+ <defs
+ id="defs2427" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 C 3,30 0.5,26.5 0.5,23 L 0.5,15 C 0.5,11.75 3,6.5 5.5,4.5 C 8,2.5 11.5,0.5 15,0.5 L 751.5,0.5 C 754,0.5 758.5,1.5 762,4.5 C 765.5,7.25 766.5,12 766.5,15 L 766.5,23 C 766.5,26.5 764.5,30 762,32.5 C 759.5,35 755.5,37.5 751.5,37.5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="M 79.5,438.5 C 79.5,443 75.75,446.5 71,446.5 C 66.5,446.5 62.75,443 62.75,438.5 C 62.75,434 66.5,430.25 71,430.25 C 75.75,430.25 79.5,434 79.5,438.5 L 79.5,438.5 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <g
+ transform="translate(0,-0.25)"
+ id="g2400">
+ <path
+ d="M 122.5,8.5 L 133.5,8.5 L 133.5,12.5 L 131,12.5 L 131,10.5 L 124,10.5"
+ id="path2402"
+ style="fill:#a0a0a0;fill-opacity:1;stroke:#808080;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 122.5,27 L 133.5,27 L 133.5,23 L 131,23 L 131,25 L 124,25"
+ id="path2404"
+ style="fill:#a0a0a0;fill-opacity:1;stroke:#808080;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 108,5.25 C 118.5,5.25 118.5,5.25 118.5,5.25 C 118.5,5.25 121.5,7 122.25,8 C 123,9 124.5,12 124.5,12 L 124.5,24.5 C 124.5,24.5 123,27 122.25,28 C 121.25,29 118.5,30.5 118.5,30.5 L 107.25,30.5 L 107.25,30 L 107.25,33.25 L 94,33.25 L 94,30.5 L 82.5,30.5 C 82.5,30.5 80,29 79,28 C 78,27 76.5,24.5 76.5,24.5 L 76.5,12 C 76.5,12 78,9 79,8 C 80,7 82.5,5.25 82.5,5.25 L 93.25,5.25 L 93.25,8.5 L 108,8.5 L 108,5.25 z"
+ id="path2406"
+ style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:1.33340001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <path
+ d="M 151.5,17.875 C 151.5,26.780592 144.95216,34 136.875,34 C 128.79784,34 122.25,26.780592 122.25,17.875 C 122.25,8.9694084 128.79784,1.75 136.875,1.75 C 144.95216,1.75 151.5,8.9694084 151.5,17.875 L 151.5,17.875 z"
+ transform="matrix(0.934374,0,0,0.8474554,3.8575668,3.8517347)"
+ id="path2408"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ff0000;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text2410"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="170"
+ y="28"
+ id="tspan2412"
+ style="font-size:24px">???</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g2414">
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-4,20)"
+ id="path2416"
+ style="opacity:1;fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(1,17)"
+ id="path2418"
+ style="opacity:1;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-1,18)"
+ id="path2420"
+ style="opacity:1;fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <text
+ x="6"
+ id="text2422"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan2424"
+ style="font-size:24px">!</tspan>
+ </text>
+</svg>
diff --git a/images/nojournal.svg b/images/nojournal.svg
new file mode 100644
index 0000000..fd8ec5a
--- /dev/null
+++ b/images/nojournal.svg
@@ -0,0 +1,144 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2467">
+ <defs
+ id="defs2493">
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172-5"
+ xlink:href="#linearGradient3166-4"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ id="linearGradient3166-4">
+ <stop
+ id="stop3168-7"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170-6"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 736.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <text
+ x="-48"
+ id="text2474"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="122"
+ y="28"
+ id="tspan2476"
+ style="font-size:24px">???</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g2478">
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-4,20)"
+ id="path2480"
+ style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(1,17)"
+ id="path2482"
+ style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-1,18)"
+ id="path2484"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <text
+ x="6"
+ id="text2486"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan2488"
+ style="font-size:24px">!</tspan>
+ </text>
+ <g
+ transform="matrix(0.67,0,0,0.67,67.66091,0.575)"
+ id="activity-journal"
+ style="stroke:#000000;stroke-opacity:1;display:block">
+ <path
+ d="M 45.866,44.669 C 45.866,47.18 44.338,49 41.534,49 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -10e-4,34.339 0,0 z"
+ id="path2458"
+ style="fill:#ffffff;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="21.341"
+ x2="21.341"
+ y1="6.1209998"
+ y2="48.881001"
+ id="line2460" />
+ <path
+ d="m 7.384,14.464 c 0,0 2.084,0.695 4.17,0.695 2.086,0 4.173,-0.695 4.173,-0.695"
+ id="path2462"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,28.021 c 0,0 1.912,0.695 4.345,0.695 2.433,0 3.999,-0.695 3.999,-0.695"
+ id="path2464"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 7.384,41.232 c 0,0 1.736,0.695 4.518,0.695 2.781,0 3.825,-0.695 3.825,-0.695"
+ id="path2466"
+ style="fill:none;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/images/nomedia.svg b/images/nomedia.svg
new file mode 100644
index 0000000..b199b57
--- /dev/null
+++ b/images/nomedia.svg
@@ -0,0 +1,133 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <defs
+ id="defs41">
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" />
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient2558"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,346.13381,24.632067)" />
+ </defs>
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 C 3,30 0.5,26.5 0.5,23 L 0.5,15 C 0.5,11.75 3,6.5 5.5,4.5 C 8,2.5 11.5,0.5 15,0.5 L 751.5,0.5 C 754,0.5 758.5,1.5 762,4.5 C 765.5,7.25 766.5,12 766.5,15 L 766.5,23 C 766.5,26.5 764.5,30 762,32.5 C 759.5,35 755.5,37.5 751.5,37.5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="M 79.5,438.5 C 79.5,443 75.75,446.5 71,446.5 C 66.5,446.5 62.75,443 62.75,438.5 C 62.75,434 66.5,430.25 71,430.25 C 75.75,430.25 79.5,434 79.5,438.5 L 79.5,438.5 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <text
+ id="text2445"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="128"
+ y="28"
+ id="tspan2447"
+ style="font-size:24px">???</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g28">
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-4,20)"
+ id="path30"
+ style="opacity:1;fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(1,17)"
+ id="path32"
+ style="opacity:1;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-1,18)"
+ id="path34"
+ style="opacity:1;fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <text
+ x="6"
+ id="text36"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan38"
+ style="font-size:24px">!</tspan>
+ </text>
+ <g
+ transform="translate(64.520224,-8.9311472)"
+ id="clipping-image"
+ style="display:block">
+ <g
+ id="g2859"
+ style="display:inline">
+ <g
+ id="g2861">
+ <polygon
+ points="48.788,43.944 48.788,23.002 36.849,11.058 5.962,11.058 5.962,43.944 48.788,43.944 "
+ id="polygon2863"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5" />
+ <polyline
+ id="polyline2865"
+ points="36.849,11.058 36.849,23.002 48.788,23.002 "
+ style="fill:none;stroke:#010101;stroke-width:3.5" />
+ </g>
+ </g>
+ <path
+ d="M 27.504,23.342 C 21.246,23.342 16.033,29.583 16.033,29.583 C 16.033,29.583 21.246,35.854 27.504,35.85 C 33.763,35.845 38.979,29.576 38.979,29.576 C 38.979,29.576 33.763,23.338 27.504,23.342 z M 27.504,33.984 C 25.081,33.984 23.117,32.018 23.117,29.595 C 23.117,27.176 25.081,25.207 27.504,25.207 C 29.924,25.207 31.89,27.176 31.89,29.595 C 31.89,32.019 29.924,33.984 27.504,33.984 z"
+ id="path2867"
+ style="fill:#010101;display:inline" />
+ <circle
+ cx="27.504"
+ cy="29.597"
+ r="1.9910001"
+ id="circle2869"
+ style="fill:#010101;display:inline" />
+ </g>
+</svg>
diff --git a/images/nostack.svg b/images/nostack.svg
new file mode 100644
index 0000000..838d061
--- /dev/null
+++ b/images/nostack.svg
@@ -0,0 +1,76 @@
+<?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.0"
+ width="767"
+ height="38"
+ id="svg2467">
+ <defs
+ id="defs2493" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 C 3,30 0.5,26.5 0.5,23 L 0.5,15 C 0.5,11.75 3,6.5 5.5,4.5 C 8,2.5 11.5,0.5 15,0.5 L 751.5,0.5 C 754,0.5 758.5,1.5 762,4.5 C 765.5,7.25 766.5,12 766.5,15 L 766.5,23 C 766.5,26.5 764.5,30 762,32.5 C 759.5,35 755.5,37.5 751.5,37.5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="M 79.5,438.5 C 79.5,443 75.75,446.5 71,446.5 C 66.5,446.5 62.75,443 62.75,438.5 C 62.75,434 66.5,430.25 71,430.25 C 75.75,430.25 79.5,434 79.5,438.5 L 79.5,438.5 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <text
+ id="text2474"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="170"
+ y="28"
+ id="tspan2476"
+ style="font-size:24px">???</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g2478">
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-4,20)"
+ id="path2480"
+ style="opacity:1;fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(1,17)"
+ id="path2482"
+ style="opacity:1;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-1,18)"
+ id="path2484"
+ style="opacity:1;fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <text
+ x="6"
+ id="text2486"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan2488"
+ style="font-size:24px">!</tspan>
+ </text>
+ <path
+ d="M 148.5,19 L 148.5,19 L 122.5,34 L 122.5,34 L 122.5,37 L 109,37 L 109,34 C 109,34 83,19 83,19 C 83,19 116,1 116,1 C 116,1 148.5,19 148.5,19 z"
+ id="path2490"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:#c0a000;stroke-width:1.33340001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+</svg>
diff --git a/images/notanumber.svg b/images/notanumber.svg
new file mode 100644
index 0000000..edf9a29
--- /dev/null
+++ b/images/notanumber.svg
@@ -0,0 +1,74 @@
+<?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.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <defs
+ id="defs32" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 736.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <text
+ x="-108"
+ id="text13"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="62"
+ y="28"
+ id="tspan15"
+ style="font-size:24px">NAN</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g17">
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-4,20)"
+ id="path19"
+ style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(1,17)"
+ id="path21"
+ style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-1,18)"
+ id="path23"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <text
+ x="6"
+ id="text25"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan27"
+ style="font-size:24px">!</tspan>
+ </text>
+</svg>
diff --git a/images/overflowerror.svg b/images/overflowerror.svg
new file mode 100644
index 0000000..dd709bc
--- /dev/null
+++ b/images/overflowerror.svg
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="overflowerror.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ inkscape:window-height="723"
+ inkscape:window-width="645"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="1"
+ inkscape:cx="186.88855"
+ inkscape:cy="17.535157"
+ inkscape:window-x="376"
+ inkscape:window-y="136"
+ inkscape:current-layer="svg2" />
+ <defs
+ id="defs32">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 19 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="767 : 19 : 1"
+ inkscape:persp3d-origin="383.5 : 12.666667 : 1"
+ id="perspective20" />
+ </defs>
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 C 3,30 0.5,26.5 0.5,23 L 0.5,15 C 0.5,11.75 3,6.5 5.5,4.5 C 8,2.5 11.5,0.5 15,0.5 L 751.5,0.5 C 754,0.5 758.5,1.5 762,4.5 C 765.5,7.25 766.5,12 766.5,15 L 766.5,23 C 766.5,26.5 764.5,30 762,32.5 C 759.5,35 755.5,37.5 751.5,37.5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="M 79.5,438.5 C 79.5,443 75.75,446.5 71,446.5 C 66.5,446.5 62.75,443 62.75,438.5 C 62.75,434 66.5,430.25 71,430.25 C 75.75,430.25 79.5,434 79.5,438.5 L 79.5,438.5 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <text
+ x="-124.76469"
+ id="text13"
+ style="font-size:13.12157726px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ y="-1.9127419">
+ <tspan
+ x="61.124317"
+ y="28.704271"
+ id="tspan15"
+ style="font-size:26.24315453px">&gt;1000000</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g17">
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-4,20)"
+ id="path19"
+ style="opacity:1;fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(1,17)"
+ id="path21"
+ style="opacity:1;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-1,18)"
+ id="path23"
+ style="opacity:1;fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <text
+ x="6"
+ id="text25"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan27"
+ style="font-size:24px">!</tspan>
+ </text>
+</svg>
diff --git a/images/palettehorizontal.svg b/images/palettehorizontal.svg
new file mode 100644
index 0000000..6cc62bf
--- /dev/null
+++ b/images/palettehorizontal.svg
@@ -0,0 +1,25 @@
+<?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.0"
+ width="16"
+ height="16"
+ id="svg2">
+ <defs
+ id="defs9" />
+ <path
+ d="m 18.983051,7.5932202 a 10.915255,10.169492 0 1 1 -21.8305089,0 10.915255,10.169492 0 1 1 21.8305089,0 z"
+ transform="matrix(0.68421284,0,0,0.73438846,2.4799099,2.4236267)"
+ id="path2819"
+ style="fill:#00000f;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <rect
+ width="5.3145914"
+ height="11.314592"
+ x="5.3427043"
+ y="2.3427038"
+ id="rect5"
+ style="fill:#ffd000;fill-opacity:1;stroke:none" />
+</svg>
diff --git a/images/palettehshift.svg b/images/palettehshift.svg
new file mode 100644
index 0000000..38158aa
--- /dev/null
+++ b/images/palettehshift.svg
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="16"
+ height="16"
+ id="svg2">
+ <metadata
+ id="metadata8">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs9">
+ <linearGradient
+ x1="0"
+ y1="17.200001"
+ x2="37"
+ y2="17.200001"
+ id="linearGradient5678"
+ xlink:href="#linearGradient1234"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient1234">
+ <stop
+ id="stop3087"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3089"
+ style="stop-color:#a0ff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <path
+ d="m 18.983051,7.5932202 a 10.915255,10.169492 0 1 1 -21.8305089,0 10.915255,10.169492 0 1 1 21.8305089,0 z"
+ transform="matrix(0.68421284,0,0,0.73438846,2.4799099,2.4236267)"
+ id="path2819"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(0,-1.3333333,1.3333333,0,-2.6666664,18.666666)"
+ id="g3924">
+ <path
+ d="m 8,4 0,8"
+ id="path3106"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(0.0303029,-0.060606)"
+ id="g3896">
+ <path
+ d="M 7.939394,4.060606 6,6"
+ id="path3108"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="M 8,4.060606 9.9393942,6"
+ id="path3108-6"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(1,0,0,-1,0.0303029,16.060606)"
+ id="g3896-4">
+ <path
+ d="M 7.939394,4.060606 6,6"
+ id="path3108-0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="M 8,4.060606 9.9393942,6"
+ id="path3108-6-8"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/images/palettenext.svg b/images/palettenext.svg
new file mode 100644
index 0000000..ccecdfa
--- /dev/null
+++ b/images/palettenext.svg
@@ -0,0 +1,30 @@
+<?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.0"
+ width="16"
+ height="16"
+ id="svg2">
+ <defs
+ id="defs9" />
+ <path
+ d="m 18.983051,7.5932202 a 10.915255,10.169492 0 1 1 -21.8305089,0 10.915255,10.169492 0 1 1 21.8305089,0 z"
+ transform="matrix(0.68421284,0,0,0.73438846,2.4799099,2.4236267)"
+ id="path2819"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <rect
+ width="5.3145914"
+ height="11.314592"
+ x="5.3427043"
+ y="-11.314592"
+ transform="matrix(0,1,-1,0,0,0)"
+ id="rect5"
+ style="fill:#00fe00;fill-opacity:1;stroke:none" />
+ <path
+ d="M 15.5,8 L 10,2 10,14 15.5,8 z"
+ id="path2816"
+ style="fill:#00fe00;fill-opacity:1;stroke:#00fe00;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+</svg>
diff --git a/images/palettevertical.svg b/images/palettevertical.svg
new file mode 100644
index 0000000..552ecdf
--- /dev/null
+++ b/images/palettevertical.svg
@@ -0,0 +1,26 @@
+<?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.0"
+ width="16"
+ height="16"
+ id="svg2">
+ <defs
+ id="defs9" />
+ <path
+ d="m 18.983051,7.5932202 a 10.915255,10.169492 0 1 1 -21.8305089,0 10.915255,10.169492 0 1 1 21.8305089,0 z"
+ transform="matrix(0.68421284,0,0,0.73438846,2.4799099,2.4236267)"
+ id="path2819"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <rect
+ width="5.3145914"
+ height="11.314592"
+ x="-10.657295"
+ y="2.3427038"
+ transform="matrix(0,-1,1,0,0,0)"
+ id="rect5"
+ style="fill:#ffd000;fill-opacity:1;stroke:none" />
+</svg>
diff --git a/images/palettevshift.svg b/images/palettevshift.svg
new file mode 100644
index 0000000..504272f
--- /dev/null
+++ b/images/palettevshift.svg
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="16"
+ height="16"
+ id="svg2">
+ <metadata
+ id="metadata8">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs9">
+ <linearGradient
+ x1="0"
+ y1="17.200001"
+ x2="37"
+ y2="17.200001"
+ id="linearGradient5678"
+ xlink:href="#linearGradient1234"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient1234">
+ <stop
+ id="stop3087"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3089"
+ style="stop-color:#a0ff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <path
+ d="m 18.983051,7.5932202 a 10.915255,10.169492 0 1 1 -21.8305089,0 10.915255,10.169492 0 1 1 21.8305089,0 z"
+ transform="matrix(0.68421284,0,0,0.73438846,2.4799099,2.4236267)"
+ id="path2819"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(1.3333333,0,0,1.3333333,-2.6666664,-2.6666664)"
+ id="g3924">
+ <path
+ d="m 8,4 0,8"
+ id="path3106"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(0.0303029,-0.060606)"
+ id="g3896">
+ <path
+ d="M 7.939394,4.060606 6,6"
+ id="path3108"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="M 8,4.060606 9.9393942,6"
+ id="path3108-6"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(1,0,0,-1,0.0303029,16.060606)"
+ id="g3896-4">
+ <path
+ d="M 7.939394,4.060606 6,6"
+ id="path3108-0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ d="M 8,4.060606 9.9393942,6"
+ id="path3108-6-8"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/images/polar.svg b/images/polar.svg
new file mode 100644
index 0000000..40f5174
--- /dev/null
+++ b/images/polar.svg
@@ -0,0 +1,230 @@
+<?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.0"
+ width="1200"
+ height="900"
+ id="svg2">
+ <defs
+ id="defs4" />
+ <path
+ d="M 899.66703,-69.03853 300.33297,969.03853"
+ id="path3403"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 600,-149.33408 0,1198.66818"
+ id="path69"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 300.33297,-69.03853 899.66703,969.03853"
+ id="path3397"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 176.2068,26.20681 847.5864,847.58638"
+ id="path3405"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 80.961482,150.33296 1119.0385,749.66704"
+ id="path3399"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 881.14902,435.72711 c 0,194.53909 -157.2228,352.24418 -351.16696,352.24418 -193.94417,0 -351.16697,-157.70509 -351.16697,-352.24418 0,-194.53909 157.2228,-352.244179 351.16696,-352.244179 193.94417,0 351.16697,157.705089 351.16697,352.244179 l 0,0 z"
+ transform="matrix(1.1334197,0,0,1.1299535,-0.6921025,-42.351375)"
+ id="path2537"
+ style="fill:none;stroke:#000000;stroke-width:0.88363791;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 881.14902,435.72711 c 0,194.53909 -157.2228,352.24418 -351.16696,352.24418 -193.94417,0 -351.16697,-157.70509 -351.16697,-352.24418 0,-194.53909 157.2228,-352.244179 351.16696,-352.244179 193.94417,0 351.16697,157.705089 351.16697,352.244179 l 0,0 z"
+ transform="matrix(0.8500647,0,0,0.8474652,149.48096,80.736436)"
+ id="path3311"
+ style="fill:none;stroke:#000000;stroke-width:1.17818391;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 881.14902,435.72711 c 0,194.53909 -157.2228,352.24418 -351.16696,352.24418 -193.94417,0 -351.16697,-157.70509 -351.16697,-352.24418 0,-194.53909 157.2228,-352.244179 351.16696,-352.244179 193.94417,0 351.16697,157.705089 351.16697,352.244179 l 0,0 z"
+ transform="matrix(0.5667098,0,0,0.5649768,299.65398,203.82429)"
+ id="path3313"
+ style="fill:none;stroke:#000000;stroke-width:1.76727581;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 881.14902,435.72711 c 0,194.53909 -157.2228,352.24418 -351.16696,352.24418 -193.94417,0 -351.16697,-157.70509 -351.16697,-352.24418 0,-194.53909 157.2228,-352.244179 351.16696,-352.244179 193.94417,0 351.16697,157.705089 351.16697,352.244179 l 0,0 z"
+ transform="matrix(0.2833549,0,0,0.2824884,449.82699,326.91215)"
+ id="path3315"
+ style="fill:none;stroke:#000000;stroke-width:3.53455162;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 881.14902,435.72711 c 0,194.53909 -157.2228,352.24418 -351.16696,352.24418 -193.94417,0 -351.16697,-157.70509 -351.16697,-352.24418 0,-194.53909 157.2228,-352.244179 351.16696,-352.244179 193.94417,0 351.16697,157.705089 351.16697,352.244179 l 0,0 z"
+ transform="matrix(1.4220377,0,0,1.417689,-153.65446,-167.72553)"
+ id="path3395"
+ style="fill:none;stroke:#000000;stroke-width:0.70429397;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 0,450 1200,0"
+ id="path49"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="M 80.96149,749.66704 1119.0385,150.33296"
+ id="path3401"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 1023.7932,26.20681 176.2068,873.79319"
+ id="path3407"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ x="602.82129"
+ y="175.8584"
+ id="text3003"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="602.82129"
+ y="175.8584"
+ id="tspan3005"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">0</tspan></text>
+ <text
+ x="869.37012"
+ y="459.64014"
+ id="text3003-4"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="869.37012"
+ y="459.64014"
+ id="tspan3005-7"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">90</tspan></text>
+ <text
+ x="602.23438"
+ y="733.8584"
+ id="text3003-5"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="602.23438"
+ y="733.8584"
+ id="tspan3005-6"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">180</tspan></text>
+ <text
+ x="319.26758"
+ y="459.64014"
+ id="text3003-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="319.26758"
+ y="459.64014"
+ id="tspan3005-74"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">270</tspan></text>
+ <text
+ x="725.93848"
+ y="209.30679"
+ id="text3003-52"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="725.93848"
+ y="209.30679"
+ id="tspan3005-5"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">30</tspan></text>
+ <text
+ x="836.63684"
+ y="321.30682"
+ id="text3003-7"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="836.63684"
+ y="321.30682"
+ id="tspan3005-4"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">60</tspan></text>
+ <text
+ x="796.0752"
+ y="262.57425"
+ id="text3003-0"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="796.0752"
+ y="262.57425"
+ id="tspan3005-78"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">45</tspan></text>
+ <text
+ x="349.23846"
+ y="323.30682"
+ id="text3003-52-6"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="349.23846"
+ y="323.30682"
+ id="tspan3005-5-8"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">300</tspan></text>
+ <text
+ x="461.2384"
+ y="209.30679"
+ id="text3003-7-8"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="461.2384"
+ y="209.30679"
+ id="tspan3005-4-4"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">330</tspan></text>
+ <text
+ x="387.23846"
+ y="262.64017"
+ id="text3003-0-3"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="387.23846"
+ y="262.64017"
+ id="tspan3005-78-1"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">315</tspan></text>
+ <text
+ x="359.25311"
+ y="595.97345"
+ id="text3003-52-9"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="359.25311"
+ y="595.97345"
+ id="tspan3005-5-2"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">240</tspan></text>
+ <text
+ x="459.25305"
+ y="699.30682"
+ id="text3003-7-0"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="459.25305"
+ y="699.30682"
+ id="tspan3005-4-6"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">210</tspan></text>
+ <text
+ x="401.25311"
+ y="654.6402"
+ id="text3003-0-8"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="401.25311"
+ y="654.6402"
+ id="tspan3005-78-9"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">225</tspan></text>
+ <text
+ x="722.58643"
+ y="699.30682"
+ id="text3003-52-66"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="722.58643"
+ y="699.30682"
+ id="tspan3005-5-4"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">150</tspan></text>
+ <text
+ x="823.25305"
+ y="595.97345"
+ id="text3003-7-9"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="823.25305"
+ y="595.97345"
+ id="tspan3005-4-5"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">120</tspan></text>
+ <text
+ x="780.58643"
+ y="654.6402"
+ id="text3003-0-0"
+ xml:space="preserve"
+ style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;fill:#0000c0;fill-opacity:1;stroke:none;font-family:DejaVu Sans"><tspan
+ x="780.58643"
+ y="654.6402"
+ id="tspan3005-78-4"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">135</tspan></text>
+</svg>
diff --git a/images/print.svg b/images/print.svg
new file mode 100644
index 0000000..9f48a36
--- /dev/null
+++ b/images/print.svg
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <metadata
+ id="metadata17">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs27" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 736.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 L 15,37.5 z"
+ id="path4"
+ style="fill:#80ffff;fill-opacity:1;fill-rule:evenodd;stroke:#00e0e0;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+</svg>
diff --git a/images/print1200.svg b/images/print1200.svg
new file mode 100644
index 0000000..e94402e
--- /dev/null
+++ b/images/print1200.svg
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="1167"
+ height="38"
+ id="svg2">
+ <metadata
+ id="metadata17">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs27" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 1136.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 z"
+ id="path4"
+ style="fill:#80ffff;fill-opacity:1;fill-rule:evenodd;stroke:#00e0e0;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(1053.75,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+</svg>
diff --git a/images/pythonoff.svg b/images/pythonoff.svg
new file mode 100644
index 0000000..ba30da0
--- /dev/null
+++ b/images/pythonoff.svg
@@ -0,0 +1,53 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="25"
+ height="25"
+ id="svg2">
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ff0000;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <g
+ transform="matrix(0.96656375,0,0,0.99278471,-22.651532,-17.827933)"
+ id="g2503">
+ <path
+ d="m 36.900421,42.139076 c 3.678049,0 9.139842,-1.579778 9.139842,-6.226149 0,-5.168511 -4.756035,-5.646118 -6.728349,-6.169371 -2.179926,-0.444765 -3.631982,-1.065431 -3.719203,-1.923232 -0.14926,-1.470673 0.711286,-2.043468 2.423778,-2.043468 0,0 4.073618,2.039572 7.534227,0.411922 0.975408,-0.458124 2.722297,-2.59845 2.722297,-4.07469 0,-1.476797 -5.639307,-3.183491 -7.35057,-3.183491 -1.712492,0 -3.168848,2.141439 -3.168848,2.141439 -3.423755,0 -6.848125,2.953594 -6.848125,5.907188 0,2.953037 3.275725,5.263141 7.190869,5.907187 1.819984,0.26107 3.342062,1.235767 2.996247,3.027072 -0.280706,1.454531 -1.839639,2.953594 -5.135633,2.953594 -2.611734,0 -8.169348,-0.121906 -9.05692,-2.270582 -0.570626,-1.383836 0.103192,-2.897372 0.959438,-2.897372 l -0.01658,-0.08906 c -0.872216,-0.08795 -3.407785,0.08906 -3.407785,3.042101 -6.14e-4,3.786345 5.61658,5.486916 12.46532,5.486916 z"
+ id="path2474"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 45.550102,26.189335 C 43.938959,26.029576 41.489383,25.076031 40.963596,23.39383"
+ id="path2476"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <circle
+ cx="35.805"
+ cy="10.96"
+ r="1.676"
+ transform="matrix(0.6142367,0,0,0.5566517,19.396517,15.137572)"
+ id="circle2478"
+ style="fill:#000000;fill-opacity:1;stroke-width:3.42034841;stroke-miterlimit:4;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/pythonon.svg b/images/pythonon.svg
new file mode 100644
index 0000000..00fb62c
--- /dev/null
+++ b/images/pythonon.svg
@@ -0,0 +1,52 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="25"
+ height="25"
+ id="svg2">
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ff0000;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <g
+ id="g3597">
+ <path
+ d="m 13.015077,24.007097 c 3.555069,0 8.83424,-1.568379 8.83424,-6.181225 0,-5.131219 -4.597011,-5.60538 -6.503378,-6.124857 -2.107037,-0.441556 -3.510542,-1.057744 -3.594847,-1.9093557 -0.144269,-1.4600617 0.687503,-2.0287238 2.342736,-2.0287238 0,0 3.937412,2.0248559 7.282311,0.4089499 0.942794,-0.4548185 2.631273,-2.5797015 2.631273,-4.04529 0,-1.4661414 -5.450749,-3.16052115 -7.104794,-3.16052115 -1.655233,0 -3.062894,2.12598785 -3.062894,2.12598785 -3.309277,0 -6.619149,2.932283 -6.619149,5.864566 0,2.9317299 3.166197,5.2251659 6.950433,5.8645649 1.759131,0.259186 3.230316,1.226851 2.896064,3.005231 -0.27132,1.444036 -1.778128,2.932283 -4.963917,2.932283 -2.5244071,0 -7.8961953,-0.121027 -8.7540902,-2.254199 -0.5515464,-1.373852 0.099742,-2.876467 0.927358,-2.876467 l -0.016026,-0.08842 c -0.843052,-0.08731 -3.29384111,0.08842 -3.29384111,3.020154 -5.9347e-4,3.759025 5.42878261,5.447326 12.04852631,5.447326 z"
+ id="path2474"
+ style="fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.95917296;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 21.375545,8.1724384 C 19.818273,8.0138321 17.450602,7.0671672 16.942395,5.3971037"
+ id="path2476"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.95917296;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <circle
+ cx="35.805"
+ cy="10.96"
+ r="1.676"
+ transform="matrix(0.59369893,0,0,0.5526353,-3.9035618,-2.799583)"
+ id="circle2478"
+ style="fill:#000000;fill-opacity:1;stroke-width:3.42034841;stroke-miterlimit:4;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/pythonsmall.svg b/images/pythonsmall.svg
new file mode 100644
index 0000000..d84a976
--- /dev/null
+++ b/images/pythonsmall.svg
@@ -0,0 +1,53 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="18.750023"
+ height="18.750151"
+ id="svg2">
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ff0000;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <g
+ transform="matrix(0.72492281,0,0,0.74458853,-16.978876,-13.360816)"
+ id="g2503">
+ <path
+ d="m 36.900421,42.139076 c 3.678049,0 9.139842,-1.579778 9.139842,-6.226149 0,-5.168511 -4.756035,-5.646118 -6.728349,-6.169371 -2.179926,-0.444765 -3.631982,-1.065431 -3.719203,-1.923232 -0.14926,-1.470673 0.711286,-2.043468 2.423778,-2.043468 0,0 4.073618,2.039572 7.534227,0.411922 0.975408,-0.458124 2.722297,-2.59845 2.722297,-4.07469 0,-1.476797 -5.639307,-3.183491 -7.35057,-3.183491 -1.712492,0 -3.168848,2.141439 -3.168848,2.141439 -3.423755,0 -6.848125,2.953594 -6.848125,5.907188 0,2.953037 3.275725,5.263141 7.190869,5.907187 1.819984,0.26107 3.342062,1.235767 2.996247,3.027072 -0.280706,1.454531 -1.839639,2.953594 -5.135633,2.953594 -2.611734,0 -8.169348,-0.121906 -9.05692,-2.270582 -0.570626,-1.383836 0.103192,-2.897372 0.959438,-2.897372 l -0.01658,-0.08906 c -0.872216,-0.08795 -3.407785,0.08906 -3.407785,3.042101 -6.14e-4,3.786345 5.61658,5.486916 12.46532,5.486916 z"
+ id="path2474"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 45.550102,26.189335 C 43.938959,26.029576 41.489383,25.076031 40.963596,23.39383"
+ id="path2476"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <circle
+ cx="35.805"
+ cy="10.96"
+ r="1.676"
+ transform="matrix(0.6142367,0,0,0.5566517,19.396517,15.137572)"
+ id="circle2478"
+ style="fill:#000000;fill-opacity:1;stroke-width:3.42034841;stroke-miterlimit:4;stroke-dasharray:none" />
+ </g>
+</svg>
diff --git a/images/status.svg b/images/status.svg
new file mode 100644
index 0000000..cb72835
--- /dev/null
+++ b/images/status.svg
@@ -0,0 +1,63 @@
+<?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.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <defs
+ id="defs27" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 C 3,30 0.5,26.5 0.5,23 L 0.5,15 C 0.5,11.75 3,6.5 5.5,4.5 C 8,2.5 11.5,0.5 15,0.5 L 751.5,0.5 C 754,0.5 758.5,1.5 762,4.5 C 765.5,7.25 766.5,12 766.5,15 L 766.5,23 C 766.5,26.5 764.5,30 762,32.5 C 759.5,35 755.5,37.5 751.5,37.5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="M 79.5,438.5 C 79.5,443 75.75,446.5 71,446.5 C 66.5,446.5 62.75,443 62.75,438.5 C 62.75,434 66.5,430.25 71,430.25 C 75.75,430.25 79.5,434 79.5,438.5 L 79.5,438.5 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <g
+ transform="translate(5.5,0)"
+ id="g14">
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-4,20)"
+ id="path16"
+ style="opacity:1;fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(1,17)"
+ id="path18"
+ style="opacity:1;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-1,18)"
+ id="path20"
+ style="opacity:1;fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <text
+ x="5.5"
+ id="text22"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="26.5"
+ y="29"
+ id="tspan24"
+ style="font-size:24px">!</tspan>
+ </text>
+</svg>
diff --git a/images/status1200.svg b/images/status1200.svg
new file mode 100644
index 0000000..6b64812
--- /dev/null
+++ b/images/status1200.svg
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="1167"
+ height="38"
+ id="svg2">
+ <metadata
+ id="metadata17">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs27" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 1136.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(1053.75,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <g
+ transform="translate(5.5,0)"
+ id="g14">
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-4,20)"
+ id="path16"
+ style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(1,17)"
+ id="path18"
+ style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-1,18)"
+ id="path20"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <text
+ x="5.5000076"
+ y="-8.4277344e-06"
+ id="text22"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="26.500023"
+ y="28.999992"
+ id="tspan24"
+ style="font-size:24px">!</tspan>
+ </text>
+</svg>
diff --git a/images/syntaxerror.svg b/images/syntaxerror.svg
new file mode 100644
index 0000000..0963986
--- /dev/null
+++ b/images/syntaxerror.svg
@@ -0,0 +1,120 @@
+<?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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <defs
+ id="defs41">
+ <linearGradient
+ x1="0"
+ y1="0"
+ x2="104"
+ y2="21"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.67,0,0,0.67,125.79816,9.965)" />
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#0000ff;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 C 3,30 0.5,26.5 0.5,23 L 0.5,15 C 0.5,11.75 3,6.5 5.5,4.5 C 8,2.5 11.5,0.5 15,0.5 L 751.5,0.5 C 754,0.5 758.5,1.5 762,4.5 C 765.5,7.25 766.5,12 766.5,15 L 766.5,23 C 766.5,26.5 764.5,30 762,32.5 C 759.5,35 755.5,37.5 751.5,37.5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="M 79.5,438.5 C 79.5,443 75.75,446.5 71,446.5 C 66.5,446.5 62.75,443 62.75,438.5 C 62.75,434 66.5,430.25 71,430.25 C 75.75,430.25 79.5,434 79.5,438.5 L 79.5,438.5 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <g
+ transform="translate(0,-0.25)"
+ id="g14">
+ <path
+ d="M 122.5,8.5 L 133.5,8.5 L 133.5,12.5 L 131,12.5 L 131,10.5 L 124,10.5"
+ id="path16"
+ style="fill:#a0a0a0;fill-opacity:1;stroke:#808080;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 122.5,27 L 133.5,27 L 133.5,23 L 131,23 L 131,25 L 124,25"
+ id="path18"
+ style="fill:#a0a0a0;fill-opacity:1;stroke:#808080;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 108,5.25 C 118.5,5.25 118.5,5.25 118.5,5.25 C 118.5,5.25 121.5,7 122.25,8 C 123,9 124.5,12 124.5,12 L 124.5,24.5 C 124.5,24.5 123,27 122.25,28 C 121.25,29 118.5,30.5 118.5,30.5 L 107.25,30.5 L 107.25,30 L 107.25,33.25 L 94,33.25 L 94,30.5 L 82.5,30.5 C 82.5,30.5 80,29 79,28 C 78,27 76.5,24.5 76.5,24.5 L 76.5,12 C 76.5,12 78,9 79,8 C 80,7 82.5,5.25 82.5,5.25 L 93.25,5.25 L 93.25,8.5 L 108,8.5 L 108,5.25 z"
+ id="path20"
+ style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:1.33340001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <path
+ d="M 126.46816,10.3 L 129.81816,10.3 L 129.81816,12.98 L 134.50816,12.98 L 134.50816,10.3 L 195.81316,10.3 L 195.81316,23.7 L 134.50816,23.7 L 134.50816,21.02 L 129.81816,21.02 L 129.81816,23.7 L 126.46816,23.7 L 126.46816,10.3 z"
+ id="path2498"
+ style="fill:url(#linearGradient3172);fill-opacity:1;stroke:#0000a0;stroke-width:1.29999995;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 205.82231,17.000001 C 205.82231,25.061518 185.73387,31.596667 160.95355,31.596667 C 136.17323,31.596667 116.08479,25.061518 116.08479,17.000001 C 116.08479,8.938484 136.17323,2.4033334 160.95355,2.4033334 C 185.73387,2.4033334 205.82231,8.938484 205.82231,17.000001 L 205.82231,17.000001 z"
+ id="path22"
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ff0000;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <text
+ x="38"
+ y="-0.09375"
+ id="text24"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="208"
+ y="27.90625"
+ id="tspan26"
+ style="font-size:24px">???</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g28">
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-4,20)"
+ id="path30"
+ style="opacity:1;fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(1,17)"
+ id="path32"
+ style="opacity:1;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ d="M 44,15 L 10.5,15 L 27.5,-14 L 44,15 z"
+ transform="translate(-1,18)"
+ id="path34"
+ style="opacity:1;fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <text
+ x="6"
+ id="text36"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan38"
+ style="font-size:24px">!</tspan>
+ </text>
+</svg>
diff --git a/images/videooff.svg b/images/videooff.svg
new file mode 100644
index 0000000..a20349c
--- /dev/null
+++ b/images/videooff.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="clipping-video">
+ <g display="inline">
+ <g>
+ <polygon fill="&fill_color;" points="48.788,43.944 48.788,23.002 36.849,11.058 5.962,11.058 5.962,43.944 " stroke="&stroke_color;" stroke-width="3.5"/>
+ <polyline fill="none" points="36.849,11.058 36.849,23.002 48.788,23.002 " stroke="&stroke_color;" stroke-width="3.5"/>
+ </g>
+ </g>
+ <path d="M27.504,24.842c-4.757,0-8.72,4.744-8.72,4.744s3.963,4.767,8.72,4.764 c4.757-0.004,8.722-4.77,8.722-4.77S32.262,24.839,27.504,24.842z M27.504,32.932c-1.842,0-3.335-1.494-3.335-3.336 c0-1.839,1.493-3.336,3.335-3.336c1.839,0,3.333,1.497,3.333,3.336C30.838,31.438,29.344,32.932,27.504,32.932z" display="inline" fill="&stroke_color;"/>
+ <circle cx="27.505" cy="29.597" display="inline" fill="&stroke_color;" r="1.514"/>
+ <circle cx="14.875" cy="29.597" display="inline" fill="&stroke_color;" r="1.514"/>
+ <circle cx="10.375" cy="29.597" display="inline" fill="&stroke_color;" r="1.514"/>
+ <circle cx="43.875" cy="29.597" display="inline" fill="&stroke_color;" r="1.514"/>
+ <circle cx="39.375" cy="29.597" display="inline" fill="&stroke_color;" r="1.514"/>
+</g></svg> \ No newline at end of file
diff --git a/images/videoon.svg b/images/videoon.svg
new file mode 100644
index 0000000..59d0f31
--- /dev/null
+++ b/images/videoon.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata29"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs27">
+
+
+
+
+
+
+
+</defs><g
+ id="g5"
+ style="display:inline">
+ <g
+ id="g7">
+ <polygon
+ points="5.962,11.058 5.962,43.944 48.788,43.944 48.788,23.002 36.849,11.058 "
+ id="polygon9"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5" />
+ <polyline
+ style="fill:none;stroke:#010101;stroke-width:3.5"
+ id="polyline11"
+ points="36.849,11.058 36.849,23.002 48.788,23.002 " />
+ </g>
+ </g><path
+ d="m 27.504,24.842 c -4.757,0 -8.72,4.744 -8.72,4.744 0,0 3.963,4.767 8.72,4.764 4.757,-0.004 8.722,-4.77 8.722,-4.77 0,0 -3.964,-4.741 -8.722,-4.738 z m 0,8.09 c -1.842,0 -3.335,-1.494 -3.335,-3.336 0,-1.839 1.493,-3.336 3.335,-3.336 1.839,0 3.333,1.497 3.333,3.336 0.001,1.842 -1.493,3.336 -3.333,3.336 z"
+ id="path13"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /><circle
+ cx="27.504999"
+ cy="29.597"
+ r="1.5140001"
+ id="circle15"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /><circle
+ cx="14.875"
+ cy="29.597"
+ r="1.5140001"
+ id="circle17"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /><circle
+ cx="10.375"
+ cy="29.597"
+ r="1.5140001"
+ id="circle19"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /><circle
+ cx="43.875"
+ cy="29.597"
+ r="1.5140001"
+ id="circle21"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /><circle
+ cx="39.375"
+ cy="29.597"
+ r="1.5140001"
+ id="circle23"
+ style="fill:#ff0101;fill-opacity:1;display:inline" /></svg> \ No newline at end of file
diff --git a/images/videosmall.svg b/images/videosmall.svg
new file mode 100644
index 0000000..d7758f5
--- /dev/null
+++ b/images/videosmall.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata29"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs27">
+
+
+
+
+
+
+
+</defs><g
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="g5"
+ style="stroke-width:3.5;stroke-miterlimit:4;stroke-dasharray:none;display:inline">
+ <g
+ id="g7"
+ style="stroke-width:3.5;stroke-miterlimit:4;stroke-dasharray:none">
+ <polygon
+ points="48.788,43.944 48.788,23.002 36.849,11.058 5.962,11.058 5.962,43.944 "
+ id="polygon9"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5;stroke-miterlimit:4;stroke-dasharray:none" />
+ <polyline
+ style="fill:none;stroke:#010101;stroke-width:3.5;stroke-miterlimit:4;stroke-dasharray:none"
+ id="polyline11"
+ points="36.849,11.058 36.849,23.002 48.788,23.002 " />
+ </g>
+ </g><path
+ d="m 27.4395,26.1715 c -2.3785,0 -4.36,2.372 -4.36,2.372 0,0 1.9815,2.3835 4.36,2.382 2.3785,-0.002 4.361,-2.385 4.361,-2.385 0,0 -1.982,-2.3705 -4.361,-2.369 z m 0,4.045 c -0.921,0 -1.6675,-0.747 -1.6675,-1.668 0,-0.9195 0.7465,-1.668 1.6675,-1.668 0.9195,0 1.6665,0.7485 1.6665,1.668 5e-4,0.921 -0.7465,1.668 -1.6665,1.668 z"
+ id="path13"
+ style="fill:#010101;display:inline" /><circle
+ cx="27.504999"
+ cy="29.597"
+ r="1.5140001"
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="circle15"
+ style="fill:#010101;display:inline" /><circle
+ cx="14.875"
+ cy="29.597"
+ r="1.5140001"
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="circle17"
+ style="fill:#010101;display:inline" /><circle
+ cx="10.375"
+ cy="29.597"
+ r="1.5140001"
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="circle19"
+ style="fill:#010101;display:inline" /><circle
+ cx="43.875"
+ cy="29.597"
+ r="1.5140001"
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="circle21"
+ style="fill:#010101;display:inline" /><circle
+ cx="39.375"
+ cy="29.597"
+ r="1.5140001"
+ transform="matrix(0.5,0,0,0.5,13.6875,13.7505)"
+ id="circle23"
+ style="fill:#010101;display:inline" /></svg> \ No newline at end of file
diff --git a/images/zerodivide.svg b/images/zerodivide.svg
new file mode 100644
index 0000000..f2aa30a
--- /dev/null
+++ b/images/zerodivide.svg
@@ -0,0 +1,74 @@
+<?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.0"
+ width="767"
+ height="38"
+ id="svg2">
+ <defs
+ id="defs32" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 736.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <text
+ x="-108"
+ id="text13"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="62"
+ y="28"
+ id="tspan15"
+ style="font-size:24px">/0</tspan>
+ </text>
+ <g
+ transform="translate(6,0)"
+ id="g17">
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-4,20)"
+ id="path19"
+ style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(1,17)"
+ id="path21"
+ style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-1,18)"
+ id="path23"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <text
+ x="6"
+ id="text25"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan27"
+ style="font-size:24px">!</tspan>
+ </text>
+</svg>
diff --git a/plugins/__init__.py b/plugins/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/__init__.py
diff --git a/plugins/plugin.py b/plugins/plugin.py
new file mode 100644
index 0000000..3065129
--- /dev/null
+++ b/plugins/plugin.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+#Copyright (c) 2011 Walter Bender
+#Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gobject
+
+
+class Plugin(gobject.GObject):
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ def setup(self):
+ """ Setup is called once, when the Turtle Window is created. """
+ pass
+
+ def start(self):
+ """ start is called when run button is pressed. """
+ pass
+
+ def stop(self):
+ """ stop is called when stop button is pressed. """
+ pass
+
+ def goto_background(self):
+ """ goto_background is called when the activity is sent to the
+ background. """
+ pass
+
+ def return_to_foreground(self):
+ """ return_to_foreground is called when the activity returns to
+ the foreground. """
+ pass
+
+ def quit(self):
+ """ cleanup is called when the activity is exiting. """
+ pass
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..fd6521a
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,3 @@
+[install]
+install_data=/usr/share/turtleart
+install_scripts=/usr/bin
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..e5026ee
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if len(sys.argv) > 1 and '--no-sugar' == sys.argv[1]:
+ # Remove the argument from the stack so we don't cause problems
+ # for distutils
+ sys.argv.pop(1)
+
+ import glob, os.path, string
+ from distutils.core import setup
+
+ DATA_FILES = [
+ ('icons', glob.glob('icons/*')),
+ ('images', glob.glob('images/*')),
+ ('/usr/share/applications', ['turtleart.desktop'])
+ ]
+
+ setup (name = 'Turtle Art',
+ description = "A LOGO-like tool for teaching programming",
+ author = "Walter Bender",
+ author_email = "walter.bender@gmail.com",
+ version = '0.9.4',
+ packages = ['TurtleArt'],
+ scripts = ['turtleart'],
+ data_files = DATA_FILES,
+ )
+else:
+ from sugar.activity import bundlebuilder
+
+ if __name__ == "__main__":
+ bundlebuilder.start()
diff --git a/util/RtfParser.py b/util/RtfParser.py
new file mode 100644
index 0000000..9a141a4
--- /dev/null
+++ b/util/RtfParser.py
@@ -0,0 +1,150 @@
+#Copyright (c) 2010, Loic Fejoz
+#Copyright (c) 2010, Walter Bender
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser 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 sys
+
+
+class RtfException(Exception):
+ pass
+
+plaintext = 1
+control = 2
+argument = 3
+backslash = 4
+escapedChar = 5
+
+
+class RtfParser(object):
+
+ def __init__(self, unicode=False):
+ self.state = plaintext
+ self.arg = ''
+ self.token = ''
+ self.unicode = unicode
+ self.par = False
+ self.output = ''
+
+ def getChar(self, code):
+ """ called when an escaped char is found """
+ return chr(code)
+
+ def getNonBreakingSpace(self):
+ return ' '
+
+ def pushState(self):
+ pass
+
+ def popState(self):
+ pass
+
+ def putChar(self):
+ pass
+
+ def doControl(self, token, arg):
+ pass
+
+ def feed(self, txt):
+ for c in txt:
+ self.feedChar(c)
+
+ def feedChar(self, char):
+ if self.state == plaintext: # this is just normal user content
+ if char == '\\':
+ self.state = backslash
+ elif char == '{':
+ self.pushState()
+ elif char == '}':
+ self.popState()
+ else:
+ self.putChar(char)
+ elif self.state == backslash: # a command or escape
+ if char == '\\' or char == '{' or char == '}':
+ self.putChar(char)
+ self.state = plaintext
+ else:
+ if char.isalpha() or char in ('*', '-', '|'):
+ self.state = control
+ self.token = char
+ elif char == "'":
+ self.state = escapedChar
+ self.escapedChar = ''
+ elif char in ['\\', '{', '}']:
+ self.putChar(char)
+ self.state = plaintext
+ elif char == "~": # non breaking space
+ self.putChar(self.getNonBreakingSpace())
+ self.state = plaintext
+ else:
+ raise RtfException(('unexpected %s after \\' % char))
+ elif self.state == escapedChar:
+ self.escapedChar = self.escapedChar + char
+ if len(self.escapedChar) == 2:
+ char = self.getChar(int(self.escapedChar, 16))
+ self.putChar(char)
+ self.state = plaintext
+ elif self.state == control: # collecting the command token
+ if char.isalpha():
+ self.token = self.token + char
+ elif char.isdigit() or char == '-':
+ self.state = argument
+ self.arg = char
+ else:
+ self.doControl(self.token, self.arg)
+ self.state = plaintext
+ if char == '\\':
+ self.state = backslash
+ elif char == '{':
+ self.pushState()
+ elif char == '}':
+ self.popState()
+ else:
+ if not char.isspace():
+ self.putChar(char)
+ elif self.state == argument: # collecting the optional argument
+ if char.isdigit():
+ self.arg = self.arg + char
+ else:
+ self.state = plaintext
+ self.doControl(self.token, self.arg)
+ if char == '\\':
+ self.state = backslash
+ elif char == '{':
+ self.pushState()
+ elif char == '}':
+ self.popState()
+ else:
+ if not char.isspace():
+ self.putChar(char)
+
+
+class RtfTextOnly(RtfParser):
+
+ def __init__(self):
+ RtfParser.__init__(self)
+ self.level = 0
+
+ def pushState(self):
+ self.level = self.level + 1
+
+ def popState(self):
+ self.level = self.level - 1
+
+ def putChar(self, ch):
+ if self.par:
+ self.output += ch
+
+ def doControl(self, token, arg):
+ if token[0:3] == 'par':
+ self.par = True
+ pass
diff --git a/util/__init__.py b/util/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/util/__init__.py
diff --git a/util/configfile.py b/util/configfile.py
new file mode 100644
index 0000000..adabb34
--- /dev/null
+++ b/util/configfile.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# 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 gobject
+
+
+class ConfigFile(gobject.GObject):
+ """Load/save a simple (key = value) config file"""
+
+ __gsignals__ = {
+ 'configuration-loaded': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ()),
+ 'configuration-saved': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ()),
+ }
+
+ def __init__(self, config_file_path, valid_keys={}):
+ gobject.GObject.__init__(self)
+
+ self._config_file_path = config_file_path
+ self._valid_keys = valid_keys
+ self._config_hash = {}
+ self._is_loaded = False
+
+ def set_valid_keys(self, valid_keys):
+ self._valid_keys = valid_keys
+
+ def is_loaded(self):
+ return self._is_loaded
+
+ def get(self, key, empty_if_not_loaded=False):
+ if not key in self._valid_keys:
+ raise RuntimeError("Unknown config value %s" % key)
+
+ if key in self._config_hash:
+ value = self._config_hash[key]
+ else:
+ if self._valid_keys[key]["type"] == "text":
+ value = ""
+ elif self._valid_keys[key]["type"] == "boolean":
+ value = False
+ elif self._valid_keys[key]["type"] == "integer":
+ value = 0
+
+ return value
+
+ def set(self, key, value):
+ if not key in self._valid_keys:
+ raise RuntimeError("Unknown config value %s" % key)
+
+ self._config_hash[key] = value
+
+ def load(self):
+ try:
+ config_file = open(self._config_file_path, 'r')
+ lines = config_file.readlines()
+ config_file.close()
+ for line in lines:
+ line = line.strip()
+ k, v = line.split('=')
+ k = k.strip(' ')
+ v = v.strip(' ')
+ if not k in self._valid_keys:
+ raise RuntimeError("Unknown config value %s" % k)
+ value_type = self._valid_keys[k]["type"]
+ if value_type == "text":
+ value = v
+ elif value_type == "boolean":
+ value = eval(v)
+ elif value_type == "integer":
+ value = int(v)
+ self._config_hash[k] = value
+ self._is_loaded = True
+ self.emit('configuration-loaded')
+ except Exception, e:
+ print e
+
+ return self._is_loaded
+
+ def save(self):
+ config_file = open(self._config_file_path, 'w')
+ for k in self._config_hash.keys():
+ v = self._config_hash[k]
+ l = "%s = %s\n" % (k, v)
+ config_file.write(l)
+ config_file.close()
+ self.emit('configuration-saved')
+
+ def dump_keys(self):
+ print "\n\nDumping keys\n\n"
+ for k in self._config_hash.keys():
+ v = self._config_hash[k]
+ l = "%s = %s\n" % (k, v)
+ print l
+
+
+def test_save_load(test_config_file):
+ keys = {}
+ keys["nick"] = {"type": "text"}
+ keys["account_id"] = {"type": "text"}
+ keys["server"] = {"type": "text"}
+ keys["port"] = {"type": "text"}
+ keys["password"] = {"type": "text"}
+ keys["register"] = {"type": "text"}
+
+ c = ConfigFile(test_config_file)
+ c.set_valid_keys(keys)
+ c.set("nick", "rgs")
+ c.set("account_id", "rgs@andromeda")
+ c.set("server", "andromeda")
+ c.set("port", 5223)
+ c.set("password", "97c74fa0dc3b39b8c87f119fa53cced2b7040786")
+ c.set("register", True)
+
+ c.save()
+
+ c = ConfigFile(test_config_file)
+ c.set_valid_keys(keys)
+ c.load()
+ c.dump_keys()
+
+
+def _configuration_saved_cb(config_file_obj):
+ print "_configuration_saved_cb called"
+ config_file_obj.dump_keys()
+
+
+def _configuration_loaded_cb(config_file_obj):
+ print "_configuration_loaded_cb called"
+ config_file_obj.dump_keys()
+
+
+def test_signals(test_config_file):
+ keys = {}
+ keys["nick"] = {"type": "text"}
+ keys["account_id"] = {"type": "text"}
+ keys["server"] = {"type": "text"}
+ keys["port"] = {"type": "text"}
+ keys["password"] = {"type": "text"}
+ keys["register"] = {"type": "text"}
+
+ c = ConfigFile(test_config_file)
+ c.connect('configuration-saved', _configuration_saved_cb)
+ c.set_valid_keys(keys)
+ c.set("nick", "rgs")
+ c.set("account_id", "rgs@andromeda")
+ c.set("server", "andromeda")
+ c.set("port", 5223)
+ c.set("password", "97c74fa0dc3b39b8c87f119fa53cced2b7040786")
+ c.set("register", True)
+
+ c.save()
+
+ c = ConfigFile(test_config_file)
+ c.connect('configuration-loaded', _configuration_loaded_cb)
+ c.set_valid_keys(keys)
+ c.load()
+
+
+if __name__ == "__main__":
+ test_save_load("/tmp/configfile.0001")
+ test_signals("/tmp/configfile.0002")
diff --git a/util/configwizard.py b/util/configwizard.py
new file mode 100644
index 0000000..6c66bd1
--- /dev/null
+++ b/util/configwizard.py
@@ -0,0 +1,218 @@
+#!/usr/bin/python
+# Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# 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 configfile import ConfigFile
+import gtk
+
+
+class ConfigWizard():
+ """Simple configuration wizard window."""
+
+ def __init__(self, config_file_path):
+ self._config_items = []
+ self._config_entries = {}
+ self._config_file_path = config_file_path
+ self._config_file_obj = None
+
+ """
+ [ {item_label, item_type, item_name, item_with_value} , ... ]
+ """
+ def set_config_items(self, items):
+ self._config_items = items
+ keys = {}
+ for i in self._config_items:
+ keys[i["item_name"]] = {"type": i["item_type"]}
+ self._valid_keys = keys
+
+ def set_config_file_obj(self, obj):
+ self._config_file_obj = obj
+
+ def get_config_file_obj(self, obj):
+ return self._config_file_obj
+
+ def show(self, read_from_disc=False):
+
+ if read_from_disc:
+ self._config_file_obj = ConfigFile(self._config_file_path)
+ self._config_file_obj.set_valid_keys(self._valid_keys)
+ self._config_file_obj.load()
+ else:
+ if self._config_file_obj is None:
+ raise RuntimeError("I need the run time obj")
+
+ self._config_popup = gtk.Window()
+ self._config_popup.set_default_size(200, 200)
+ self._config_popup.connect('delete_event', self._close_config_cb)
+ table = gtk.Table(12, 1, True)
+ self._config_popup.add(table)
+
+ row = 1
+ for i in self._config_items:
+ hbox = self._create_param(i)
+ table.attach(hbox, 0, 1, row, row + 1, xpadding=5, ypadding=2)
+ row = row + 1
+
+ hbox = gtk.HBox()
+ save_button = gtk.Button('Save')
+ save_button.set_size_request(50, 15)
+ save_button.connect('pressed', self._save_config_cb)
+ hbox.add(save_button)
+ cancel_button = gtk.Button('Cancel')
+ cancel_button.set_size_request(50, 15)
+ cancel_button.connect('pressed', self._close_config_cb)
+ hbox.add(cancel_button)
+ table.attach(hbox, 0, 1, row, row + 1, xpadding=5, ypadding=2)
+
+ self._config_popup.show_all()
+
+ def _save_config_cb(self, widget):
+ try:
+ self._do_save_config()
+ except Exception, e:
+ w = gtk.Window()
+ l = gtk.Label(e.message)
+ w.add(l)
+ w.show_all()
+ finally:
+ self._config_popup.hide()
+
+ def _do_save_config(self):
+ for i in self._config_items:
+ param_name = i["item_name"]
+ v = self._config_entries[param_name]
+ if v.__class__ is gtk.Entry:
+ value = v.get_text()
+ elif v.__class__ is gtk.CheckButton:
+ value = v.get_active()
+ else:
+ raise RuntimeError("Don't recognize the class %s" % type(v))
+ self._config_file_obj.set(param_name, value)
+
+ self._config_file_obj.save()
+
+ """
+ {item_label, item_type, item_name, item_with_value}
+ """
+ def _create_param(self, opts):
+ param_name = opts["item_name"]
+ with_value = opts["item_with_value"] if "item_with_value" in opts \
+ else True
+ hbox = gtk.HBox()
+ if opts["item_type"] == "text":
+ entry = gtk.Entry()
+ entry.set_size_request(150, 25)
+ if with_value:
+ value = self._config_file_obj.get(param_name, True)
+ entry.set_text(str(value))
+ elif opts["item_type"] == "boolean":
+ entry = gtk.CheckButton()
+ if with_value:
+ value = self._config_file_obj.get(param_name, True)
+ entry.set_active(value)
+ self._config_entries[param_name] = entry
+ label = gtk.Label(opts["item_label"] + ': ')
+ label.set_alignment(1.0, 0.5)
+ label.set_size_request(100, 25)
+ hbox.add(label)
+ hbox.add(entry)
+ return hbox
+
+ def _close_config_cb(self, widget, event=None):
+ self._config_popup.hide()
+
+
+def test_wizard_from_config_file_obj(test_config_file):
+ keys = {}
+ keys["nick"] = {"type": "text"}
+ keys["account_id"] = {"type": "text"}
+ keys["server"] = {"type": "text"}
+ keys["port"] = {"type": "text"}
+ keys["password"] = {"type": "text"}
+ keys["register"] = {"type": "text"}
+
+ c = ConfigFile(test_config_file)
+ c.set_valid_keys(keys)
+ c.set("nick", "rgs")
+ c.set("account_id", "rgs@andromeda")
+ c.set("server", "andromeda")
+ c.set("port", 5223)
+ c.set("password", "97c74fa0dc3b39b8c87f119fa53cced2b7040786")
+ c.set("register", True)
+
+ c.save()
+
+ c = ConfigFile(test_config_file)
+ c.set_valid_keys(keys)
+ c.load()
+
+ config_w = ConfigWizard(test_config_file)
+ config_items = [
+ {"item_label": "Nickname", "item_type": "text", "item_name": "nick"},
+ {"item_label": "Account ID", "item_type": "text",
+ "item_name": "account_id"},
+ {"item_label": "Server", "item_type": "text", "item_name": "server"},
+ {"item_label": "Port", "item_type": "text", "item_name": "port"},
+ {"item_label": "Password", "item_type": "text",
+ "item_name": "password"},
+ {"item_label": "Register", "item_type": "text",
+ "item_name": "register"}
+ ]
+ config_w.set_config_items(config_items)
+ config_w.set_config_file_obj(c)
+ config_w.show()
+
+
+def test_wizard_from_config_file_path(test_config_file):
+ keys = {}
+ keys["nick"] = {"type": "text"}
+ keys["account_id"] = {"type": "text"}
+ keys["server"] = {"type": "text"}
+ keys["port"] = {"type": "text"}
+ keys["password"] = {"type": "text"}
+ keys["register"] = {"type": "text"}
+
+ c = ConfigFile(test_config_file)
+ c.set_valid_keys(keys)
+ c.set("nick", "rgs")
+ c.set("account_id", "rgs@andromeda")
+ c.set("server", "andromeda")
+ c.set("port", 5223)
+ c.set("password", "97c74fa0dc3b39b8c87f119fa53cced2b7040786")
+ c.set("register", True)
+
+ c.save()
+
+ config_w = ConfigWizard(test_config_file)
+ config_items = [
+ {"item_label": "Nickname", "item_type": "text", "item_name": "nick"},
+ {"item_label": "Account ID", "item_type": "text",
+ "item_name": "account_id"},
+ {"item_label": "Server", "item_type": "text", "item_name": "server"},
+ {"item_label": "Port", "item_type": "text", "item_name": "port"},
+ {"item_label": "Password", "item_type": "text",
+ "item_name": "password"},
+ {"item_label": "Register", "item_type": "text",
+ "item_name": "register"}
+ ]
+ config_w.set_config_items(config_items)
+ config_w.show(True)
+
+
+if __name__ == "__main__":
+ #test_wizard_from_config_file_obj("/tmp/configwizard.test.0001")
+ test_wizard_from_config_file_path("/tmp/configwizard.test.0002")
+ gtk.main()
diff --git a/util/helpbutton.py b/util/helpbutton.py
new file mode 100644
index 0000000..47a36cd
--- /dev/null
+++ b/util/helpbutton.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2012, Gonzalo Odiard <godiard@gmail.com>
+# Copyright (C) 2012, Walter Bender <walter@sugarlabs.org>
+
+# 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.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+# HelpButton widget
+
+from gettext import gettext as _
+
+import gtk
+
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.icon import Icon
+from sugar.graphics import style
+
+from TurtleArt.tapalette import palette_names, help_windows
+
+import logging
+_logger = logging.getLogger('turtleart-activity')
+
+
+class HelpButton(gtk.ToolItem):
+
+ def __init__(self, activity):
+ self._activity = activity
+ self._current_palette = 'turtle'
+
+ gtk.ToolItem.__init__(self)
+
+ help_button = ToolButton('help-toolbar')
+ help_button.set_tooltip(_('Help'))
+ self.add(help_button)
+ help_button.show()
+
+ self._palette = help_button.get_palette()
+
+ help_button.connect('clicked', self.__help_button_clicked_cb)
+
+ def set_current_palette(self, name):
+ self._current_palette = name
+
+ def __help_button_clicked_cb(self, button):
+ if self._activity.palette_toolbar_button.is_expanded():
+ if not (self._current_palette in help_windows):
+ _logger.debug('name %s not found' % (self._current_palette))
+ return
+ self._palette.set_content(help_windows[self._current_palette])
+ help_windows[self._current_palette].show_all()
+ elif self._activity.edit_toolbar_button.is_expanded():
+ self._palette.set_content(help_windows['edit-toolbar'])
+ help_windows['edit-toolbar'].show_all()
+ elif self._activity.view_toolbar_button.is_expanded():
+ self._palette.set_content(help_windows['view-toolbar'])
+ help_windows['view-toolbar'].show_all()
+ elif self._activity.activity_toolbar_button.is_expanded():
+ self._palette.set_content(help_windows['activity-toolbar'])
+ help_windows['activity-toolbar'].show_all()
+ else:
+ self._palette.set_content(help_windows['main-toolbar'])
+ help_windows['main-toolbar'].show_all()
+
+ self._palette.popup(immediate=True, state=1)
+
+
+def add_section(help_box, section_text, icon=None):
+ ''' Add a section to the help palette. From helpbutton.py by
+ Gonzalo Odiard '''
+ max_text_width = int(gtk.gdk.screen_width() / 3) - 20
+ hbox = gtk.HBox()
+ label = gtk.Label()
+ label.set_use_markup(True)
+ label.set_markup('<b>%s</b>' % section_text)
+ label.set_line_wrap(True)
+ label.set_size_request(max_text_width, -1)
+ hbox.add(label)
+ if icon is not None:
+ _icon = Icon(icon_name=icon)
+ hbox.add(_icon)
+ label.set_size_request(max_text_width - 20, -1)
+ else:
+ label.set_size_request(max_text_width, -1)
+
+ hbox.show_all()
+ help_box.pack_start(hbox, False, False, padding=5)
+
+
+def add_paragraph(help_box, text, icon=None):
+ ''' Add an entry to the help palette. From helpbutton.py by
+ Gonzalo Odiard '''
+ max_text_width = int(gtk.gdk.screen_width() / 3) - 20
+ hbox = gtk.HBox()
+ label = gtk.Label(text)
+ label.set_justify(gtk.JUSTIFY_LEFT)
+ label.set_line_wrap(True)
+ hbox.add(label)
+ if icon is not None:
+ _icon = Icon(icon_name=icon)
+ hbox.add(_icon)
+ label.set_size_request(max_text_width - 20, -1)
+ else:
+ label.set_size_request(max_text_width, -1)
+
+ hbox.show_all()
+ help_box.pack_start(hbox, False, False, padding=5)
diff --git a/util/menubuilder.py b/util/menubuilder.py
new file mode 100644
index 0000000..4ee4550
--- /dev/null
+++ b/util/menubuilder.py
@@ -0,0 +1,39 @@
+#!/usr/bin/python
+# Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+
+
+class MenuBuilder():
+ @classmethod
+ def make_sub_menu(cls, menu, name):
+ """ add a new submenu to the toolbar """
+ sub_menu = gtk.MenuItem(name)
+ sub_menu.show()
+ sub_menu.set_submenu(menu)
+ return sub_menu
+
+ @classmethod
+ def make_menu_item(cls, menu, tooltip, callback, arg=None):
+ """ add a new item to the submenu """
+ menu_items = gtk.MenuItem(tooltip)
+ menu.append(menu_items)
+ if arg is None:
+ menu_items.connect('activate', callback)
+ else:
+ menu_items.connect('activate', callback, arg)
+ menu_items.show()