Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPootle daemon <pootle@pootle.sugarlabs.org>2011-06-13 14:23:40 (GMT)
committer Pootle daemon <pootle@pootle.sugarlabs.org>2011-06-13 14:23:40 (GMT)
commit86e2111461fab98d8c188d50ec8bd0c72c527f73 (patch)
tree6976b1ce163cb60586011951d748177e3663e937
parentd0bcef61b8ca9b8ef0066a891819dba950764d40 (diff)
parentc9b92a2a244430c677b2e27645a23c082c6c8188 (diff)
Merge branch 'master' of git.sugarlabs.org:turtleart/mainline
-rw-r--r--.gitignore4
-rw-r--r--COPYING4
-rw-r--r--NEWS112
-rw-r--r--TurtleArt/sprites.py5
-rw-r--r--TurtleArt/tabasics.py1270
-rw-r--r--TurtleArt/tablock.py255
-rw-r--r--TurtleArt/tacanvas.py362
-rw-r--r--TurtleArt/tacollaboration.py379
-rw-r--r--TurtleArt/taconstants.py830
-rw-r--r--TurtleArt/taexporthtml.py160
-rw-r--r--TurtleArt/taexportlogo.py560
-rw-r--r--TurtleArt/tagettext.py24
-rw-r--r--TurtleArt/tajail.py13
-rw-r--r--TurtleArt/talogo.py1414
-rw-r--r--TurtleArt/tapalette.py299
-rwxr-xr-xTurtleArt/tasprite_factory.py20
-rw-r--r--TurtleArt/taturtle.py54
-rw-r--r--TurtleArt/tautils.py154
-rw-r--r--TurtleArt/tawindow.py1140
-rw-r--r--TurtleArtActivity.py658
-rw-r--r--[-rwxr-xr-x]activity/activity.info2
-rwxr-xr-xcollaboration/neighborhood.py15
-rw-r--r--collaboration/presenceservice.py14
-rw-r--r--collaboration/telepathyclient.py8
-rw-r--r--extra/plugin.py10
-rw-r--r--gnome_plugins/__init__.py (copied from devices/__init__.py)0
-rw-r--r--gnome_plugins/collaboration_plugin.py (renamed from extra/collaborationplugin.py)235
-rw-r--r--gnome_plugins/plugin.py (copied from TurtleArt/tacamera.py)30
-rw-r--r--gnome_plugins/uploader_plugin.py (renamed from extra/upload.py)49
-rw-r--r--icons/blocksoff.svg45
-rw-r--r--icons/blockson.svg115
-rw-r--r--icons/colorsoff.svg82
-rw-r--r--icons/colorson.svg81
-rw-r--r--icons/extrasoff.svg41
-rw-r--r--icons/extrason.svg42
-rw-r--r--icons/view-metric.svg47
-rw-r--r--[-rwxr-xr-x]images/cameraoff.svg52
-rwxr-xr-ximages/camerasmall.svg37
-rw-r--r--images/metric.svg489
-rw-r--r--plugins/__init__.py (renamed from extra/__init__.py)0
-rw-r--r--plugins/audio_sensors/__init__.py (copied from devices/__init__.py)0
-rw-r--r--plugins/audio_sensors/audio_sensors.py321
-rw-r--r--plugins/audio_sensors/audiograb.py (renamed from TurtleArt/audiograb.py)73
-rw-r--r--plugins/audio_sensors/icons/sensoroff.svg (copied from icons/sensoroff.svg)0
-rw-r--r--plugins/audio_sensors/icons/sensoron.svg (copied from icons/sensoron.svg)0
-rw-r--r--plugins/audio_sensors/ringbuffer.py (renamed from TurtleArt/ringbuffer.py)0
-rw-r--r--plugins/camera_sensor/__init__.py (copied from devices/__init__.py)0
-rw-r--r--plugins/camera_sensor/camera_sensor.py222
-rw-r--r--plugins/camera_sensor/icons/sensoroff.svg (copied from icons/sensoroff.svg)0
-rw-r--r--plugins/camera_sensor/icons/sensoron.svg (copied from icons/sensoron.svg)0
-rw-r--r--plugins/camera_sensor/tacamera.py (renamed from TurtleArt/tacamera.py)11
-rw-r--r--plugins/camera_sensor/v4l2.py (renamed from TurtleArt/v4l2.py)0
-rw-r--r--plugins/plugin.py50
-rw-r--r--plugins/rfid/__init__.py (copied from devices/__init__.py)0
-rw-r--r--plugins/rfid/device.py (renamed from devices/device.py)0
-rw-r--r--plugins/rfid/icons/sensoroff.svg (copied from icons/sensoroff.svg)0
-rw-r--r--plugins/rfid/icons/sensoron.svg (copied from icons/sensoron.svg)0
-rw-r--r--plugins/rfid/rfid.py150
-rw-r--r--plugins/rfid/rfidrweusb.py (renamed from devices/rfidrweusb.py)0
-rw-r--r--plugins/rfid/rfidutils.py (renamed from TurtleArt/rfidutils.py)6
-rw-r--r--plugins/rfid/serial/__init__.py (renamed from devices/serial/__init__.py)0
-rw-r--r--plugins/rfid/serial/serialposix.py (renamed from devices/serial/serialposix.py)0
-rw-r--r--plugins/rfid/serial/serialutil.py (renamed from devices/serial/serialutil.py)0
-rw-r--r--plugins/rfid/tis2000.py (renamed from devices/tis2000.py)0
-rw-r--r--plugins/rfid/utils.py (renamed from devices/utils.py)0
-rw-r--r--plugins/turtle_blocks_extras/__init__.py (renamed from devices/__init__.py)0
-rw-r--r--plugins/turtle_blocks_extras/icons/extrasoff.svg75
-rw-r--r--plugins/turtle_blocks_extras/icons/extrason.svg158
-rw-r--r--plugins/turtle_blocks_extras/icons/mediaoff.svg (renamed from icons/portfoliooff.svg)0
-rw-r--r--plugins/turtle_blocks_extras/icons/mediaon.svg46
-rw-r--r--plugins/turtle_blocks_extras/icons/portfoliooff.svg72
-rw-r--r--plugins/turtle_blocks_extras/icons/portfolioon.svg (renamed from icons/portfolioon.svg)74
-rw-r--r--plugins/turtle_blocks_extras/icons/sensoroff.svg (renamed from icons/sensoroff.svg)0
-rw-r--r--plugins/turtle_blocks_extras/icons/sensoron.svg (renamed from icons/sensoron.svg)0
-rw-r--r--plugins/turtle_blocks_extras/turtle_blocks_extras.py1246
-rw-r--r--pysamples/COPYING21
-rw-r--r--pysamples/copy_from_heap.py37
-rw-r--r--pysamples/dotted_line.py136
-rw-r--r--pysamples/grecord.py227
-rw-r--r--pysamples/load_block.py78
-rw-r--r--pysamples/load_journal_entry_to_heap.py39
-rw-r--r--pysamples/paste_to_heap.py36
-rw-r--r--pysamples/push_mouse_event.py50
-rw-r--r--pysamples/push_time.py40
-rw-r--r--pysamples/save_heap_to_journal_entry.py44
-rw-r--r--pysamples/set_rgb.py61
-rw-r--r--pysamples/sinewave.py36
-rw-r--r--pysamples/speak.py57
-rw-r--r--pysamples/svg_end_group.py17
-rw-r--r--pysamples/svg_start_group.py17
-rw-r--r--pysamples/uturn.py35
-rw-r--r--samples/dice.ta99
-rw-r--r--samples/hoops.ta101
-rw-r--r--samples/images/basketball-court1-a.pngbin0 -> 14766 bytes
-rw-r--r--samples/images/basketball.pngbin0 -> 6143 bytes
-rw-r--r--samples/images/turtle-a.pngbin0 -> 107267 bytes
-rw-r--r--samples/images/turtle-b.pngbin0 -> 87746 bytes
-rw-r--r--samples/loudness-monitor.ta74
-rw-r--r--samples/love-speaks-volumes.ta109
-rw-r--r--samples/scratch.ta88
-rw-r--r--samples/spectrum_analyzer.ta53
-rw-r--r--samples/spiralaterals.ta55
-rw-r--r--samples/timer.ta82
-rw-r--r--samples/vumeter.ta68
-rwxr-xr-xturtleart.py263
-rw-r--r--util/RtfParser.py (renamed from TurtleArt/RtfParser.py)0
-rw-r--r--util/configfile.py71
-rw-r--r--util/configwizard.py100
-rw-r--r--util/menubuilder.py16
109 files changed, 8948 insertions, 4777 deletions
diff --git a/.gitignore b/.gitignore
index bea5755..737cf64 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
-TAGS
+*.pyc
+*~
+*.pyo
diff --git a/COPYING b/COPYING
index d2954ad..ff7d256 100644
--- a/COPYING
+++ b/COPYING
@@ -1,6 +1,6 @@
Copyright (c) 2007, Playful Invention Company
-Copyright (c) 2008-10, Walter Bender
-Copyright (c) 2009-10 Raúl Gutiérrez Segalés
+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
diff --git a/NEWS b/NEWS
index e317257..92b5534 100644
--- a/NEWS
+++ b/NEWS
@@ -1,20 +1,110 @@
-106
+109
+
+ENHANCEMENTS
+
+* CM coordinate grid overlay (XO hardware only)
BUG FIXES
+* Restored missing localization files
+* Only initialize camera if camera block is being used
+* Suppress unnecessary debug output from talogo
+* Fix hardware detection on some old builds
-* Exposed see as an external method (#2542)
-* Media type tests on file suffix use lower()
-* Uninitialized variables in Show block for numeric arguments (#2543)
-* Catch potential zero-divide in set_gray method (#2545)
-* Fixed regression with show Journal object thumbnails
+108
+
+ENHANCEMENTS
+
+* vspace, hspace run stack on mouse click (#2790)
+* added clear all button to Trash Palette
+* added language support to speak.py sample code
+* added 'scratch' and 'hoops' examples
+
+BUG FIXES
+
+* Save to SVG was not working for setxy
+* Save to SVG was not working for fill
+* Save to SVG was broken for arc
+
+107
ENHANCEMENTS
-* Code clean up by RGS
-* Added 'brightness' block for reading camera luminance level
-* Added 'camera sees' block for reading average camera RGB value
+* Refactoring of block and palette generation to use the plugin
+ classes for all blocks. (The goal is to make it easier for end-user
+ modifications.)
+* Added the ability to add new blocks on the fly (See uturn.py example)
+* Added basic-style-3arg block (used by NXT plugin)
+* Moved plugin libraries to ./plugins
+* Shared turtles are updated after mouse drag (#2687)
+* Shared turtles cannot be repositioned remotely (#2687)
+* Shared turtles have labels
+* Turtles are synchronized when joining share (#2687)
+* Comments on usage included in the Python sample code (#2709)
+* Using standard Sugar icons for colors and camera
+* Refactoring of export Logo code to enable plugins to define new functions
+* Value blocks updated properly on first appearance
+* Reduced the header size in Python sample code for easier reading (#2748)
+* Extend clearscreen to entire canvas (#2745)
+* Cleaned up some issues with .es translations (Guzman Trinidad)
+* More robust handling of broken projects and unknown block types
+* Added hidden blocks when plugin devices are unavailable
+* Added new Python example for recording audio
+
+BUG FIXES
+
+* Fixed regression with help messages when running Sugar 0.84 (#2676)
+* Fixed problem with selected turtle reverting back to default turtle (#2687)
+* Fixed problem with camera block graphic in trash (#2678)
+* Fixed regression with camera_plugin after refactoring (#2689)
+* Fixed problem with saving/loading extra turtles when nick changes (#2441)
+* Fixed problem with displaying large FILO heap (#2751)
+* Fixed a problem with tags causing malformed HTML export
+* Fixed problem with first sample when using sensors
+* Fixed problem with method-name conflict with gtk.widget
+
+PYTHON CODE CHANGE
+
+* Userdefined code gets TurtleWindow instance rather than LogoCode
+ instance as first argument. This change was made in order to better
+ accommodate and better parallel the new plugin mechanism. Most
+ likely, you had been referencing tw, the TurtleWindow instance from
+ lc, the LogoCode instance, e.g., lc.tw. Now you can reference tw
+ directly, e.g., tw. To reference lc, use tw.lc. Under most
+ circumstances, Turtle Art will detect when myblock is looking for lc
+ rather than tw and it will pass the correct argument.
+
+106
+
+ENHANCEMENTS
+
+* Added sharing to draw_text, fill_polygon, draw_pixbuf (#2461)
+* Added sharing (EXPERIMENTAL) between Gnome and Sugar versions (with Raul
+ Gutierrez Segales)
+* Added 'time' block for measuring elapsed time (in seconds)
+* Added 'brightness' block for reading average camera luminance value
+ (with help from Tony Forster and Guzman Trinidad)
* Added camera media block for grabbing images from the camera
+* New speak.py sample code for doing text to speech (Tony Forster)
+* New load_block.py sample code for programatically creating TA projects
* New psuedo-color.ta example (Tony Forster)
+* New love-speaks-volumes.ta example -- use mic input to scale shapes
+* New spiralaterals.ta example inspired by Spiralaterals activity
+* Added a Media Palette for all media-related blocks and reorganized palettes
+ (#2633) for more clarity (Claudia Urrea and Bill Kerr)
+* Added offset for second argument in Boolean compare blocks
+* More complete translations in Spanish (es) and Italian (it)
+* Added plugin support for non-standard devices (camera, sensors, RFID)
+
+BUG FIXES
+
+* Exposed see as an external method (#2542)
+* Media type tests on file suffix use lower()
+* Added support for localization to GNOME version (Raul Gutierrez Segales)
+* Work around for situations where gst is not available
+* Fixed problem with displaying Journal preview images in portfolio
+* Restore overlay grids on clear
+* Fixed problem with help-string wrap width (#2633)
+* Added missing dependency to RFID plugin (Emiliano Pastorino)
105
@@ -110,7 +200,7 @@ ENHANCEMENTS
* rgs fixed resume problem in GNOME version (#2293)
* checking alpha value to block 'hit' detection (fixes problem with
selecting the wrong block when blocks wrap around each other).
-* trap and display math errors in python block (#2313)
+* trap and display math errors in Python block (#2313)
96
@@ -386,7 +476,7 @@ ENHANCEMENTS
* first pass at hover help support (thanks Raul)
* put samples button, keep button on project toolbar
-* fixed journal icons associated with html, python, logo
+* fixed journal icons associated with HTML, Python, Logo
* improved compatibility with old Sugar builds
* images centered under turtle
* text vertically centered under turtle
diff --git a/TurtleArt/sprites.py b/TurtleArt/sprites.py
index fdcd72f..c633397 100644
--- a/TurtleArt/sprites.py
+++ b/TurtleArt/sprites.py
@@ -426,12 +426,15 @@ class Sprite:
b = ord(array[offset + 2])
return(r, g, b, a)
except IndexError:
+ """
print "Index Error: %d %d (%d, %d) (w: %d, h: %d) (%dx%d)"\
% (len(array), offset, x, y,
self.images[i].get_width(),
self.images[i].get_height(),
self.rect.width, self.rect.height)
- return(-1, -1, -1, -1)
+ """
+ pass
+ return(-1, -1, -1, -1)
else:
w, h = self.images[i].get_size()
if x < 0 or x > (w - 1) or y < 0 or y > (h - 1):
diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py
new file mode 100644
index 0000000..bfed2d7
--- /dev/null
+++ b/TurtleArt/tabasics.py
@@ -0,0 +1,1270 @@
+# -*- 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
+
+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.tw.lc.prim_clear
+ 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',
+ 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',
+ 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',
+ 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',
+ 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 """
+
+ 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',
+ 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',
+ 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',
+ 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',
+ 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', CONSTANTS['red'])
+ self._make_constant(palette, 'orange', CONSTANTS['orange'])
+ self._make_constant(palette, 'yellow', CONSTANTS['yellow'])
+ self._make_constant(palette, 'green', CONSTANTS['green'])
+ self._make_constant(palette, 'cyan', CONSTANTS['cyan'])
+ self._make_constant(palette, 'blue', CONSTANTS['blue'])
+ self._make_constant(palette, 'purple', CONSTANTS['purple'])
+ self._make_constant(palette, 'white', WHITE)
+ self._make_constant(palette, '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'),
+ 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',
+ 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',
+ 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',
+ style='compare-porch-style',
+ label='>',
+ special_name=_('greater than'),
+ 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))
+
+ primitive_dictionary['less'] = self._prim_less
+ palette.add_block('less2',
+ style='compare-porch-style',
+ label='<',
+ special_name=_('less than'),
+ 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))
+
+ primitive_dictionary['equal'] = self._prim_equal
+ palette.add_block('equal2',
+ style='compare-style',
+ label='=',
+ 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',
+ 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',
+ 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',
+ 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',
+ 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',
+ 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',
+ 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',
+ 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',
+ 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'),
+ 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'),
+ 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',
+ 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',
+ style='number-style-1strarg',
+ label=_('box'),
+ prim_name='box',
+ 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',
+ style='basic-style-2arg',
+ label=[_('store in'), _('box'), _('value')],
+ 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',
+ style='basic-style-head-1arg',
+ label=_('action'),
+ prim_name='nop3',
+ default=_('stack'),
+ 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',
+ style='basic-style-1arg',
+ label=_('action'),
+ 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='basic-style-tail',
+ label=_('empty trash'),
+ help_string=_('permanently deletes items in trash'))
+
+ palette.add_block('restoreall',
+ style='basic-style-head',
+ label=_('restore all'),
+ help_string=_('restore all blocks from trash'))
+
+ palette.add_block('trashall',
+ style='basic-style-tail',
+ label=_('clear all'),
+ help_string=_('move all blocks to trash'))
+
+ # Block primitives
+
+ 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 """
+ 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 """
+ try:
+ return float(x) == float(y)
+ except TypeError:
+ 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 ValueError:
+ 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)
+ 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)
+ 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)
+ 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))
+ 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, constant):
+ """ Factory for constant blocks """
+ palette.add_block(block_name, style='box-style',
+ label=_(block_name), 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
index 05b95ae..bcca2cd 100644
--- a/TurtleArt/tablock.py
+++ b/TurtleArt/tablock.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-#Copyright (c) 2010 Walter Bender
+#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
@@ -20,31 +20,21 @@
#THE SOFTWARE.
import gtk
-
from gettext import gettext as _
-from taconstants import EXPANDABLE, EXPANDABLE_BLOCKS, EXPANDABLE_ARGS, \
- PRIMITIVES, OLD_NAMES, BLOCK_SCALE, BLOCK_NAMES, CONTENT_BLOCKS, \
- PALETTES, COLORS, BASIC_STYLE_HEAD, BASIC_STYLE_HEAD_1ARG, \
- BASIC_STYLE_TAIL, BASIC_STYLE, BASIC_STYLE_EXTENDED, BASIC_STYLE_1ARG, \
- BASIC_STYLE_VAR_ARG, BULLET_STYLE, BASIC_STYLE_2ARG, 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, 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, \
- STANDARD_STROKE_WIDTH, BOX_COLORS, GRADIENT_COLOR, \
- BASIC_STYLE_EXTENDED_VERTICAL, CONSTANTS, INVISIBLE
+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
-import logging
-_logger = logging.getLogger('turtleart-activity')
+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='.'):
@@ -130,7 +120,9 @@ class Block:
""" A class for the individual blocks """
def __init__(self, block_list, sprite_list, name, x, y, type='block',
- values=[], scale=BLOCK_SCALE, colors=['#FF0000', '#A00000']):
+ values=[], scale=BLOCK_SCALE[0],
+ colors=['#A0A0A0', '#808080']):
+
self.block_list = block_list
self.spr = None
self.shapes = [None, None]
@@ -150,6 +142,51 @@ class Block:
self._font_size = [6.0, 4.5]
self._image = None
+ 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-while': self._make_flow_style_while,
+ '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]
@@ -162,22 +199,38 @@ class Block:
# If there is already a block with the same name, reuse it
copy_block = None
- if self.name not in EXPANDABLE and \
- self.name not in EXPANDABLE_BLOCKS and \
- self.name not in EXPANDABLE_ARGS and \
- self.name not in BOX_STYLE and \
- self.name not in ['sandwichtop', 'sandwichtop_no_label']:
+ 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 PRIMITIVES:
- self.primitive = PRIMITIVES[self.name]
+ if name in block_primitives:
+ self.primitive = block_primitives[self.name]
self.block_list.append_to_list(self)
+ 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:
@@ -310,7 +363,6 @@ class Block:
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])
@@ -335,10 +387,7 @@ class Block:
self.shapes[1] = copy_block.shapes[1]
self.docks = copy_block.docks[:]
else:
- if (self.name in EXPANDABLE or \
- self.name in EXPANDABLE_BLOCKS or \
- self.name in EXPANDABLE_ARGS) and \
- self.type == 'block':
+ if self.expandable() and self.type == 'block':
self.svg.set_show(True)
self._make_block(self.svg)
@@ -358,11 +407,11 @@ class Block:
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] + ' = ' + \
+ 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]):
+ 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:
@@ -374,18 +423,24 @@ class Block:
self.svg.margins[2], self.svg.margins[3])
def _set_label_attributes(self):
- if self.name in CONTENT_BLOCKS:
+ 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])
+ if self.name in block_names:
+ n = len(block_names[self.name])
else:
- _logger.debug('WARNING: unknown block name %s' % (self.name))
+ debug_output('WARNING: unknown block name %s' % (self.name))
n = 0
for i in range(n):
- if i == 1: # top
+ 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 i == 1: # top
self.spr.set_label_attributes(int(self._font_size[1] + 0.5),
True, 'right', 'top', i)
elif i == 2: # bottom
@@ -405,92 +460,26 @@ class Block:
self._bottom = 0
self.svg.set_stroke_width(STANDARD_STROKE_WIDTH)
self.svg.clear_docks()
- if self.name in BASIC_STYLE:
- self._make_basic_style(svg)
- elif self.name in BASIC_STYLE_HEAD:
- self._make_basic_style_head(svg)
- elif self.name in BASIC_STYLE_EXTENDED:
- self._make_basic_style(svg, 16, 16)
- elif self.name in BASIC_STYLE_EXTENDED_VERTICAL:
- self._make_basic_style(svg, 0, 4)
- elif self.name in BASIC_STYLE_HEAD_1ARG:
- self._make_basic_style_head_1arg(svg)
- elif self.name in BASIC_STYLE_TAIL:
- self._make_basic_style_tail(svg)
- elif self.name in BASIC_STYLE_1ARG:
- self._make_basic_style_1arg(svg)
- elif self.name in BASIC_STYLE_2ARG:
- self._make_basic_style_2arg(svg)
- elif self.name in BASIC_STYLE_VAR_ARG:
- self._make_basic_style_var_arg(svg)
- elif self.name in BULLET_STYLE:
- self._make_bullet_style(svg)
- elif self.name in BOX_STYLE:
- self._make_box_style(svg)
- elif self.name in BOX_STYLE_MEDIA:
- self._make_media_style(svg)
- elif self.name in NUMBER_STYLE:
- self._make_number_style(svg)
- elif self.name in NUMBER_STYLE_BLOCK:
- self._make_number_style_block(svg)
- elif self.name in NUMBER_STYLE_VAR_ARG:
- self._make_number_style_var_arg(svg)
- elif self.name in NUMBER_STYLE_1ARG:
- self._make_number_style_1arg(svg)
- elif self.name in NUMBER_STYLE_1STRARG:
- self._make_number_style_1strarg(svg)
- elif self.name in NUMBER_STYLE_PORCH:
- self._make_number_style_porch(svg)
- elif self.name in COMPARE_STYLE:
- self._make_compare_style(svg)
- elif self.name in BOOLEAN_STYLE:
- self._make_boolean_style(svg)
- elif self.name in NOT_STYLE:
- self._make_not_style(svg)
- elif self.name in FLOW_STYLE:
- self._make_flow_style(svg)
- elif self.name in FLOW_STYLE_TAIL:
- self._make_flow_style_tail(svg)
- elif self.name in FLOW_STYLE_1ARG:
- self._make_flow_style_1arg(svg)
- elif self.name in FLOW_STYLE_BOOLEAN:
- self._make_flow_style_boolean(svg)
- elif self.name in FLOW_STYLE_WHILE:
- self._make_flow_style_while(svg)
- elif self.name in FLOW_STYLE_ELSE:
- self._make_flow_style_else(svg)
- elif self.name in COLLAPSIBLE_TOP:
- self._make_collapsible_style_top(svg, arm=True, label=True)
- elif self.name in COLLAPSIBLE_TOP_NO_ARM:
- self._make_collapsible_style_top(svg, arm=False, label=True)
- elif self.name in COLLAPSIBLE_TOP_NO_LABEL:
- self._make_collapsible_style_top(svg, arm=True, label=False)
- elif self.name in COLLAPSIBLE_TOP_NO_ARM_NO_LABEL:
- self._make_collapsible_style_top(svg, arm=False, label=False)
- elif self.name in COLLAPSIBLE_BOTTOM:
- self._make_collapsible_style_bottom(svg)
- elif self.name in INVISIBLE:
- self._make_invisible_style(svg)
- elif self.name in PORTFOLIO_STYLE_2x2:
- self._make_portfolio_style_2x2(svg)
- elif self.name in PORTFOLIO_STYLE_2x1:
- self._make_portfolio_style_2x1(svg)
- elif self.name in PORTFOLIO_STYLE_1x1:
- self._make_portfolio_style_1x1(svg)
- elif self.name in PORTFOLIO_STYLE_1x2:
- self._make_portfolio_style_1x2(svg)
- else:
- self._make_basic_style(svg)
- _logger.debug("WARNING: I don't know how to create a %s block" % \
- (self.name))
+ 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.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(PALETTES)):
- if self.name in PALETTES[p]:
- self.colors = COLORS[p]
+ 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):
@@ -500,6 +489,13 @@ class Block:
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._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)
@@ -553,6 +549,21 @@ class Block:
['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]
@@ -706,6 +717,10 @@ class Block:
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)
diff --git a/TurtleArt/tacanvas.py b/TurtleArt/tacanvas.py
index d4395a2..bf866fb 100644
--- a/TurtleArt/tacanvas.py
+++ b/TurtleArt/tacanvas.py
@@ -1,5 +1,5 @@
#Copyright (c) 2007-8, Playful Invention Company.
-#Copyright (c) 2008-10, Walter Bender
+#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
@@ -24,15 +24,15 @@ import gtk
from math import sin, cos, pi
import pango
import cairo
+import base64
+from gettext import gettext as _
from sprites import Sprite
from tasprite_factory import SVG
-from tautils import image_to_base64, data_to_string, round_int
+from tautils import image_to_base64, get_path, data_to_string, round_int, \
+ debug_output
from taconstants import CANVAS_LAYER, BLACK, WHITE
-import logging
-_logger = logging.getLogger('turtleart-activity')
-
def wrap100(n):
""" A variant on mod... 101 -> 99; 199 -> 1 """
@@ -43,6 +43,23 @@ def wrap100(n):
return n
+def calc_poly_bounds(poly_points):
+ """ Calculate the minx, miny, width, height of polygon """
+ minx = poly_points[0][0]
+ miny = poly_points[0][1]
+ maxx, maxy = minx, miny
+ for p in poly_points:
+ if p[0] < minx:
+ minx = p[0]
+ elif p[0] > maxx:
+ maxx = p[0]
+ if p[1] < miny:
+ miny = p[1]
+ elif p[1] > maxy:
+ maxy = p[1]
+ return(minx, miny, maxx - minx, maxy - miny)
+
+
def calc_shade(c, s, invert=False):
""" Convert a color to the current shade (lightness/darkness). """
# Assumes 16 bit input values
@@ -119,8 +136,8 @@ class TurtleGraphics:
self.fgcolor = self.cm.alloc_color('red')
self.bgrgb = [255, 248, 222]
self.bgcolor = self.cm.alloc_color('#fff8de')
- self.textsize = 48 # depreciated
- self.textcolor = self.cm.alloc_color('blue')
+ self.textsize = 48 # deprecated
+ self.textcolor = self.cm.alloc_color('red') # deprecated
self.tw.active_turtle.show()
self.shade = 0
self.pendown = False
@@ -128,7 +145,6 @@ class TurtleGraphics:
self.ycor = 0
self.heading = 0
self.pensize = 5
- self.tcolor = 0
self.color = 0
self.gray = 100
self.fill = False
@@ -142,57 +158,72 @@ class TurtleGraphics:
""" Start accumulating points of a polygon to fill. """
self.fill = True
self.poly_points = []
+ if self.tw.saving_svg:
+ self.tw.svg_string += '<g>'
def stop_fill(self):
""" Fill the polygon. """
self.fill = False
if len(self.poly_points) == 0:
return
- minx = self.poly_points[0][0]
- miny = self.poly_points[0][1]
- maxx = minx
- maxy = miny
- for p in self.poly_points:
- if p[0] < minx:
- minx = p[0]
- elif p[0] > maxx:
- maxx = p[0]
- if p[1] < miny:
- miny = p[1]
- elif p[1] > maxy:
- maxy = p[1]
- w = maxx - minx
- h = maxy - miny
- self.canvas.images[0].draw_polygon(self.gc, True, self.poly_points)
+ 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 = []
+ if self.tw.saving_svg:
+ self.tw.svg_string += '</g>'
+
+ def fill_polygon(self, poly_points):
+ minx, miny, w, h = calc_poly_bounds(poly_points)
+ self.canvas.images[0].draw_polygon(self.gc, True, poly_points)
self.invalt(minx - self.pensize * self.tw.coord_scale / 2 - 3,
miny - self.pensize * self.tw.coord_scale / 2 - 3,
w + self.pensize * self.tw.coord_scale + 6,
h + self.pensize * self.tw.coord_scale + 6)
- self.poly_points = []
+ if self.tw.saving_svg and self.pendown:
+ self.svg.set_fill_color("#%02x%02x%02x" % (self.fgrgb[0],
+ self.fgrgb[1],
+ self.fgrgb[2]))
+ self.tw.svg_string += self.svg.new_path(poly_points[0][0],
+ poly_points[0][1])
+ for p in range(len(poly_points)):
+ if p > 0:
+ self.tw.svg_string += self.svg.line_to(poly_points[p][0],
+ poly_points[p][1])
+ self.tw.svg_string += "\"\n"
+ self.tw.svg_string += self.svg.style()
+ self.svg.set_fill_color('none')
def clearscreen(self, share=True):
"""Clear the canvas and reset most graphics attributes to defaults."""
- rect = gtk.gdk.Rectangle(0, 0, self.width, self.height)
+ rect = gtk.gdk.Rectangle(0, 0, self.width * 2, self.height * 2)
self.gc.set_foreground(self.bgcolor)
self.canvas.images[0].draw_rectangle(self.gc, True, *rect)
self.invalt(0, 0, self.width, self.height)
self.setpensize(5, share)
self.setgray(100, share)
self.setcolor(0, share)
- self.settextcolor(70)
self.setshade(50, share)
for turtle_key in iter(self.tw.turtles.dict):
- 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()
+ # 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)
self.tw.svg_string = ''
self.svg.reset_min_max()
@@ -208,44 +239,46 @@ class TurtleGraphics:
self.xcor += nn * sin(self.heading * DEGTOR)
self.ycor += nn * cos(self.heading * DEGTOR)
except TypeError, ValueError:
- _logger.debug("bad value sent to %s" % (__name__))
+ 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.saving_svg and self.pendown:
- self.tw.svg_string += self.svg.new_path(oldx,
- self.height / 2 - oldy)
- self.tw.svg_string += self.svg.line_to(self.xcor,
- self.height / 2 - self.ycor)
- self.tw.svg_string += "\"\n"
- self.tw.svg_string += self.svg.style()
- event = "f|%s" % (data_to_string([self._get_my_nick(), int(n)]))
- self._send_event(event, share)
+
+ if self.tw.sharing() and share:
+ event = "f|%s" % (data_to_string([self._get_my_nick(), int(n)]))
+ self.tw.send_event(event)
def seth(self, n, share=True):
""" Set the turtle heading. """
try:
self.heading = n
except TypeError, ValueError:
- _logger.debug("bad value sent to %s" % (__name__))
+ debug_output("bad value sent to %s" % (__name__),
+ self.tw.running_sugar)
return
self.heading %= 360
self.turn_turtle()
- event = "r|%s" % (data_to_string([self._get_my_nick(), round_int(self.heading)]))
- self._send_event(event, share)
+ 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:
- _logger.debug("bad value sent to %s" % (__name__))
+ debug_output("bad value sent to %s" % (__name__),
+ self.tw.running_sugar)
return
self.heading %= 360
self.turn_turtle()
- event = "r|%s" % (data_to_string([self._get_my_nick(), round_int(self.heading)]))
- self._send_event(event, share)
+ 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 """
@@ -257,11 +290,14 @@ class TurtleGraphics:
else:
self.rarc(a, rr)
except TypeError, ValueError:
- _logger.debug("bad value sent to %s" % (__name__))
+ debug_output("bad value sent to %s" % (__name__),
+ self.tw.running_sugar)
return
self.move_turtle()
- event = "a|%s" % (data_to_string([self._get_my_nick(), [round_int(a), round_int(r)]]))
- self._send_event(event, share)
+ 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 """
@@ -274,8 +310,7 @@ class TurtleGraphics:
oldx, oldy = self.xcor, self.ycor
cx = self.xcor + r * cos(self.heading * DEGTOR)
cy = self.ycor - r * sin(self.heading * DEGTOR)
- x = self.width / 2 + int(cx - r)
- y = self.height / 2 - int(cy + r)
+ x, y = self.turtle_to_screen_coordinates(int(cx - r), int(cy + r))
w = int(2 * r)
h = w
if self.pendown:
@@ -289,10 +324,10 @@ class TurtleGraphics:
self.xcor = cx - r * cos(self.heading * DEGTOR)
self.ycor = cy + r * sin(self.heading * DEGTOR)
if self.tw.saving_svg and self.pendown:
- self.tw.svg_string += self.svg.new_path(oldx,
- self.height / 2 - oldy)
- self.tw.svg_string += self.svg.arc_to(self.xcor,
- self.height / 2 - self.ycor, r, a, 0, s)
+ x, y = self.turtle_to_screen_coordinates(oldx, oldy)
+ self.tw.svg_string += self.svg.new_path(x, y)
+ x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor)
+ self.tw.svg_string += self.svg.arc_to(x, y, r, a, 0, s)
self.tw.svg_string += "\"\n"
self.tw.svg_string += self.svg.style()
@@ -307,8 +342,7 @@ class TurtleGraphics:
oldx, oldy = self.xcor, self.ycor
cx = self.xcor - r * cos(self.heading * DEGTOR)
cy = self.ycor + r * sin(self.heading * DEGTOR)
- x = self.width / 2 + int(cx - r)
- y = self.height / 2 - int(cy + r)
+ x, y = self.turtle_to_screen_coordinates(int(cx - r), int(cy + r))
w = int(2 * r)
h = w
if self.pendown:
@@ -323,11 +357,10 @@ class TurtleGraphics:
self.xcor = cx + r * cos(self.heading * DEGTOR)
self.ycor = cy - r * sin(self.heading * DEGTOR)
if self.tw.saving_svg and self.pendown:
- self.tw.svg_string += self.svg.new_path(oldx,
- self.height / 2 - oldy)
- self.tw.svg_string += self.svg.arc_to(self.xcor,
- self.height / 2 - self.ycor,
- r, a, 0, s)
+ x, y = self.turtle_to_screen_coordinates(oldx, oldy)
+ self.tw.svg_string += self.svg.new_path(x, y)
+ x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor)
+ self.tw.svg_string += self.svg.arc_to(x, y, r, a, 0, s)
self.tw.svg_string += "\"\n"
self.tw.svg_string += self.svg.style()
@@ -339,16 +372,19 @@ class TurtleGraphics:
try:
self.xcor, self.ycor = x, y
except TypeError, ValueError:
- _logger.debug("bad value sent to %s" % (__name__))
+ debug_output("bad value sent to %s" % (__name__),
+ self.tw.running_sugar)
return
if self.pendown and pendown:
self.gc.set_foreground(self.fgcolor)
self.draw_line(oldx, oldy, self.xcor, self.ycor)
-
self.move_turtle()
- event = "x|%s" % (data_to_string([self._get_my_nick(), [round_int(x), round_int(y)]]))
- self._send_event(event, share)
+
+ 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 """
@@ -357,81 +393,85 @@ class TurtleGraphics:
ps = 0
self.pensize = ps
except TypeError, ValueError:
- _logger.debug("bad value sent to %s" % (__name__))
+ debug_output("bad value sent to %s" % (__name__),
+ self.tw.running_sugar)
return
self.tw.active_turtle.set_pen_size(ps)
self.gc.set_line_attributes(int(self.pensize * self.tw.coord_scale),
- gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_MITER)
+ gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_MITER)
self.svg.set_stroke_width(self.pensize)
- event = "w|%s" % (data_to_string([self._get_my_nick(), round_int(ps)]))
- self._send_event(event, share)
+ 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
- self.tcolor = c
except TypeError, ValueError:
- _logger.debug("bad value sent to %s" % (__name__))
+ debug_output("bad value sent to %s" % (__name__),
+ self.tw.running_sugar)
return
self.tw.active_turtle.set_color(c)
self.set_fgcolor()
- self.set_textcolor()
- event = "c|%s" % (data_to_string([self._get_my_nick(), round_int(c)]))
- self._send_event(event, share)
+ 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:
- _logger.debug("bad value sent to %s" % (__name__))
+ 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.set_textcolor()
self.tw.active_turtle.set_gray(self.gray)
- event = "g|%s" % (data_to_string([self._get_my_nick(), round_int(self.gray)]))
- self._send_event(event, share)
+ 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 settextcolor(self, c):
+ def settextcolor(self, c): # deprecated
""" Set the text color """
- try:
- self.tcolor = c
- except TypeError, ValueError:
- _logger.debug("bad value sent to %s" % (__name__))
- return
- self.set_textcolor()
+ return
- def settextsize(self, c): # depreciated
+ def settextsize(self, c): # deprecated
""" Set the text size """
try:
self.tw.textsize = c
except TypeError, ValueError:
- _logger.debug("bad value sent to %s" % (__name__))
+ 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:
- _logger.debug("bad value sent to %s" % (__name__))
+ debug_output("bad value sent to %s" % (__name__),
+ self.tw.running_sugar)
return
self.tw.active_turtle.set_shade(s)
self.set_fgcolor()
- self.set_textcolor()
- event = "s|%s" % (data_to_string([self._get_my_nick(), round_int(s)]))
- self._send_event(event, share)
+ 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)
- rect = gtk.gdk.Rectangle(0, 0, self.width, self.height)
+ rect = gtk.gdk.Rectangle(0, 0, self.width * 2, self.height * 2)
self.gc.set_foreground(self.fgcolor)
self.bgrgb = self.fgrgb[:]
self.canvas.images[0].draw_rectangle(self.gc, True, *rect)
@@ -473,16 +513,17 @@ class TurtleGraphics:
def set_textcolor(self):
""" Set the text color to foreground color. """
- self.tw.textcolor = self.fgcolor
+ return
def setpen(self, bool, share=True):
""" Lower or raise the pen """
self.pendown = bool
self.tw.active_turtle.set_pen_state(bool)
- event = "p|%s" % (data_to_string([self._get_my_nick(), bool]))
- self._send_event(event, share)
+ if self.tw.sharing() and share:
+ event = "p|%s" % (data_to_string([self._get_my_nick(), bool]))
+ self.tw.send_event(event)
- def draw_pixbuf(self, pixbuf, a, b, x, y, w, h, path):
+ def draw_pixbuf(self, pixbuf, a, b, x, y, w, h, path, share=True):
""" Draw a pixbuf """
w *= self.tw.coord_scale
h *= self.tw.coord_scale
@@ -492,20 +533,40 @@ class TurtleGraphics:
if self.tw.running_sugar:
# In Sugar, we need to embed the images inside the SVG
self.tw.svg_string += self.svg.image(x - self.width / 2,
- y, w, h, path, image_to_base64(pixbuf, self.tw.activity))
+ y, w, h, path, image_to_base64(pixbuf,
+ get_path(self.tw.activity, 'instance')))
else:
+ # Outside of Sugar, we save a path
self.tw.svg_string += self.svg.image(x - self.width / 2,
y, w, h, path)
-
- def draw_text(self, label, x, y, size, w):
+ if self.tw.sharing() and share:
+ if self.tw.running_sugar:
+ tmp_path = get_path(self.tw.activity, 'instance')
+ else:
+ tmp_path = '/tmp'
+ data = image_to_base64(pixbuf, 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)
+
+ def draw_text(self, label, x, y, size, w, share=True):
""" Draw text """
w *= self.tw.coord_scale
- self.gc.set_foreground(self.tw.textcolor)
+ self.gc.set_foreground(self.fgcolor)
fd = pango.FontDescription('Sans')
try:
fd.set_size(int(size * self.tw.coord_scale) * pango.SCALE)
except TypeError, ValueError:
- _logger.debug("bad value sent to %s" % (__name__))
+ debug_output("bad value sent to %s" % (__name__),
+ self.tw.running_sugar)
return
if self.tw.interactive_mode:
if type(label) == str or type(label) == unicode:
@@ -529,32 +590,57 @@ class TurtleGraphics:
context.move_to(x, y + h)
context.show_text(message)
- if self.tw.saving_svg and self.pendown:
- self.tw.svg_string += self.svg.text(x - self.width / 2,
+ if self.tw.saving_svg: # and self.pendown:
+ self.tw.svg_string += self.svg.text(x, # - self.width / 2,
y + size, size, w, label)
+ 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.width / 2 + int(x1), self.height / 2 - int(y1)
- x2, y2 = self.width / 2 + int(x2), self.height / 2 - int(y2)
+ x1, y1 = self.turtle_to_screen_coordinates(x1, y1)
+ x2, y2 = self.turtle_to_screen_coordinates(x2, y2)
if x1 < x2:
- minx, maxx = x1, x2
+ minx, maxx = int(x1), int(x2)
else:
- minx, maxx = x2, x1
+ minx, maxx = int(x2), int(x1)
if y1 < y2:
- miny, maxy = y1, y2
+ miny, maxy = int(y1), int(y2)
else:
- miny, maxy = y2, y1
+ miny, maxy = int(y2), int(y1)
w, h = maxx - minx, maxy - miny
- self.canvas.images[0].draw_line(self.gc, x1, y1, x2, y2)
+ self.canvas.images[0].draw_line(self.gc, int(x1), int(y1), int(x2),
+ int(y2))
if self.fill and self.poly_points == []:
- self.poly_points.append((x1, y1))
+ self.poly_points.append((int(x1), int(y1)))
if self.fill:
- self.poly_points.append((x2, y2))
- self.invalt(minx - self.pensize * self.tw.coord_scale / 2 - 3,
- miny - self.pensize * self.tw.coord_scale / 2 - 3,
+ self.poly_points.append((int(x2), int(y2)))
+ self.invalt(minx - int(self.pensize * self.tw.coord_scale / 2) - 3,
+ miny - int(self.pensize * self.tw.coord_scale / 2) - 3,
w + self.pensize * self.tw.coord_scale + 6,
h + self.pensize * self.tw.coord_scale + 6)
+ if self.tw.saving_svg and self.pendown:
+ self.tw.svg_string += self.svg.new_path(x1, y1)
+ self.tw.svg_string += self.svg.line_to(x2, y2)
+ self.tw.svg_string += "\"\n"
+ self.tw.svg_string += self.svg.style()
def turn_turtle(self):
""" Change the orientation of the turtle """
@@ -562,8 +648,7 @@ class TurtleGraphics:
def move_turtle(self):
""" Move the turtle """
- x, y = self.width / 2 + int(self.xcor), \
- self.height / 2 - int(self.ycor)
+ x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor)
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)))
@@ -611,9 +696,9 @@ class TurtleGraphics:
def get_pixel(self):
""" Read the pixel at x, y """
if self.tw.interactive_mode:
- return self.canvas.get_pixel(
- (self.width / 2 + int(self.xcor),
- self.height / 2 - int(self.ycor)), 0, self.tw.color_mode)
+ x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor)
+ return self.canvas.get_pixel((int(x), int(y)), 0,
+ self.tw.color_mode)
else:
return(-1, -1, -1, -1)
@@ -625,13 +710,16 @@ class TurtleGraphics:
self.seth(0, False)
self.setxy(0, 0, False, pendown=False)
self.tw.active_turtle.set_pen_state(True)
- self.tw.active_turtle = self.tw.turtles.get_turtle(k, False)
+ 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.width / 2 + tx + \
- self.tw.active_turtle.spr.rect.width / 2
- self.ycor = self.height / 2 - ty - \
- self.tw.active_turtle.spr.rect.height / 2
+ self.xcor, self.ycor = self.screen_to_turtle_coordinates(tx, ty)
+ 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)
@@ -651,11 +739,3 @@ class TurtleGraphics:
def _get_my_nick(self):
return self.tw.nick
-
- def _send_event(self, entry, share):
- if not share:
- return
-
- if self.tw.sharing():
- print "Sending: %s" % entry
- self.tw.send_event(entry)
diff --git a/TurtleArt/tacollaboration.py b/TurtleArt/tacollaboration.py
index 52164e0..e1534a4 100644
--- a/TurtleArt/tacollaboration.py
+++ b/TurtleArt/tacollaboration.py
@@ -1,9 +1,34 @@
+#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 logging
import telepathy
-from TurtleArt.tautils import data_to_string, data_from_string
+
+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
@@ -17,43 +42,63 @@ except:
SERVICE = 'org.laptop.TurtleArtActivity'
IFACE = SERVICE
PATH = '/org/laptop/TurtleArtActivity'
-_logger = logging.getLogger('turtleart-activity')
+
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)
+ 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._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:
- _logger.error("Failed to share or join activity ... \
- _shared_activity is null in _shared_cb()")
+ 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.turtle_dictionary = self._get_dictionary()
-
- _logger.debug('I am sharing...')
+ 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
@@ -62,7 +107,8 @@ class Collaboration():
self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
'NewTube', self._new_tube_cb)
- _logger.debug('This is my activity: making a tube...')
+ debug_output('This is my activity: making a tube...',
+ self._tw.running_sugar)
id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
SERVICE, {})
@@ -70,8 +116,9 @@ class Collaboration():
def _joined_cb(self, activity):
self._shared_activity = self._activity._shared_activity
if self._shared_activity is None:
- _logger.error("Failed to share or join activity ... \
- _shared_activity is null in _shared_cb()")
+ 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)
@@ -85,7 +132,8 @@ class Collaboration():
self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
'NewTube', self._new_tube_cb)
- _logger.debug('I am joining an activity: waiting for a tube...')
+ 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)
@@ -98,13 +146,13 @@ class Collaboration():
self._new_tube_cb(*tube_info)
def _list_tubes_error_cb(self, e):
- _logger.error('ListTubes() failed: %s', 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. """
- _logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
- 'params=%r state=%d', id, initiator, type, service,
- params, state)
+ 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:
@@ -120,106 +168,35 @@ class Collaboration():
self.event_received_cb)
# Now that we have the tube, we can ask for the turtle dictionary.
- if self.waiting_for_turtles:
- _logger.debug("Sending a request for the turtle dictionary")
- # we need to send our own nick and colors
+ 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])
- _logger.debug(event)
+ 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, text):
+ 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(text) == 0:
+ if len(event_message) == 0:
return
- # Save active Turtle
+
+ # Save active Turtle
save_active_turtle = self._tw.active_turtle
- e = text.split("|", 2)
- text = e[1]
- if e[0] == 't': # request for turtle dictionary
- if text > 0:
- [nick, colors] = data_from_string(text)
- if nick != self._tw.nick:
- # There may not be a turtle dictionary.
- if hasattr(self, "turtle_dictionary"):
- self.turtle_dictionary[nick] = colors
- else:
- self.turtle_dictionary = {nick: colors}
- # Add new turtle for the joiner.
- self._tw.canvas.set_turtle(nick, colors)
- # Sharer should send turtle dictionary.
- if self.initiating:
- text = data_to_string(self.turtle_dictionary)
- self.send_event("T|" + text)
- elif e[0] == 'T': # Receiving the turtle dictionary.
- if self.waiting_for_turtles:
- if len(text) > 0:
- self.turtle_dictionary = data_from_string(text)
- for nick in self.turtle_dictionary:
- if nick != self._tw.nick:
- colors = self.turtle_dictionary[nick]
- # add new turtle for the joiner
- self._tw.canvas.set_turtle(nick, colors)
- self.waiting_for_turtles = False
- elif e[0] == 'f': # move a turtle forward
- if len(text) > 0:
- [nick, x] = data_from_string(text)
- if nick != self._tw.nick:
- self._tw.canvas.set_turtle(nick)
- self._tw.canvas.forward(x, False)
- elif e[0] == 'a': # move a turtle in an arc
- if len(text) > 0:
- [nick, [a, r]] = data_from_string(text)
- if nick != self._tw.nick:
- self._tw.canvas.set_turtle(nick)
- self._tw.canvas.arc(a, r, False)
- elif e[0] == 'r': # rotate turtle
- if len(text) > 0:
- [nick, h] = data_from_string(text)
- if nick != self._tw.nick:
- self._tw.canvas.set_turtle(nick)
- self._tw.canvas.seth(h, False)
- elif e[0] == 'x': # set turtle xy position
- if len(text) > 0:
- [nick, [x, y]] = data_from_string(text)
- if nick != self._tw.nick:
- self._tw.canvas.set_turtle(nick)
- self._tw.canvas.setxy(x, y, False)
- elif e[0] == 'c': # set turtle pen color
- if len(text) > 0:
- [nick, x] = data_from_string(text)
- if nick != self._tw.nick:
- self._tw.canvas.set_turtle(nick)
- self._tw.canvas.setcolor(x, False)
- elif e[0] == 'g': # set turtle pen gray level
- if len(text) > 0:
- [nick, x] = data_from_string(text)
- if nick != self._tw.nick:
- self._tw.canvas.set_turtle(nick)
- self._tw.canvas.setgray(x, False)
- elif e[0] == 's': # set turtle pen shade
- if len(text) > 0:
- [nick, x] = data_from_string(text)
- if nick != self._tw.nick:
- self._tw.canvas.set_turtle(nick)
- self._tw.canvas.setshade(x, False)
- elif e[0] == 'w': # set turtle pen width
- if len(text) > 0:
- [nick, x] = data_from_string(text)
- if nick != self._tw.nick:
- self._tw.canvas.set_turtle(nick)
- self._tw.canvas.setpensize(x, False)
- elif e[0] == 'p': # set turtle pen state
- if len(text) > 0:
- [nick, x] = data_from_string(text)
- if nick != self._tw.nick:
- self._tw.canvas.set_turtle(nick)
- self._tw.canvas.setpen(x, False)
+
+ 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))
@@ -229,19 +206,193 @@ class Collaboration():
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. '''
+ 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):
- d = { self._get_nick(): self._get_colors()}
- return d
+ return {self._get_nick(): self._get_colors()}
def _get_nick(self):
return self._tw.nick
def _get_colors(self):
- if profile:
- colors = profile.get_color().to_string()
+ 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()
- return colors
+ if colors is None:
+ colors = '%s,%s' % (DEFAULT_TURTLE_COLORS[0],
+ DEFAULT_TURTLE_COLORS[1])
+ return colors.split(',')
+
class ChatTube(ExportedGObject):
@@ -249,7 +400,7 @@ class ChatTube(ExportedGObject):
"""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.is_initiator = is_initiator # Are we sharing or joining activity?
self.stack_received_cb = stack_received_cb
self.stack = ''
diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py
index 77ebefb..30920d6 100644
--- a/TurtleArt/taconstants.py
+++ b/TurtleArt/taconstants.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-#Copyright (c) 2010, Walter Bender
+#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
@@ -19,79 +19,6 @@
#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, it is generally a
-matter of modifying some tables below and then adding the primitive to
-talogo.py. For example, if we want to add a new turtle command,
-'uturn', we'd make the following changes:
-
-(1) We'd add 'uturn' to the PALETTES list of lists:
-
-PALETTES = [['forward', 'back', 'clean', 'left', 'right', 'uturn', 'show',
- 'seth', 'setxy', 'heading', 'xcor', 'ycor', 'setscale',
- 'arc', 'scale'],
- ['penup','pendown', 'setpensize', 'fillscreen', 'pensize',...
-
-(2) Then we'd add it to one of the block-style definitions. Since it takes
-no arguments, we'd add it here:
-
-BASIC_STYLE = ['clean', 'penup', 'pendown', 'stack1', 'stack2', 'vspace',
- 'hideblocks', 'showblocks', 'clearheap', 'printheap', 'kbinput', 'uturn']
-
-(3) Then we give it a name (Note the syntax _('string to be
-translated') used by the language-internationalization system; also
-note that the name is an array, as some blocks contain multiple
-strings.):
-
-BLOCK_NAMES = {
-...
- 'uturn':[_('u-turn')],
-...
- }
-
-(4) and a help-menu entry:
-
-HELP_STRINGS = {
-...
- 'uturn':_('change the heading of the turtle 180 degrees'),
-...
- }
-
-(5) Next, we need to define it as a primitive for the Logo command
-parser (generally just the same name):
-
-PRIMITIVES = {
-...
- 'uturn':'uturn',
-...
- }
-
-(6) Since there are no default arguments, we don't need to do anything
-else here. But we do need to define the actual function in talogo.py
-
-DEFPRIM = {
-...
- '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 Turtle Palette.
-
-Adding a new palette is simply a matter of: (1) adding an additional
-entry to PALETTE_NAMES; (2) new list of blocks to PALETTES; and (3) an
-additional entry in COLORS. However you will have to: (4) create icons
-for the palette-selector buttons. These are kept in the icons
-subdirectory. You need two icons: yourpalettenameoff.svg and
-yourpalettenameon.svg, where yourpalettename is the same string as the
-entry you added to the PALETTE_NAMES list. Note that the icons should
-be the same size (55x55) as the others. This is the default icon size
-for Sugar toolbars.
-
-"""
-
from gettext import gettext as _
#
@@ -108,52 +35,7 @@ TAB_LAYER = 710
STATUS_LAYER = 900
TOP_LAYER = 1000
-#
-# Block-palette categories
-#
-
-PALETTE_NAMES = ['turtle', 'pen', 'colors', 'numbers', 'flow', 'blocks',
- 'extras', 'sensor', 'portfolio', 'trash']
-
-PALETTES = [['clean', 'forward', 'back', 'show', 'left', 'right',
- 'seth', 'setxy2', 'heading', 'xcor', 'ycor', 'setscale',
- 'arc', 'scale', 'leftpos', 'toppos', 'rightpos',
- 'bottompos'],
- ['penup', 'pendown', 'setpensize', 'fillscreen', 'pensize',
- 'setcolor', 'setshade', 'setgray', 'color', 'shade',
- 'gray', 'startfill', 'stopfill'],
- ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple',
- 'white', 'black'],
- ['plus2', 'minus2', 'product2',
- 'division2', 'identity2', 'remainder2', 'sqrt', 'random',
- 'number', 'greater2', 'less2', 'equal2', 'not', 'and2', 'or2'],
- ['wait', 'forever', 'repeat', 'if', 'ifelse', 'while', 'until',
- 'hspace', 'vspace', 'stopstack'],
- ['start', 'hat1', 'stack1', 'hat', 'hat2', 'stack2', 'stack',
- 'storeinbox1', 'storeinbox2', 'string', 'box1', 'box2', 'box',
- 'storein'],
- ['push', 'printheap', 'clearheap', 'pop', 'comment', 'print',
- 'myfunc1arg', 'userdefined',
- 'cartesian', 'width', 'height', 'polar', 'addturtle', 'reskin',
- 'sandwichtop_no_label', 'sandwichbottom'],
- ['kbinput', 'keyboard', 'readpixel', 'see',
- 'sound', 'volume', 'pitch'],
- ['journal', 'audio', 'video', 'description', 'hideblocks',
- 'showblocks', 'fullscreen', 'savepix', 'savesvg', 'mediawait',
- 'picturelist', 'picture1x1a', 'picture1x1', 'picture2x2',
- 'picture2x1', 'picture1x2'],
- ['empty', 'restoreall']]
-
-#
-# Block-style attributes
-#
-
-COLORS = [["#00FF00", "#00A000"], ["#00FFFF", "#00A0A0"],
- ["#00FFFF", "#00A0A0"], ["#FF00FF", "#A000A0"],
- ["#FFC000", "#A08000"], ["#FFFF00", "#A0A000"],
- ["#FF0000", "#A00000"], ["#FF0000", "#A00000"],
- ["#0000FF", "#0000A0"], ["#FFFF00", "#A0A000"]]
-
+# Special-case some block colors
BOX_COLORS = {'red': ["#FF0000", "#A00000"],
'orange': ["#FFD000", "#AA8000"],
'yellow': ["#FFFF00", "#A0A000"],
@@ -173,9 +55,10 @@ SELECTOR_WIDTH = 55
ICON_SIZE = 55
GRADIENT_COLOR = "#FFFFFF"
STANDARD_STROKE_WIDTH = 1.0
-BLOCK_SCALE = 2.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']
HORIZONTAL_PALETTE = 0
VERTICAL_PALETTE = 1
BLACK = -9999
@@ -190,78 +73,23 @@ DEFAULT_SCALE = 33
XO1 = 'xo1'
XO15 = 'xo1.5'
UNKNOWN = 'unknown'
-SENSOR_AC_NO_BIAS = 'external'
-SENSOR_AC_BIAS = 'sound'
-SENSOR_DC_NO_BIAS = 'voltage'
-SENSOR_DC_BIAS = 'resistance'
-#
-# Block-style definitions
-#
-BASIC_STYLE_HEAD = ['start', 'hat1', 'hat2', 'restore', 'restoreall']
-BASIC_STYLE_HEAD_1ARG = ['hat']
-BASIC_STYLE_TAIL = ['stopstack', 'empty']
-BASIC_STYLE = []
-BASIC_STYLE_EXTENDED_VERTICAL = ['clean', 'penup', 'pendown', 'stack1',
- 'stack2', 'hideblocks', 'showblocks', 'clearheap', 'printheap', 'kbinput',
- 'fullscreen', 'cartesian', 'polar', 'startfill', 'mediawait',
- 'stopfill', 'readpixel', 'readcamera', 'vspace']
-INVISIBLE = ['sandwichcollapsed']
-BASIC_STYLE_EXTENDED = ['picturelist', 'picture1x1', 'picture2x2',
- 'picture2x1', 'picture1x2', 'picture1x1a']
-BASIC_STYLE_1ARG = ['forward', 'back', 'left', 'right', 'seth', 'show', 'image',
- 'setscale', 'setpensize', 'setcolor', 'setshade', 'print', 'showaligned',
- 'settextsize', 'settextcolor', 'print', 'wait', 'storeinbox1', 'savepix',
- 'storeinbox2', 'wait', 'stack', 'push', 'nop', 'addturtle', 'comment',
- 'savesvg', 'setgray', 'skin', 'reskin']
-BASIC_STYLE_VAR_ARG = ['userdefined', 'userdefined2args', 'userdefined3args']
-BULLET_STYLE = ['templatelist', 'list']
-BASIC_STYLE_2ARG = ['arc', 'setxy', 'setxy2', 'fillscreen', 'storein', 'write']
-BOX_STYLE = ['number', 'xcor', 'ycor', 'heading', 'pensize', 'color', 'shade',
- 'textcolor', 'textsize', 'box1', 'box2', 'string', 'leftpos', 'scale',
- 'toppos', 'rightpos', 'bottompos', 'width', 'height', 'pop', 'keyboard',
- 'red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple', 'white',
- 'black', 'titlex', 'titley', 'leftx', 'topy', 'rightx', 'bottomy',
- 'sound', 'volume', 'pitch', 'voltage', 'resistance', 'gray', 'see', 'rfid',
- 'luminance']
-BOX_STYLE_MEDIA = ['description', 'audio', 'journal', 'video', 'camera']
-NUMBER_STYLE = ['plus2', 'product2', 'myfunc']
-NUMBER_STYLE_VAR_ARG = ['myfunc1arg', 'myfunc2arg', 'myfunc3arg']
-NUMBER_STYLE_BLOCK = ['random']
-NUMBER_STYLE_PORCH = ['minus2', 'division2', 'remainder2']
-NUMBER_STYLE_1ARG = ['sqrt', 'identity2']
-NUMBER_STYLE_1STRARG = ['box']
-COMPARE_STYLE = ['greater2', 'less2', 'equal2']
-BOOLEAN_STYLE = ['and2', 'or2']
-NOT_STYLE = ['not']
-FLOW_STYLE = ['forever']
-FLOW_STYLE_TAIL = ['hspace']
-FLOW_STYLE_1ARG = ['repeat']
-FLOW_STYLE_BOOLEAN = ['if', 'while', 'until']
-FLOW_STYLE_WHILE = ['while2']
-FLOW_STYLE_ELSE = ['ifelse']
-COLLAPSIBLE_TOP = ['sandwichtop']
-COLLAPSIBLE_TOP_NO_ARM = ['sandwichtop_no_arm']
-COLLAPSIBLE_TOP_NO_LABEL = ['sandwichtop_no_label']
-COLLAPSIBLE_TOP_NO_ARM_NO_LABEL = ['sandwichtop_no_arm_no_label']
-COLLAPSIBLE_BOTTOM = ['sandwichbottom']
-
-# Depreciated block styles
-PORTFOLIO_STYLE_2x2 = ['template2x2']
-PORTFOLIO_STYLE_1x1 = ['template1x1', 'template1x1a']
-PORTFOLIO_STYLE_2x1 = ['template2x1']
-PORTFOLIO_STYLE_1x2 = ['template1x2']
+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 = ['vspace', 'hspace', 'identity2']
+EXPANDABLE_STYLE = ['boolean-style', 'compare-porch-style', 'compare-style',
+ 'number-style-porch', 'number-style', 'basic-style-2arg',
+ 'number-style-block']
-EXPANDABLE_BLOCKS = ['plus2', 'minus2', 'division2', 'remainder2', 'product2',
- 'random', 'equal2', 'greater2', 'less2', 'and2', 'or2',
- 'arc', 'setxy', 'setxy2', 'fillscreen', 'storein', 'write']
+EXPANDABLE = ['vspace', 'hspace', 'identity2']
-EXPANDABLE_ARGS = ['templatelist', 'list', 'myfunc1arg', 'myfunc2arg',
+EXPANDABLE_ARGS = ['list', 'myfunc1arg', 'myfunc2arg',
'myfunc3arg', 'userdefined', 'userdefined2args',
'userdefined3args']
#
@@ -270,418 +98,18 @@ EXPANDABLE_ARGS = ['templatelist', 'list', 'myfunc1arg', 'myfunc2arg',
COLLAPSIBLE = ['sandwichbottom', 'sandwichcollapsed']
#
-# Depreciated block styles that need dock adjustments
+# Deprecated block styles that need dock adjustments
#
OLD_DOCK = ['and', 'or', 'plus', 'minus', 'division', 'product', 'remainder']
#
-# Blocks that contain media
-#
-CONTENT_BLOCKS = ['number', 'string', 'description', 'audio', 'video',
- 'journal', 'camera']
-
-#
# These blocks get a special skin
#
BLOCKS_WITH_SKIN = ['journal', 'audio', 'description', 'nop', 'userdefined',
- 'video', 'userdefined2args', 'userdefined3args']
+ 'video', 'userdefined2args', 'userdefined3args', 'camera']
PYTHON_SKIN = ['nop', 'userdefined', 'userdefined2args', 'userdefined3args']
-#
-# These blocks hold constants
-#
-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}
-
-#
-# Block-name dictionary used for labels
-#
-BLOCK_NAMES = {
- 'addturtle': [_('turtle')],
- 'and2': [_('and')],
- 'arc': [_('arc'), _('angle'), _('radius')],
- 'audio': [' '],
- 'back': [_('back')],
- 'black': [_('black')],
- 'blocks': [_('blocks')],
- 'blue': [_('blue')],
- 'bottompos': [_('bottom')],
- 'bottomy': [_('picture bottom')],
- 'box': [_('box')],
- 'box1': [_('box 1')],
- 'box2': [_('box 2')],
- 'camera': [' '],
- 'cartesian': [_('Cartesian')],
- 'clean': [_(' clean ')],
- 'clearheap': [_('empty heap')],
- 'color': [_('color')],
- 'colors': [_('colors')],
- 'comment': [_('comment')],
- 'cyan': [_('cyan')],
- 'decription': [' '],
- 'division2': ['/'],
- 'empty': [_('empty trash')],
- 'equal2': ['='],
- 'extras': [_('extras')],
- 'fillscreen': [_('fill screen'), _('color'), _('shade')],
- 'flow': [_('flow')],
- 'forever': [_('forever')],
- 'forward': [_('forward')],
- 'fullscreen': [_('full screen')],
- 'gray': [_('gray')],
- 'greater2': [">"],
- 'green': [_('green')],
- 'hat': [_('action')],
- 'hat1': [_('action 1')],
- 'hat2': [_('action 2')],
- 'heading': [_('heading')],
- 'height': [_('height')],
- 'hideblocks': [_('hide blocks')],
- 'hspace': [' '],
- 'identity2': ['←'],
- 'if': [' ', _('if'), _('then')],
- 'ifelse': [' ', _('if'), _('then else')],
- 'image': [_('show')],
- 'journal': [' '],
- 'kbinput': [_('query keyboard')],
- 'keyboard': [_('keyboard')],
- 'left': [_('left')],
- 'leftpos': [_('left')],
- 'leftx': [_('picture left')],
- 'less2': ['<'],
- 'list': ['list'],
- 'luminance': [_('brightness')],
- 'mediawait': [_('media wait')],
- 'minus2': ['–'],
- 'myfunc': [_('Python'), 'f(x)', 'x'],
- 'myfunc1arg': [_('Python'), 'f(x)', 'x'],
- 'myfunc2arg': [_('Python'), 'f(x,y)', ' '],
- 'myfunc3arg': [_('Python'), 'f(x,y,z)', ' '],
- 'nop': [_(' ')],
- 'not': [_('not')],
- 'number': ['100'],
- 'numbers': [_('numbers')],
- 'orange': [_('orange')],
- 'or2': [_('or')],
- 'pen': [_('pen')],
- 'pendown': [_('pen down')],
- 'pensize': [_('pen size')],
- 'penup': [_('pen up')],
- 'picturelist': [' '],
- 'picture1x1': [' '],
- 'picture1x1a': [' '],
- 'picture2x2': [' '],
- 'picture2x1': [' '],
- 'picture1x2': [' '],
- 'pitch': [_('pitch')],
- 'plus2': [' + '],
- 'polar': [_('polar')],
- 'pop': [_('pop')],
- 'portfolio': [_('portfolio')],
- 'printheap': [_('show heap')],
- 'print': [_('print')],
- 'product2': ['×'],
- 'purple': [_('purple')],
- 'push': [_('push')],
- 'random': [_('random'), _('min'), _('max')],
- 'readcamera': [_('read camera')],
- 'readpixel': [_('read pixel')],
- 'red': [_('red')],
- 'remainder2': [_('mod')],
- 'repeat': [' ', _('repeat')],
- 'reskin': [_('turtle shell')],
- 'resistance': [_('resistance')],
- 'restore': [_('restore last')],
- 'restoreall': [_('restore all')],
- 'rfid': [_('RFID')],
- 'right': [_('right')],
- 'rightpos': [_('right')],
- 'rightx': [_('picture right')],
- 'savepix': [_('save picture')],
- 'savesvg': [_('save SVG')],
- 'sandwichbottom': [' ', ' '],
- 'sandwichcollapsed': [' '],
- 'sandwichtop': [_('top of stack')],
- 'sandwichtop_no_arm': [_('top of stack')],
- 'sandwichtop_no_label': [' ', ' '],
- 'sandwichtop_no_arm_no_label': [' ', _('click to open')],
- 'scale': [_('scale')],
- 'see': [_('turtle sees')],
- 'sensor': [_('sensors')],
- 'setcolor': [_('set color')],
- 'setgray': [_('set gray')],
- 'seth': [_('set heading')],
- 'setpensize': [_('set pen size')],
- 'setscale': [_('set scale')],
- 'setshade': [_('set shade')],
- 'settextcolor': [_('set text color')],
- 'settextsize': [_('set text size')],
- 'setxy': [_('set xy'), _('x'), _('y')],
- 'setxy2': [_('set xy'), _('x'), _('y')],
- 'shade': [_('shade')],
- 'show': [_('show')],
- 'showblocks': [_('show blocks')],
- 'showaligned': [_('show aligned')],
- 'skin': [_('turtle shell')],
- 'sound': [_('sound')],
- 'sqrt': ['√'],
- 'stack': [_('action')],
- 'stack1': [_('action 1')],
- 'stack2': [_('action 2')],
- 'start': [_('start')],
- 'startfill': [_('start fill')],
- 'stopfill': [_('end fill')],
- 'stopstack': [_('stop action')],
- 'storein': [_('store in'), _('box'), _('value')],
- 'storeinbox1': [_('store in box 1')],
- 'storeinbox2': [_('store in box 2')],
- 'string': [_('text')],
- 'template1x1': [' '],
- 'template1x1a': [' '],
- 'template1x2': [' '],
- 'template2x1': [' '],
- 'template2x2': [' '],
- 'templatelist': [' '],
- 'textsize': [_('text size')],
- 'titlex': [_('title x')],
- 'titley': [_('title y')],
- 'toppos': [_('top')],
- 'topy': [_('picture top')],
- 'trash': [_('trash')],
- 'turtle': [_('turtle')],
- 'until': [_('until')],
- 'userdefined': [_(' ')],
- 'userdefined2args': [_(' ')],
- 'userdefined3args': [_(' ')],
- 'video': [' '],
- 'voltage': [_('voltage')],
- 'volume': [_('volume')],
- 'vspace': [' '],
- 'wait': [_('wait')],
- 'while': [_('while')],
- 'while2': [_('while')],
- 'white': [_('white')],
- 'width': [_('width')],
- 'write': [_('write')],
- 'xcor': [_('xcor')],
- 'ycor': [_('ycor')],
- 'yellow': [_('yellow')]}
-
-#
-# Logo primitives
-#
-
-PRIMITIVES = {
- 'addturtle': 'turtle',
- 'and2': 'and',
- 'arc': 'arc',
- 'back': 'back',
- 'black': 'black',
- 'blue': 'blue',
- 'bottompos': 'bpos',
- 'bottomy': 'boty',
- 'box1': 'box1',
- 'box2': 'box2',
- 'box': 'box',
- 'cartesian': 'cartesian',
- 'clean': 'clean',
- 'clearheap': 'clearheap',
- 'color': 'color',
- 'comment': 'comment',
- 'cyan': 'cyan',
- 'division2': 'division',
- 'equal2': 'equal?',
- 'fillscreen': 'fillscreen',
- 'forever': 'forever',
- 'forward': 'forward',
- 'fullscreen': 'fullscreen',
- 'gray': 'gray',
- 'greater2': 'greater?',
- 'green': 'green',
- 'hat': 'nop3',
- 'hat1': 'nop1',
- 'hat2': 'nop2',
- 'heading': 'heading',
- 'height': 'vres',
- 'hideblocks': 'hideblocks',
- 'hspace': 'nop',
- 'identity2': 'id',
- 'if': 'if',
- 'ifelse': 'ifelse',
- 'image': 'show',
- 'kbinput': 'kbinput',
- 'keyboard': 'keyboard',
- 'left': 'left',
- 'leftpos': 'lpos',
- 'leftx': 'leftx',
- 'less2': 'less?',
- 'list': 'bulletlist',
- 'luminance': 'luminance',
- 'mediawait': 'mediawait',
- 'minus2': 'minus',
- 'myfunc': 'myfunction',
- 'myfunc1arg': 'myfunction',
- 'myfunc2arg': 'myfunction2',
- 'myfunc3arg': 'myfunction3',
- 'nop': 'userdefined',
- 'not': 'not',
- 'orange': 'orange',
- 'or2': 'or',
- 'pendown': 'pendown',
- 'pensize': 'pensize',
- 'penup': 'penup',
- 'pitch': 'pitch',
- 'plus2': 'plus',
- 'polar': 'polar',
- 'pop': 'pop',
- 'printheap': 'printheap',
- 'print': 'print',
- 'product2': 'product',
- 'purple': 'purple',
- 'push': 'push',
- 'random': 'random',
- 'red': 'red',
- 'readcamera': 'readcamera',
- 'readpixel': 'readpixel',
- 'remainder2': 'mod',
- 'repeat': 'repeat',
- 'resistance': 'resistance',
- 'rfid': 'rfid',
- 'right': 'right',
- 'rightpos': 'rpos',
- 'rightx': 'rightx',
- 'sandwichtop': 'comment',
- 'sandwichtop_no_arm': 'comment',
- 'sandwichtop_no_label': 'nop',
- 'sandwichtop_no_arm_no_label': 'nop',
- 'sandwichbottom': 'nop',
- 'sandwichcollapsed': 'nop',
- 'savepix': 'savepix',
- 'savesvg': 'savesvg',
- 'see': 'see',
- 'scale': 'scale',
- 'setcolor': 'setcolor',
- 'setgray': 'setgray',
- 'seth': 'seth',
- 'setpensize': 'setpensize',
- 'setscale': 'setscale',
- 'setshade': 'setshade',
- 'settextsize': 'settextsize',
- 'settextcolor': 'settextcolor',
- 'setxy': 'setxy',
- 'setxy2': 'setxy2',
- 'shade': 'shade',
- 'show': 'show',
- 'showblocks': 'showblocks',
- 'showaligned': 'showaligned',
- 'skin': 'skin',
- 'sound': 'sound',
- 'sqrt': 'sqrt',
- 'stack': 'stack',
- 'stack1': 'stack1',
- 'stack2': 'stack2',
- 'start': 'start',
- 'startfill': 'startfill',
- 'stopfill': 'stopfill',
- 'stopstack': 'stopstack',
- 'storein': 'storeinbox',
- 'storeinbox1': 'storeinbox1',
- 'storeinbox2': 'storeinbox2',
- 'template1x1': 't1x1',
- 'template1x1a': 't1x1a',
- 'template1x2': 't1x2',
- 'template2x1': 't2x1',
- 'template2x2': 't2x2',
- 'templatelist': 'bullet',
- 'textsize': 'textsize',
- 'titlex': 'titlex',
- 'titley': 'titley',
- 'toppos': 'tpos',
- 'topy': 'topy',
- 'userdefined': 'userdefined',
- 'userdefined2args': 'userdefined2',
- 'userdefined3args': 'userdefined3',
- 'voltage': 'voltage',
- 'volume': 'volume',
- 'vspace': 'nop',
- 'wait': 'wait',
- 'while2': 'while',
- 'white': 'white',
- 'width': 'hres',
- 'write': 'write',
- 'xcor': 'xcor',
- 'ycor': 'ycor',
- 'yellow': 'yellow'}
-
-#
-# block default values
-#
-
-DEFAULTS = {
- 'addturtle': [1],
- 'arc': [90, 100],
- 'audio': [None],
- 'back': [100],
- 'box': [_('my box')],
- 'camera': ['CAMERA'],
- 'comment': [_('comment')],
- 'description': [None],
- 'fillscreen': [60, 80],
- 'forever': [None, 'vspace'],
- 'forward': [100],
- 'hat': [_('action')],
- 'if': [None, None, 'vspace'],
- 'ifelse': [None, 'vspace', None, 'vspace'],
- 'journal': [None],
- 'left': [90],
- 'list': ['∙ ', '∙ '],
- 'media': [None],
- 'myfunc': ['x', 100],
- 'myfunc1arg': ['x', 100],
- 'myfunc2arg': ['x+y', 100, 100],
- 'myfunc3arg': ['x+y+z', 100, 100, 100],
- 'nop': [100],
- 'number': [100],
- 'random': [0, 100],
- 'repeat': [4, None, 'vspace'],
- 'right': [90],
- 'sandwichtop': [_('label')],
- 'sandwichtop_no_arm': [_('label')],
- 'savepix': [_('picture name')],
- 'savesvg': [_('picture name')],
- 'setcolor': [0],
- 'setgray': [100],
- 'seth': [0],
- 'setpensize': [5],
- 'setscale': [33],
- 'setshade': [50],
- 'settextsize': [48],
- 'settextcolor': [0],
- 'setxy': [0, 0],
- 'setxy2': [0, 0],
- 'show': [_('text')],
- 'showaligned': [_('text')],
- 'stack': [_('action')],
- 'storeinbox1': [100],
- 'storeinbox2': [100],
- 'storein': [_('my box'), 100],
- 'string': [_('text')],
- 'template1x1': [_('Title'), 'None'],
- 'template1x1a': [_('Title'), 'None'],
- 'template1x2': [_('Title'), 'None', 'None'],
- 'template2x1': [_('Title'), 'None', 'None'],
- 'template2x2': [_('Title'), 'None', 'None', 'None', 'None'],
- 'templatelist': [_('Title'), '∙ '],
- 'userdefined': [100],
- 'userdefined2args': [100, 100],
- 'userdefined3args': [100, 100, 100],
- 'video': [None],
- 'wait': [1],
- 'write': [_('text'), 32]}
#
# Blocks that can interchange strings and numbers for their arguments
@@ -697,6 +125,9 @@ STRING_OR_NUMBER_ARGS = ['plus2', 'equal2', 'less2', 'greater2', 'box',
CONTENT_ARGS = ['show', 'showaligned', 'push', 'storein', 'storeinbox1',
'storeinbox2']
+PREFIX_DICTIONARY = {'journal': '#smedia_', 'description': '#sdescr_',
+ 'audio': '#saudio_', 'video': '#svideo_'}
+
#
# Status blocks
#
@@ -709,7 +140,7 @@ MEDIA_SHAPES = ['audiooff', 'audioon', 'audiosmall',
'pythonoff', 'pythonon', 'pythonsmall',
'list', '1x1', '1x1a', '2x1', '1x2', '2x2']
-OVERLAY_SHAPES = ['Cartesian', 'Cartesian_labeled', 'polar']
+OVERLAY_SHAPES = ['Cartesian', 'Cartesian_labeled', 'polar', 'metric']
STATUS_SHAPES = ['status', 'info', 'nostack', 'dupstack', 'noinput',
'emptyheap', 'emptybox', 'nomedia', 'nocode', 'overflowerror',
@@ -735,7 +166,8 @@ OLD_NAMES = {'product': 'product2', 'storeinbox': 'storein', 'minus': 'minus2',
'template1': 'template1x1', 'template2': 'template2x1',
'template6': 'template1x2', 'template7': 'template2x2',
'template4': 'template1x1a', 'hres': 'width', 'vres': 'height',
- 'sandwichtop2': 'sandwichtop'}
+ 'sandwichtop2': 'sandwichtop', 'image': 'show',
+ 'container': 'indentity2', 'insertimage': 'show'}
#
# Define the relative size and postion of media objects
@@ -744,7 +176,7 @@ OLD_NAMES = {'product': 'product2', 'storeinbox': 'storein', 'minus': 'minus2',
TITLEXY = (0.9375, 0.875)
#
-# Relative placement of portfolio objects (used by depreciated blocks)
+# 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),
@@ -755,215 +187,21 @@ TEMPLATES = {'t1x1': (0.5, 0.5, 0.0625, 0.125, 1.05, 0),
'insertimage': (0.333, 0.333)}
#
-# Names for blocks without names for popup help
-#
-SPECIAL_NAMES = {
- 'audio': _('audio'),
- 'camera': _('camera'),
- 'division2': _('divide'),
- 'equal2': _('equal'),
- 'greater2': _('greater than'),
- 'hspace': _('horizontal space'),
- 'identity2': _('identity'),
- 'if': _('if then'),
- 'ifelse': _('if then else'),
- 'journal': _('journal'),
- 'less2': _('less than'),
- 'minus2': _('minus'),
- 'nop': _('Python code'),
- 'number': _('number'),
- 'plus2': _('plus'),
- 'product2': _('multiply'),
- 'sqrt': _('square root'),
- 'template1x1': _('presentation 1x1'),
- 'template1x1a': _('presentation 1x1'),
- 'template1x2': _('presentation 1x2'),
- 'template2x1': _('presentation 2x1'),
- 'template2x2': _('presentation 2x2'),
- 'templatelist': _('presentation bulleted list'),
- 'textsize': _('text size'),
- 'video': _('video'),
- 'vspace': _('vertical space')}
-
-#
-# Help messages
-#
-HELP_STRINGS = {
- 'addturtle': _("chooses which turtle to command"),
- 'and2': _("logical AND operator"),
- 'arc': _("moves turtle along an arc"),
- 'audio': _("Sugar Journal audio object"),
- 'back': _("moves turtle backward"),
- 'blocks': _("Palette of variable blocks"),
- 'bottompos': _("ycor of bottom of screen"),
- 'box1': _("Variable 1 (numeric value)"),
- 'box2': _("Variable 2 (numeric value)"),
- 'box': _("named variable (numeric value)"),
- 'camera': _('camera output'),
- 'cartesian': _("displays Cartesian coordinates"),
- 'clean': _("clears the screen and reset the turtle"),
- 'clearheap': _("emptys FILO (first-in-last-out heap)"),
- 'color': _("holds current pen color (can be used in place of a number block)"),
- 'colors': _("Palette of pen colors"),
- 'comment': _("places a comment in your code"),
- 'debugoff': _("Debug"),
- 'description': _("Sugar Journal description field"),
- 'division2': _("divides top numeric input (numerator) by bottom numeric input (denominator)"),
- 'empty': _("permanently deletes items in trash"),
- 'eraseron': _("Clean"),
- 'equal2': _("logical equal-to operator"),
- 'extras': _("Palette of extra options"),
- 'fillscreen': _("fills the background with (color, shade)"),
- 'flow': _("Palette of flow operators"),
- 'forever': _("loops forever"),
- 'forward': _("moves turtle forward"),
- 'fullscreen': _("hides the Sugar toolbars"),
- 'gray': _("holds current gray level (can be used in place of a number block)"),
- 'greater2': _("logical greater-than operator"),
- 'hat1': _("top of Action 1 stack"),
- 'hat2': _("top of Action 2 stack"),
- 'hat': _("top of nameable action stack"),
- 'heading': _("holds current heading value of the turtle (can be used in place of a number block)"),
- 'height': _("the canvas height"),
- 'hideblocks': _("declutters canvas by hiding blocks"),
- 'hideshowoff': _("Hide blocks"),
- 'hspace': _("jogs stack right"),
- 'identity2': _("identity operator used for extending blocks"),
- 'ifelse': _("if-then-else operator that uses boolean operators from Numbers palette"),
- 'if': _("if-then operator that uses boolean operators from Numbers palette"),
- 'journal': _("Sugar Journal media object"),
- 'kbinput': _("query for keyboard input (results stored in keyboard block)"),
- 'keyboard': _("holds results of query-keyboard block"),
- 'leftpos': _("xcor of left of screen"),
- 'left': _("turns turtle counterclockwise (angle in degrees)"),
- 'less2': _("logical less-than operator"),
- 'luminance': _("light level detected by camera"),
- 'mediawait': _("wait for current video or audio to complete"),
- 'minus2': _("subtracts bottom numeric input from top numeric input"),
- 'myfunc': _("a programmable block: used to add advanced math equations, e.g., sin(x)"),
- 'myfunc1arg': _("a programmable block: used to add advanced single-variable math equations, e.g., sin(x)"),
- 'myfunc2arg': _("a programmable block: used to add advanced multi-variable math equations, e.g., sqrt(x*x+y*y)"),
- 'myfunc3arg': _("a programmable block: used to add advanced multi-variable math equations, e.g., sin(x+y+z)"),
- 'next': _('displays next palette'),
- 'nop': _("runs code found in the tamyblock.py module found in the Journal"),
- 'not': _("logical NOT operator"),
- 'numbers': _("Palette of numeric operators"),
- 'number': _("used as numeric input in mathematic operators"),
- 'or': _("logical OR operator"),
- 'orientation': _("changes the orientation of the palette of blocks"),
- 'pendown': _("Turtle will draw when moved."),
- 'pen': _("Palette of pen commands"),
- 'pensize': _("holds current pen size (can be used in place of a number block)"),
- 'penup': _("Turtle will not draw when moved."),
- 'picture1x1': _("presentation template: select Journal object (with description)"),
- 'picture1x1a': _("presentation template: select Journal object (no description)"),
- 'picture1x2': _("presentation template: select two Journal objects"),
- 'picture2x1': _("presentation template: select two Journal objects"),
- 'picture2x2': _("presentation template: select four Journal objects"),
- 'picturelist': _("presentation template: list of bullets"),
- 'pitch': _('microphone input pitch'),
- 'plus2': _("adds two alphanumeric inputs"),
- 'polar': _("displays polar coordinates"),
- 'pop': _("pops value off FILO (first-in last-out heap)"),
- 'portfolio': _("Palette of presentation templates"),
- 'print': _("prints value in status block at bottom of the screen"),
- 'printheap': _("shows values in FILO (first-in last-out heap)"),
- 'product2': _("multiplies two numeric inputs"),
- 'push': _("pushes value onto FILO (first-in last-out heap)"),
- 'random': _("returns random number between minimum (top) and maximum (bottom) values"),
- 'readcamera': _("Average RGB color from camera is pushed to the stack"),
- 'readpixel': _("RGB color under the turtle is pushed to the stack"),
- 'remainder2': _("modular (remainder) operator"),
- 'repeat': _("loops specified number of times"),
- 'resistance': _("sensor input resistance"),
- 'reskin': _("put a custom 'shell' on the turtle"),
- 'restore': _("restores most recent blocks from trash"),
- 'restoreall': _("restore all blocks from trash"),
- 'rfid': _("RFID"),
- 'rightpos': _("xcor of right of screen"),
- 'right': _("turns turtle clockwise (angle in degrees)"),
- 'run-fastoff': _("Run"),
- 'run-slowoff': _("Step"),
- 'sandwichbottom': _("bottom block in a collapsibe stack: click to collapse"),
- 'sandwichcollapsed': _("bottom block in a collapsed stack: click to open"),
- 'sandwichtop': _("top of a collapsible stack"),
- 'sandwichtop_no_label': _("top of a collapsed stack"),
- 'sandwichtop_no_arm': _("top of a collapsible stack"),
- 'sandwichtop_no_arm_no_label': _("top of a collapsed stack"),
- 'savepix': _("saves a picture to the Sugar Journal"),
- 'savesvg': _("saves turtle graphics as an SVG file in the Sugar Journal"),
- 'scale': _("holds current scale value"),
- 'see': _('returns the color that the turtle "sees"'),
- 'sensor': _("Palette of sensor blocks"),
- 'setcolor': _("sets color of the line drawn by the turtle"),
- 'setgray': _("sets gray level of the line drawn by the turtle"),
- 'seth': _("sets the heading of the turtle (0 is towards the top of the screen.)"),
- 'setpensize': _("sets size of the line drawn by the turtle"),
- 'setscale': _("sets the scale of media"),
- 'setshade': _("sets shade of the line drawn by the turtle"),
- 'settextcolor': _("sets color of text drawn by the turtle"),
- 'settextsize': _("sets size of text drawn by turtle"),
- 'setxy': _("moves turtle to position xcor, ycor; (0, 0) is in the center of the screen."),
- 'setxy2': _("moves turtle to position xcor, ycor; (0, 0) is in the center of the screen."),
- 'shade': _("holds current pen shade"),
- 'show': _("draws text or show media from the Journal"),
- 'showblocks': _("restores hidden blocks"),
- 'skin': _("put a custom 'shell' on the turtle"),
- 'sound': _("raw microphone input signal"),
- 'sqrt': _("calculates square root"),
- 'stack1': _("invokes Action 1 stack"),
- 'stack2': _("invokes Action 2 stack"),
- 'stack': _("invokes named action stack"),
- 'start': _("connects action to toolbar run buttons"),
- 'startfill': _("starts filled polygon (used with end fill block)"),
- 'stopfill': _("completes filled polygon (used with start fill block)"),
- 'stopiton': _("Stop turtle"),
- 'stopstack': _("stops current action"),
- 'storeinbox1': _("stores numeric value in Variable 1"),
- 'storeinbox2': _("stores numeric value in Variable 2"),
- 'storein': _("stores numeric value in named variable"),
- 'string': _("string value"),
- 'template1x1': _("presentation template: select Journal object (with description)"),
- 'template1x1a': _("presentation template: select Journal object (no description)"),
- 'template1x2': _("presentation template: select two Journal objects"),
- 'template2x1': _("presentation template: select two Journal objects"),
- 'template2x2': _("presentation template: select four Journal objects"),
- 'templatelist': _("presentation template: list of bullets"),
- 'textcolor': _("holds current text color (can be used in place of a number block)"),
- 'textsize': _("holds current text size (can be used in place of a number block)"),
- 'toppos': _("ycor of top of screen"),
- 'trash': _("Trashcan"),
- 'turtle': _("Palette of turtle commands"),
- 'until': _("do-until-True operator that uses boolean operators from Numbers palette"),
- 'userdefined': _("runs code found in the tamyblock.py module found in the Journal"),
- 'userdefined2args': _("runs code found in the tamyblock.py module found in the Journal"),
- 'userdefined3args': _("runs code found in the tamyblock.py module found in the Journal"),
- 'video': _("Sugar Journal video object"),
- 'voltage': _("sensor voltage"),
- 'volume': _("microphone input volume"),
- 'vspace': _("jogs stack down"),
- 'wait': _("pauses program execution a specified number of seconds"),
- 'while': _("do-while-True operator that uses boolean operators from Numbers palette"),
- 'width': _("the canvas width"),
- 'xcor': _("holds current x-coordinate value of the turtle (can be used in place of a number block)"),
- 'ycor': _("holds current y-coordinate value of the turtle (can be used in place of a number block)")}
-
-#
# '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}]
+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',
diff --git a/TurtleArt/taexporthtml.py b/TurtleArt/taexporthtml.py
index 09042f8..843b8b0 100644
--- a/TurtleArt/taexporthtml.py
+++ b/TurtleArt/taexporthtml.py
@@ -22,67 +22,68 @@ import pygtk
pygtk.require('2.0')
import gtk
import os.path
-from tautils import data_to_string, save_picture, image_to_base64
-from gettext import gettext as _
from cgi import escape
+from gettext import gettext as _
+from tautils import data_to_string, save_picture, image_to_base64, get_path
-def save_html(self, tw, embed_flag=True):
- """ Either: Save canvas and code or pictures to HTML """
- self.embed_images = embed_flag
-
- # A dictionary to define the HTML wrappers around template elements
- self.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" ' + \
+# 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')}
+ '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\
+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'
- if self.embed_images == True:
- self.html_glue['img'] = ('<img width="400" height="300" alt=" + \
- ""Image" src="data:image/png;base64,\n"',
- '" "/>\n')
- self.html_glue['img2'] = ('<img alt="Image" src="data:image/png;" + \
- "base64,\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.
+ If there are saved_pictures, put them into a .html; otherwise,
+ save a screendump and the turtle project code.
"""
- code = ''
+ htmlcode = ''
if len(tw.saved_pictures) > 0:
for i, p in enumerate(tw.saved_pictures):
- code += self.html_glue['slide'][0] + str(i)
- code += self.html_glue['slide'][1] + \
- self.html_glue['div'][0] + \
- self.html_glue['h1'][0]
- if self.embed_images == True:
+ htmlcode += HTML_GLUE['slide'][0] + str(i)
+ htmlcode += HTML_GLUE['slide'][1] + \
+ HTML_GLUE['div'][0] + \
+ HTML_GLUE['h1'][0]
+ if embed_flag:
f = open(p, 'r')
imgdata = f.read()
f.close()
@@ -90,10 +91,11 @@ def save_html(self, tw, embed_flag=True):
tmp = imgdata
else:
pixbuf = gtk.gdk.pixbuf_new_from_file(p)
- imgdata = image_to_base64(pixbuf, tw.activity)
- tmp = self.html_glue['img2'][0]
+ imgdata = image_to_base64(pixbuf,
+ get_path(tw.activity, 'instance'))
+ tmp = HTML_GLUE['img2'][0]
tmp += imgdata
- tmp += self.html_glue['img2'][1]
+ tmp += HTML_GLUE['img2'][1]
else:
if p.endswith(('.svg')):
f = open(p, 'r')
@@ -101,49 +103,49 @@ def save_html(self, tw, embed_flag=True):
f.close()
tmp = imgdata
else:
- tmp = self.html_glue['img3'][0]
+ tmp = HTML_GLUE['img3'][0]
tmp += p
- tmp += self.html_glue['img3'][1]
- code += tmp + \
- self.html_glue['h1'][1] + \
- self.html_glue['div'][1]
+ tmp += HTML_GLUE['img3'][1]
+ htmlcode += tmp + \
+ HTML_GLUE['h1'][1] + \
+ HTML_GLUE['div'][1]
else:
- if self.embed_images == True:
+ if embed_flag:
imgdata = image_to_base64(save_picture(self.tw.canvas),
- tw.activity)
+ get_path(tw.activity, 'instance'))
else:
imgdata = os.path.join(self.tw.load_save_folder, 'image')
self.tw.save_as_image(imgdata)
- code += (self.html_glue['img'][0] + imgdata + \
- self.html_glue['img'][1])
- code += self.html_glue['div'][0]
- code += escape(data_to_string(tw.assemble_data_to_save(False, True)))
- code += self.html_glue['div'][1]
+ 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 = self.html_glue['doctype'] + \
- self.html_glue['html'][0]
- style = self.html_glue['style'][0] + \
- self.html_glue['style'][1]
+ 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].endswith(('.svg')):
- header = self.html_glue['html_svg'][0]
- style = comment
+ header = HTML_GLUE['html_svg'][0]
+ style = COMMENT
- code = header + \
- self.html_glue['head'][0] + \
- self.html_glue['meta'] + \
- self.html_glue['title'][0] + \
+ return header + \
+ HTML_GLUE['head'][0] + \
+ HTML_GLUE['meta'] + \
+ HTML_GLUE['title'][0] + \
title + \
- self.html_glue['title'][1] + \
+ HTML_GLUE['title'][1] + \
style + \
- self.html_glue['head'][1] + \
- self.html_glue['body'][0] + \
- code + \
- self.html_glue['body'][1] + \
- self.html_glue['html'][1]
- return code
+ 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
index bd8a1e6..127689b 100644
--- a/TurtleArt/taexportlogo.py
+++ b/TurtleArt/taexportlogo.py
@@ -1,4 +1,4 @@
-#Copyright (c) 2008-10, Walter Bender
+#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
@@ -18,351 +18,255 @@
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
-IGNORE = ['hideblocks', 'showblocks', 'fullscreen', 'polar', 'cartesian',
- 'sandwichbottom', 'id']
+from gettext import gettext as _
-import math
try:
from sugar.datastore import datastore
+ HAS_DATASTORE = True
except:
- pass
+ 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. """
- color_processing = '\
-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'
-
- bs = tw.just_blocks()
- code = ''
+
+ # 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
- show = 0
-
- # These flags are used to trigger the prepending of additional procedures.
- random = False
- fillscreen = False
- setcolor = False
- setxy = False
- setxy2 = False
- pensize = False
- setpensize = False
- arc = False
- heap = False
- write = False
- minus = False
- division = False
- image = False
+
+ logocode = ''
"""
Walk through the code, substituting UCB Logo for Turtle Art primitives.
"""
- for b in bs:
+ for stack in stacks_of_blocks:
this_stack = ''
- data = walk_stack(tw.lc, b, tw.block_list.list)
- # We need to catch several special cases: stacks, random, etc.
- stack = False
- namedstack = False
- namedbox = False
- refstack = False
- refbox = False
- myvar = ''
- for d in data:
- if type(d) == type((1, 2)):
- (d, b) = d
- if type(d) is float:
- if namedbox:
- myvar += str(d)
- myvar += ' '
- elif write:
- this_stack += 'labelsize '
- this_stack += str(d)
- write = False
- else:
- this_stack += str(d)
- elif show == 2:
- # Use title for Journal objects
- if d[0:8] == '#smedia_':
- try:
- dsobject = datastore.get(d[8:])
- this_stack += dsobject.metadata['title']
- dsobject.destroy()
- except:
- this_stack += str(d)
- else:
- this_stack += str(d)
- show = 0
+ 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:
- # Translate some Turtle Art primitives into UCB Logo
- if namedstack:
- this_stack += 'to '
- this_stack += d[2:].replace(' ', '_')
- this_stack += '\r'
- stack = True
- namedstack = False
- elif namedbox:
- if d[0:2] == '#s':
- this_stack += 'make "'
- this_stack += d[2:].replace(' ', '_')
- this_stack += ' '
- this_stack += myvar
- namedbox = False
- myvar = ''
- else:
- myvar += d
- elif refstack:
- this_stack += d[2:].replace(' ', '_')
- this_stack += ' '
- refstack = False
- elif refbox:
- this_stack += ':'
- this_stack += d[2:].replace(' ', '_')
- refbox = False
- elif d == 'stack':
- refstack = True
- elif d == 'box':
- refbox = True
- elif d == 'storeinbox':
- namedbox = True
- elif d == 'storeinbox1':
- this_stack += 'make "box1'
- elif d == 'box1':
- this_stack += ':box1'
- elif d == 'storeinbox2':
- this_stack += 'make "box2'
- elif d == 'box2':
- this_stack += ':box2'
- elif d == 'shade':
- this_stack += ':shade'
- elif d == 'setshade':
- setcolor = True
- this_stack += 'tasetshade'
- elif d == 'color':
- this_stack += 'pencolor'
- elif d == 'nop':
- this_stack += ' '
- elif d == 'start':
- this_stack += 'to start\r'
- stack = True
- elif d == 'nop1':
- this_stack += 'to stack1\r'
- stack = True
- elif d == 'nop2':
- this_stack += 'to stack2\r'
- stack = True
- elif d == 'nop3':
- namedstack = True
- elif d == 'stopstack':
- this_stack += 'stop'
- elif d == 'clean':
- this_stack += 'clearscreen'
- elif d == 'setxy':
- setxy = True
- this_stack += 'tasetxypenup'
- elif d == 'setxy2':
- setxy2 = True
- this_stack += 'tasetxy'
- elif d == 'color':
- this_stack += ':color'
- elif d == 'plus':
- this_stack += 'sum'
- elif d == 'setcolor':
- setcolor = True
- this_stack += 'tasetpencolor'
- elif d == 'fillscreen':
- fillscreen = True
- setcolor = True
- this_stack += 'tasetbackground'
- elif d == 'random':
- random = True
- this_stack += 'tarandom'
- elif d == 'pensize':
- pensize = True
- this_stack += 'tapensize'
- elif d == 'setpensize':
- setpensize = True
- this_stack += 'tasetpensize'
- elif d == 'arc':
- arc = True
- this_stack += 'taarc'
- elif d == 'pop':
- heap = True
- this_stack += 'tapop'
- elif d == 'push':
- heap = True
- this_stack += 'tapush'
- elif d == 'heap':
- heap = True
- this_stack += 'taprintheap'
- elif d == 'emptyheap':
- heap = True
- this_stack += 'taclearheap'
- elif d == 'kbinput':
- this_stack += 'make "keyboard readchar'
- elif d == 'keyboard':
- this_stack += ':keyboard'
- elif d == 'insertimage':
- image = True
- elif image:
- # Skip this arg
- image = 2
- elif image == 2:
- # Skip this arg
- image = False
- elif d[0:2] == '#s':
- # output single characters as a string
- if len(d[2:]):
- this_stack += '"'
- this_stack += d[2:]
- # make a sentence out of everything else
- else:
- this_stack += 'sentence '
- this_stack += d[2:].replace('\s', ' "')
- this_stack += '\r'
- elif d == 'write':
- this_stack += 'label'
- write = True
- elif d == 'show' or d == 'showaligned':
- this_stack += 'label'
- show = 1
- elif d == 'minus2':
- this_stack += 'taminus'
- minus = True
- elif d == 'division':
- this_stack += 'quotient'
- elif d == 'lpos':
- this_stack += str(-tw.canvas.width / (tw.coord_scale * 2))
- elif d == 'rpos':
- this_stack += str(tw.canvas.width / (tw.coord_scale * 2))
- elif d == 'bpos':
- this_stack += str(-tw.canvas.height / (tw.coord_scale * 2))
- elif d == 'tpos':
- this_stack += str(tw.canvas.height / (tw.coord_scale * 2))
- elif d in IGNORE:
- this_stack += ' '
- elif show == 1 and d[0:2] == '#s':
- this_stack += d[2:]
- # We don't handle depreciated 'template' blocks
+ 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:
- this_stack += d
+ 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 += ' '
- if stack:
- stack = False
- # if it is not a stack, we need to add a 'to ta#' label
- elif len(data) > 0:
- this_stack = 'to ta' + str(stack_count) + '\r' + this_stack
- stack_count += 1
- if len(data) > 0:
- code += this_stack
- code += '\rend\r'
-
- # We need to define some additional procedures.
- if minus: # Logo minus only takes one argument.
- code = 'to taminus :y :x\routput sum :x minus :y\rend\r' + code
- if random: # to avoid negative numbers
- code = 'to tarandom :min :max\r' + \
- 'output (random (:max - :min)) + :min\rend\r' + code
- if fillscreen: # Set shade than background color
- code = 'to tasetbackground :color :shade\r' + \
- 'tasetshade :shade\rsetbackground :color\rend\r' + code
- if setcolor: # Load the Turtle Art color palette.
- code = color_processing + code
- if setpensize: # Set int of pensize
- code = 'to tasetpensize :a\rsetpensize round :a\rend\r' + code
- if pensize: # Return only the first argument.
- code = 'to tapensize\routput first round pensize\rend\r' + code
- if setxy2: # Swap and round arguments
- code = 'to tasetxy :x :y\rsetxy :x :y\rend\r' + code
- if setxy: # Swap and round arguments and add pen up/down
- code = 'to tasetxy :x :y\rpenup\rsetxy :x :y\rpendown\rend\r' + code
- if arc: # Turtle Art 'arc' needs to be redefined.
- c = (2 * math.pi) / 360
- code = 'to taarc :a :r\rrepeat round :a [right 1 forward (' + \
- str(c) + ' * :r)]\rend\r' + code
- if heap: # Add psuedo 'push' and 'pop'
- code = 'to tapush :foo\rmake "taheap fput :foo :taheap\rend\r' + \
- 'to tapop\rif emptyp :taheap [stop]\rmake \'tmp first :taheap\r' +\
- 'make "taheap butfirst :taheap\routput :tmp\rend\r' + \
- 'to taclearheap\rmake "taheap []\rend\r' + \
- 'to taprintheap \rprint :taheap\rend\r' + \
- 'make "taheap []\r' + code
- code = 'window\r' + code
- return code
-
-
-def walk_stack(lc, blk, list):
+
+ 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)
- if blk == top:
- code = lc.run_blocks(top, list, False)
- return code
+ top = find_top_block(blk_in_stack)
+ if blk_in_stack == top:
+ psuedocode = tw.lc.run_blocks(top, tw.block_list.list, 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/tajail.py b/TurtleArt/tajail.py
index 081248f..0444dc7 100644
--- a/TurtleArt/tajail.py
+++ b/TurtleArt/tajail.py
@@ -19,14 +19,14 @@
#THE SOFTWARE.
# A naive approach to running myfunc in a jail
-import logging
-_logger = logging.getLogger('turtleart-activity')
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', '')
@@ -45,11 +45,16 @@ def myfunc(f, args):
return userdefined.values()[0](args[0], args[1], args[2])
-def myfunc_import(lc, f, x):
+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'](lc, x)
+ return userdefined['myblock'](base_class, x)
except:
traceback.print_exc()
return None
diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py
index e3576fa..12be92f 100644
--- a/TurtleArt/talogo.py
+++ b/TurtleArt/talogo.py
@@ -22,65 +22,22 @@
#THE SOFTWARE.
import gtk
-
from time import time
-from math import sqrt
-from numpy import append
-from numpy.fft import rfft
-from random import uniform
-from operator import isNumberType
-from fcntl import ioctl
-
-import os.path
+from operator import isNumberType
from UserDict import UserDict
-try:
- from sugar.datastore import datastore
-except ImportError:
- pass
-
-from taconstants import TAB_LAYER, BLACK, WHITE, \
- DEFAULT_SCALE, ICON_SIZE, BLOCK_NAMES, CONSTANTS, SENSOR_DC_NO_BIAS, \
- SENSOR_DC_BIAS, XO1, XO15
-from tagplay import play_audio_from_file, play_movie_from_file, stop_media, \
- media_playing
-from tacamera import Camera
-import v4l2
-from tajail import myfunc, myfunc_import
+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, chr_to_ord, strtype, get_path
-
-from RtfParser import RtfTextOnly
+ text_media_type, round_int, debug_output
-from ringbuffer import RingBuffer1d
+from util.RtfParser import RtfTextOnly
from gettext import gettext as _
-VALUE_BLOCKS = ['box1', 'box2', 'color', 'shade', 'gray', 'scale', 'pensize',
- 'heading', 'xcor', 'ycor', 'pop', 'see', 'keyboard',
- 'sound', 'volume', 'pitch', 'resistance', 'voltage',
- 'luminance']
-
-import logging
-_logger = logging.getLogger('turtleart-activity')
-
-
-def find_device():
- """ Search for RFID devices. Return a device instance or None. """
- device = None
- for i in os.listdir(os.path.join('.', 'devices')):
- if not os.path.isdir(os.path.join('.', 'devices', i)):
- try:
- _tempmod = __import__('devices.%s' % i.split('.')[0],
- globals(), locals(), ['RFIDReader'], -1)
- devtemp = _tempmod.RFIDReader()
- if devtemp.get_present() == True:
- device = devtemp
- except Exception, e:
- _logger.error('FIND_DEVICE: %s: %s' % (i, e))
- pass
- return device
+media_blocks_dictionary = {} # new media blocks get added here
+primitive_dictionary = {} # new block primitives get added here
class noKeyError(UserDict):
@@ -112,213 +69,6 @@ class logoerror(Exception):
# Utility functions
-
-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 _string_to_num(x):
- """ Try to comvert a string to a number """
- 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 _and(x, y):
- """ Logical and """
- return x & y
-
-
-def _or(x, y):
- """ Logical or """
- return x | y
-
-
-def _careful_divide(x, y):
- """ Raise error on divide by zero """
- try:
- return x / y
- except ZeroDivisionError:
- raise logoerror("#zerodivide")
- except TypeError:
- try:
- return _string_to_num(x) / _string_to_num(y)
- except ZeroDivisionError:
- raise logoerror("#zerodivide")
- except ValueError:
- raise logoerror("#syntaxerror")
- except TypeError:
- raise logoerror("#notanumber")
-
-
-def _equal(x, y):
- """ Numeric and logical equal """
- try:
- return float(x) == float(y)
- except TypeError:
- typex, typey = False, False
- if strtype(x):
- typex = True
- if strtype(y):
- typey = True
- if typex and typey:
- return x == y
- try:
- return _string_to_num(x) == _string_to_num(y)
- except ValueError:
- raise logoerror("#syntaxerror")
-
-
-def _less(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 _string_to_num(x) < _string_to_num(y)
- except TypeError:
- raise logoerror("#notanumber")
-
-
-def _more(x, y):
- """ Compare numbers and strings """
- return _less(y, x)
-
-
-def _plus(x, y):
- """ Add numbers, concat strings """
- if _num_type(x) and _num_type(y):
- return(x + y)
- 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 _minus(x, y):
- """ Numerical subtraction """
- if _num_type(x) and _num_type(y):
- return(x - y)
- try:
- return _string_to_num(x) - _string_to_num(y)
- except TypeError:
- raise logoerror("#notanumber")
-
-
-def _product(x, y):
- """ Numerical multiplication """
- if _num_type(x) and _num_type(y):
- return(x * y)
- try:
- return _string_to_num(x) * _string_to_num(y)
- except TypeError:
- raise logoerror("#notanumber")
-
-
-def _mod(x, y):
- """ Numerical mod """
- if _num_type(x) and _num_type(y):
- return(x % y)
- try:
- return _string_to_num(x) % _string_to_num(y)
- except TypeError:
- raise logoerror("#notanumber")
- except ValueError:
- raise logoerror("#syntaxerror")
-
-
-def _sqrt(x):
- """ Square root """
- if _num_type(x):
- if x < 0:
- raise logoerror("#negroot")
- return sqrt(x)
- try:
- return sqrt(_string_to_num(x))
- except ValueError:
- raise logoerror("#negroot")
- except TypeError:
- raise logoerror("#notanumber")
-
-
-def _random(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 = _string_to_num(x)
- if not yflag:
- yy = _string_to_num(y)
- try:
- return(int(round(uniform(xx, yy), 0)))
- except TypeError:
- raise logoerror("#notanumber")
-
-
-def _identity(x):
- """ Identity function """
- return(x)
-
-
-def _avg(array, abs_value=False):
- """ Calc. the average value of an array """
- if len(array) == 0:
- return 0
- array_sum = 0
- if abs_value:
- for a in array:
- array_sum += abs(a)
- else:
- for a in array:
- array_sum += a
- return float(array_sum) / len(array)
-
-
-def stop_logo(tw):
- """ Stop logo is called from the Stop button on the toolbar """
- tw.step_time = 0
- tw.lc.step = _just_stop()
- stop_media(tw.lc)
- if tw.camera_available:
- if tw.lc._video_capture_device is not None:
- # restore AG and then close device
- tw.lc._set_ag(1)
- tw.lc._video_capture_device.close()
- tw.lc._video_capture_device = None
- tw.lc.camera.stop_camera_input()
- tw.active_turtle.show()
-
-
def _just_stop():
""" yield False to stop stack """
yield False
@@ -337,164 +87,15 @@ class LogoCode:
self.tw = tw
self.oblist = {}
- DEFPRIM = {
- '(': [1, lambda self, x: self._prim_opar(x)],
- 'and': [2, lambda self, x, y: _and(x, y)],
- 'arc': [2, lambda self, x, y: self._prim_arc(self.tw.canvas.arc, x,
- y)],
- 'audio': [1, lambda self, x: self._play_sound(x)],
- 'back': [1, lambda self, x: self._prim_move(self.tw.canvas.forward,
- -x)],
- 'black': [0, lambda self: BLACK],
- 'blue': [0, lambda self: CONSTANTS['blue']],
- 'bpos': [0, lambda self: CONSTANTS['bottompos']],
- 'boty': [0, lambda self: CONSTANTS['bottomy']],
- 'box1': [0, lambda self: self.boxes['box1']],
- 'box': [1, lambda self, x: self._box(x)],
- 'box2': [0, lambda self: self.boxes['box2']],
- 'bullet': [1, self._prim_bullet, True],
- 'bulletlist': [1, self._prim_list, True],
- 'cartesian': [0, lambda self: self.tw.set_cartesian(True)],
- 'clean': [0, lambda self: self.prim_clear()],
- 'clearheap': [0, lambda self: self._empty_heap()],
- 'color': [0, lambda self: self.tw.canvas.color],
- 'gray': [0, lambda self: self.tw.canvas.gray],
- 'comment': [1, lambda self, x: self._prim_print(x, True)],
- 'container': [1, lambda self, x: x],
- 'cyan': [0, lambda self: CONSTANTS['cyan']],
- 'define': [2, self._prim_define],
- 'division': [2, lambda self, x, y: _careful_divide(x, y)],
- 'equal?': [2, lambda self, x, y: _equal(x, y)],
- 'fillscreen': [2, lambda self, x, y: self.tw.canvas.fillscreen(x, y)],
- 'forever': [1, self._prim_forever, True],
- 'forward': [1, lambda self, x: self._prim_move(self.tw.canvas.forward,
- x)],
- 'fullscreen': [0, lambda self: self.tw.set_fullscreen()],
- 'greater?': [2, lambda self, x, y: _more(x, y)],
- 'green': [0, lambda self: CONSTANTS['green']],
- 'heading': [0, lambda self: self.tw.canvas.heading],
- 'hideblocks': [0, lambda self: self.tw.hideblocks()],
- 'hres': [0, lambda self: CONSTANTS['width']],
- 'id': [1, lambda self, x: _identity(x)],
- 'if': [2, self._prim_if, True],
- 'ifelse': [3, self._prim_ifelse, True],
- 'insertimage': [1, lambda self, x: self._insert_image(False,
- filepath=x)],
- 'kbinput': [0, lambda self: self._prim_kbinput()],
- 'keyboard': [0, lambda self: self.keyboard],
- 'left': [1, lambda self, x: self._prim_right(-x)],
- 'leftx': [0, lambda self: CONSTANTS['leftx']],
- 'lpos': [0, lambda self: CONSTANTS['leftpos']],
- 'less?': [2, lambda self, x, y: _less(x, y)],
- 'luminance': [0, lambda self: self._read_camera(True)],
- 'mediawait': [0, self._media_wait, True],
- 'minus': [2, lambda self, x, y: _minus(x, y)],
- 'mod': [2, lambda self, x, y: _mod(x, y)],
- 'myfunction': [2, lambda self, f, x: self._myfunction(f, [x])],
- 'myfunction2': [3, lambda self, f, x, y: self._myfunction(f, [x, y])],
- 'myfunction3': [4, lambda self, f, x, y, z: self._myfunction(
- f, [x, y, z])],
- 'nop': [0, lambda self: None],
- 'nop1': [0, lambda self: None],
- 'nop2': [0, lambda self: None],
- 'nop3': [1, lambda self, x: None],
- 'not': [1, lambda self, x: not x],
- 'orange': [0, lambda self: CONSTANTS['orange']],
- 'or': [2, lambda self, x, y: _or(x, y)],
- 'pendown': [0, lambda self: self.tw.canvas.setpen(True)],
- 'pensize': [0, lambda self: self.tw.canvas.pensize],
- 'penup': [0, lambda self: self.tw.canvas.setpen(False)],
- 'pitch': [0, lambda self: self._get_pitch()],
- 'plus': [2, lambda self, x, y: _plus(x, y)],
- 'polar': [0, lambda self: self.tw.set_polar(True)],
- 'pop': [0, lambda self: self._prim_pop()],
- 'print': [1, lambda self, x: self._prim_print(x, False)],
- 'printheap': [0, lambda self: self._prim_print_heap()],
- 'product': [2, lambda self, x, y: _product(x, y)],
- 'purple': [0, lambda self: CONSTANTS['purple']],
- 'push': [1, lambda self, x: self._prim_push(x)],
- 'random': [2, lambda self, x, y: _random(x, y)],
- 'readcamera': [0, lambda self: self._read_camera()],
- 'readpixel': [0, lambda self: self._read_pixel()],
- 'red': [0, lambda self: CONSTANTS['red']],
- 'repeat': [2, self._prim_repeat, True],
- 'resistance': [0, lambda self: self._get_resistance()],
- 'rfid': [0, lambda self: self.tw.rfid_idn],
- 'right': [1, lambda self, x: self._prim_right(x)],
- 'rightx': [0, lambda self: CONSTANTS['rightx']],
- 'rpos': [0, lambda self: CONSTANTS['rightpos']],
- 'savepix': [1, lambda self, x: self._save_picture(x)],
- 'savesvg': [1, lambda self, x: self._save_svg(x)],
- 'scale': [0, lambda self: self.scale],
- 'see': [0, lambda self: self.see()],
- 'setcolor': [1, lambda self, x: self._prim_set('color',
- self.tw.canvas.setcolor, x)],
- 'setgray': [1, lambda self, x: self._prim_set('gray',
- self.tw.canvas.setgray, x)],
- 'seth': [1, lambda self, x: self._prim_set('heading',
- self.tw.canvas.seth, x)],
- 'setpensize': [1, lambda self, x: self._prim_set('pensize',
- self.tw.canvas.setpensize, x)],
- 'setscale': [1, lambda self, x: self._prim_set('scale',
- self._set_scale, x)],
- 'setshade': [1, lambda self, x: self._prim_set('shade',
- self.tw.canvas.setshade, x)],
- 'settextcolor': [1, lambda self, x: self.tw.canvas.settextcolor(x)],
- 'settextsize': [1, lambda self, x: self.tw.canvas.settextsize(x)],
- 'setxy2': [2, lambda self, x, y: self._prim_move(self.tw.canvas.setxy,
- x, y)],
- 'setxy': [2, lambda self, x, y: self._prim_move(self.tw.canvas.setxy,
- x, y, pendown=False)],
- 'shade': [0, lambda self: self.tw.canvas.shade],
- 'show': [1, lambda self, x: self._show(x, True)],
- 'showaligned': [1, lambda self, x: self._show(x, False)],
- 'showblocks': [0, lambda self: self.tw.showblocks()],
- 'skin': [1, lambda self, x: self._reskin(x)],
- 'sound': [0, lambda self: self._get_sound()],
- 'sqrt': [1, lambda self, x: _sqrt(x)],
- 'stack1': [0, self._prim_stack1, True],
- 'stack': [1, self._prim_stack, True],
- 'stack2': [0, self._prim_stack2, True],
- 'start': [0, lambda self: self._prim_start()],
- 'startfill': [0, lambda self: self.tw.canvas.start_fill()],
- 'stopfill': [0, lambda self: self.tw.canvas.stop_fill()],
- 'stopstack': [0, lambda self: self._prim_stopstack()],
- 'storeinbox1': [1, lambda self, x: self._prim_setbox('box1', None, x)],
- 'storeinbox2': [1, lambda self, x: self._prim_setbox('box2', None, x)],
- 'storeinbox': [2, lambda self, x, y: self._prim_setbox('box3', x, y)],
- 't1x1': [2, lambda self, x, y: self._show_template1x1(x, y)],
- 't1x1a': [2, lambda self, x, y: self._show_template1x1a(x, y)],
- 't1x2': [3, lambda self, x, y, z: self._show_template1x2(x, y, z)],
- 't2x1': [3, lambda self, x, y, z: self._show_template2x1(x, y, z)],
- 't2x2': [5, lambda self, x, y, z, a, b: self._show_template2x2(
- x, y, z, a, b)],
- 'textcolor': [0, lambda self: self.tw.canvas.textcolor],
- 'textsize': [0, lambda self: self.tw.textsize],
- 'titlex': [0, lambda self: CONSTANTS['titlex']],
- 'titley': [0, lambda self: CONSTANTS['titley']],
- 'topy': [0, lambda self: CONSTANTS['topy']],
- 'tpos': [0, lambda self: CONSTANTS['toppos']],
- 'turtle': [1, lambda self, x: self.tw.canvas.set_turtle(x)],
- 'userdefined': [1, lambda self, x: self._prim_myblock([x])],
- 'userdefined2': [2, lambda self, x, y: self._prim_myblock([x, y])],
- 'userdefined3': [3, lambda self, x, y,
- z: self._prim_myblock([x, y, z])],
- 'video': [1, lambda self, x: self._play_video(x)],
- 'voltage': [0, lambda self: self._get_voltage()],
- 'volume': [0, lambda self: self._get_volume()],
- 'vres': [0, lambda self: CONSTANTS['height']],
- 'wait': [1, self._prim_wait, True],
- 'white': [0, lambda self: WHITE],
- 'write': [2, lambda self, x, y: self._write(self, x, y)],
- 'xcor': [0, lambda self: self.tw.canvas.xcor / self.tw.coord_scale],
- 'ycor': [0, lambda self: self.tw.canvas.ycor / self.tw.coord_scale],
- 'yellow': [0, lambda self: CONSTANTS['yellow']]}
+ 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])
+ 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.def_prim(p, DEFPRIM[p][0], DEFPRIM[p][1], DEFPRIM[p][2])
self.symtype = type(self._intern('print'))
self.listtype = type([])
@@ -520,38 +121,26 @@ class LogoCode:
self.trace = 0
self.update_values = False
self.gplay = None
- self.ag = None
self.filepath = None
self.dsobject = None
+ self.start_time = None
- # Scale factors for depreciated portfolio blocks
- self.title_height = int((self.tw.canvas.height / 20) * self.tw.scale)
self.body_height = int((self.tw.canvas.height / 40) * self.tw.scale)
- self.bullet_height = int((self.tw.canvas.height / 30) * self.tw.scale)
self.scale = DEFAULT_SCALE
- self.max_samples = 1500
- self.input_step = 1
-
- self.ringbuffer = RingBuffer1d(self.max_samples, dtype='int16')
- if self.tw.hw == XO1:
- self.voltage_gain = 0.00002225
- self.voltage_bias = 1.140
- elif self.tw.hw == XO15:
- self.voltage_gain = -0.0001471
- self.voltage_bias = 1.695
-
- if self.tw.camera_available:
- self._video_capture_device = None
- if self.tw.running_sugar:
- self.imagepath = get_path(self.tw.activity,
- 'data/turtlepic.png')
- else:
- self.imagepath = '/tmp/turtlepic.png'
- self.camera = Camera(self.imagepath)
+ 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):
+ 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
@@ -573,8 +162,6 @@ class LogoCode:
self.stacks['stack2'] = None
self.tw.saving_svg = False
- self.find_value_blocks()
- self._update_audio_mode()
if self.trace > 0:
self.update_values = True
else:
@@ -592,7 +179,11 @@ class LogoCode:
if b.connections is not None and len(b.connections) > 1 and \
b.connections[1] is not None:
code = self._blocks_to_code(b)
- x = b.connections[1].values[0]
+ try:
+ x = b.connections[1].values[0]
+ except IndexError:
+ self.tw.showlabel('#nostack')
+ return None
if type(convert(x, float, False)) == float:
if int(float(x)) == x:
x = int(x)
@@ -600,7 +191,8 @@ class LogoCode:
code = self._blocks_to_code(blk)
if run_flag:
- _logger.debug("running code: %s" % (code))
+ # debug_output("running code: %s" % (code), self.tw.running_sugar)
+ self.start_time = time()
self._setup_cmd(code)
if not self.tw.hide:
self.tw.display_coordinates()
@@ -625,35 +217,22 @@ class LogoCode:
code.append(float(blk.values[0]))
except ValueError:
code.append(float(ord(blk.values[0][0])))
- elif blk.name == 'string' or blk.name == 'title':
+ 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 == 'journal':
- if blk.values[0] is not None:
- code.append('#smedia_' + str(blk.values[0]))
- else:
- code.append('#smedia_None')
- elif blk.name == 'description':
+ elif blk.name in PREFIX_DICTIONARY:
if blk.values[0] is not None:
- code.append('#sdescr_' + str(blk.values[0]))
+ code.append(PREFIX_DICTIONARY[blk.name] + \
+ str(blk.values[0]))
else:
- code.append('#sdescr_None')
- elif blk.name == 'audio':
- if blk.values[0] is not None:
- code.append('#saudio_' + str(blk.values[0]))
- else:
- code.append('#saudio_None')
- elif blk.name == 'video':
- if blk.values[0] is not None:
- code.append('#svideo_' + str(blk.values[0]))
- else:
- code.append('#svideo_None')
- elif blk.name == 'camera':
- code.append('#smedia_CAMERA')
+ code.append(PREFIX_DICTIONARY[blk.name] + 'None')
+ elif blk.name in media_blocks_dictionary:
+ code.append('#smedia_' + blk.name.upper())
else:
return ['%nothing%']
else:
@@ -718,7 +297,7 @@ class LogoCode:
elif self.tw.interactive_mode:
self.tw.toolbar_shapes['stopiton'].set_layer(TAB_LAYER)
self.running = True
- self._icall(self._evline, blklist)
+ self.icall(self.evline, blklist)
yield True
if self.tw.running_sugar:
self.tw.activity.stop_turtle_button.set_icon("stopitoff")
@@ -727,12 +306,12 @@ class LogoCode:
yield False
self.running = False
- def _icall(self, fcn, *args):
+ 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):
+ def evline(self, blklist):
""" Evaluate a line of code from the list. """
oldiline = self.iline
self.iline = blklist[:]
@@ -762,7 +341,7 @@ class LogoCode:
(token, self.bindex) = self.iline[1]
# Process the token and any arguments.
- self._icall(self._eval)
+ self.icall(self._eval)
yield True
# Time to unhighlight the current block.
@@ -778,7 +357,7 @@ class LogoCode:
self.tw.block_list.list[self.bindex].highlight()
raise logoerror(str(self.iresult))
self.iline = oldiline
- self._ireturn()
+ self.ireturn()
if not self.tw.hide and self.tw.step_time > 0:
self.tw.display_coordinates()
yield True
@@ -795,7 +374,7 @@ class LogoCode:
# 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)
+ self.icall(self._evalsym, token)
yield True
# and unhighlight if everything was OK.
if not self.tw.hide and bindex is not None:
@@ -804,7 +383,7 @@ class LogoCode:
else:
res = token
- self._ireturn(res)
+ self.ireturn(res)
yield True
def _evalsym(self, token):
@@ -817,32 +396,31 @@ class LogoCode:
raise logoerror("#noinput")
for i in range(token.nargs):
self._no_args_check()
- self._icall(self._eval)
+ self.icall(self._eval)
yield True
self.arglist.append(self.iresult)
if self.cfun.rprim:
if type(self.cfun.fcn) == self.listtype:
- _logger.debug("evalsym rprim list: %s" % (str(token)))
- self._icall(self._ufuncall, self.cfun.fcn)
+ # debug_output('evalsym rprim list: %s' % (str(token)),
+ # self.tw.running_sugar)
+ self.icall(self._ufuncall, self.cfun.fcn)
yield True
else:
- # print "evalsym rprim: ", token
- self._icall(self.cfun.fcn, *self.arglist)
+ self.icall(self.cfun.fcn, *self.arglist)
yield True
result = None
else:
- # print "evalsym: ", token
result = self.cfun.fcn(self, *self.arglist)
self.cfun, self.arglist = oldcfun, oldarglist
if self.arglist is not None and result == None:
raise logoerror("%s %s %s" % \
(oldcfun.name, _("did not output to"), self.cfun.name))
- self._ireturn(result)
+ self.ireturn(result)
yield True
def _ufuncall(self, body):
""" ufuncall """
- self._ijmp(self._evline, body)
+ self.ijmp(self.evline, body)
yield True
def doevalstep(self):
@@ -869,12 +447,12 @@ class LogoCode:
return False
return True
- def _ireturn(self, res=None):
+ def ireturn(self, res=None):
""" return value """
self.step = self.istack.pop()
self.iresult = res
- def _ijmp(self, fcn, *args):
+ def ijmp(self, fcn, *args):
""" ijmp """
self.step = fcn(*(args))
@@ -898,123 +476,6 @@ class LogoCode:
# Primitives
#
- def prim_clear(self):
- """ Clear screen """
- stop_media(self)
- self.tw.canvas.clearscreen()
- self.scale = DEFAULT_SCALE
- self.tw.set_polar(False)
- self.tw.set_cartesian(False)
- self.hidden_turtle = None
- for name in VALUE_BLOCKS:
- self.update_label_value(name)
-
- def _prim_start(self):
- """ Start block: recenter """
- if self.tw.running_sugar:
- self.tw.activity.recenter()
-
- def _prim_wait(self, time):
- """ Show the turtle while we wait """
- self.tw.active_turtle.show()
- endtime = _millisecond() + time * 1000.
- while _millisecond() < endtime:
- yield True
- self.tw.active_turtle.hide()
- self._ireturn()
- yield True
-
- def _prim_repeat(self, num, blklist):
- """ Repeat list num times. """
- num = self._int(num)
- for i in range(num):
- self._icall(self._evline, blklist[:])
- yield True
- if self.procstop:
- break
- self._ireturn()
- yield True
-
- def _prim_bullet(self, blklist):
- """ Depreciated bullet-list block style """
- self._show_bullets(blklist)
- self._ireturn()
- yield True
-
- def _prim_list(self, blklist):
- """ Expandable list block """
- self._show_list(blklist)
- self._ireturn()
- yield True
-
- def _myfunction(self, f, x):
- """ Programmable block """
- try:
- y = myfunc(f, x)
- if str(y) == 'nan':
- _logger.debug("python function returned nan")
- stop_logo(self.tw)
- raise logoerror("#notanumber")
- else:
- return y
- except ZeroDivisionError:
- stop_logo(self.tw)
- raise logoerror("#zerodivide")
- except ValueError, e:
- stop_logo(self.tw)
- raise logoerror('#' + str(e))
- except SyntaxError, e:
- stop_logo(self.tw)
- raise logoerror('#' + str(e))
- except NameError, e:
- stop_logo(self.tw)
- raise logoerror('#' + str(e))
- except OverflowError:
- stop_logo(self.tw)
- raise logoerror("#overflowerror")
- except TypeError:
- stop_logo(self.tw)
- raise logoerror("#notanumber")
-
- def _prim_forever(self, blklist):
- """ Do list forever """
- while True:
- self._icall(self._evline, blklist[:])
- yield True
- if self.procstop:
- break
- self._ireturn()
- yield True
-
- '''
- def _prim_while(self, list1, list2):
- list = [self._intern('if')]
- for i in list1:
- list.append(i)
- list.append(list2)
- while self._icall(self._evline, list[:]):
- yield True
- self._ireturn()
- yield True
- '''
-
- def _prim_if(self, boolean, blklist):
- """ If bool, do list """
- if boolean:
- self._icall(self._evline, blklist[:])
- yield True
- self._ireturn()
- yield True
-
- def _prim_ifelse(self, boolean, list1, list2):
- """ If bool, do list1, else do list2 """
- if boolean:
- self._ijmp(self._evline, list1[:])
- yield True
- else:
- self._ijmp(self._evline, list2[:])
- yield True
-
def _prim_opar(self, val):
self.iline.pop(0)
return val
@@ -1026,49 +487,19 @@ class LogoCode:
name.nargs, name.fcn = 0, body
name.rprim = True
- 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.stacks or\
- self.stacks['stack3' + str(x)] is None:
- raise logoerror("#nostack")
- self._icall(self._evline, self.stacks['stack3' + str(x)][:])
- yield True
- self.procstop = False
- self._ireturn()
- yield True
-
- def _prim_stack1(self):
- """ Process Stack 1 """
- if self.stacks['stack1'] is None:
- raise logoerror("#nostack")
- self._icall(self._evline, self.stacks['stack1'][:])
- yield True
- self.procstop = False
- self._ireturn()
- yield True
-
- def _prim_stack2(self):
- """ Process Stack 2 """
- if self.stacks['stack2'] is None:
- raise logoerror("#nostack")
- self._icall(self._evline, self.stacks['stack2'][:])
- yield True
- self.procstop = False
- self._ireturn()
- yield True
-
- def _prim_stopstack(self):
- """ Stop execution of a stack """
- self.procstop = True
-
- def _prim_print_heap(self):
- """ Display contents of heap """
- self.tw.showlabel('status', str(self.heap) + ' ')
+ def prim_clear(self):
+ """ Clear screen """
+ if self.tw.gst_available:
+ from tagplay import stop_media
+ # stop_media(self) # TODO: gplay variable
+ self.tw.canvas.clearscreen()
+ self.tw.lc.scale = DEFAULT_SCALE
+ self.tw.lc.hidden_turtle = None
+ self.tw.lc.start_time = time()
+ for name in value_blocks:
+ self.tw.lc.update_label_value(name)
- def _int(self, n):
+ def int(self, n):
""" Raise an error if n doesn't convert to int. """
if type(n) == int:
return n
@@ -1080,96 +511,20 @@ class LogoCode:
raise logoerror("%s %s %s %s" \
% (self.cfun.name, _("doesn't like"), str(n), _("as input")))
- def _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.boxes['box3' + str(x)]
- except KeyError:
- raise logoerror("#emptybox")
-
- def _prim_myblock(self, x):
- """ Run Python code imported from Journal """
- if self.bindex is not None and self.bindex in self.tw.myblock:
- try:
- if len(x) == 1:
- myfunc_import(self, self.tw.myblock[self.bindex], x[0])
- else:
- myfunc_import(self, self.tw.myblock[self.bindex], x)
- except:
- raise logoerror("#syntaxerror")
-
- def _prim_print(self, n, flag):
- """ Print object n """
- if flag and (self.tw.hide or self.tw.step_time == 0):
- return
- if type(n) == str or type(n) == unicode:
- if n[0:6] == 'media_' and n[6:] != 'CAMERA':
- try:
- if self.tw.running_sugar:
- try:
- dsobject = datastore.get(n[6:])
- except:
- _logger.debug("Couldn't open %s" % (n[6:]))
- self.tw.showlabel('status', dsobject.metadata['title'])
- dsobject.destroy()
- else:
- self.tw.showlabel('status', n[6:])
- except IOError:
- self.tw.showlabel('status', n)
- else:
- self.tw.showlabel('status', n)
- elif type(n) == int:
- self.tw.showlabel('status', n)
- else:
- self.tw.showlabel('status',
- str(round_int(n)).replace('.', self.tw.decimal_point))
-
- def _prim_kbinput(self):
- """ Query keyboard """
- if len(self.tw.keypress) == 1:
- self.keyboard = ord(self.tw.keypress[0])
- else:
- try:
- self.keyboard = {'Escape': 27, 'space': 32, ' ': 32,
- 'Return': 13, \
- 'KP_Up': 2, 'KP_Down': 4, 'KP_Left': 1, \
- 'KP_Right': 3}[self.tw.keypress]
- except KeyError:
- self.keyboard = 0
- self.update_label_value('keyboard', self.keyboard)
- self.tw.keypress = ''
-
def find_value_blocks(self):
""" Find any value blocks that may need label updates """
- self.value_blocks = {}
- for name in VALUE_BLOCKS:
- self.value_blocks[name] = self.tw.block_list.get_similar_blocks(
- 'block', name)
-
- def _update_audio_mode(self):
- """ If there are sensor blocks, set the appropriate audio mode """
- for name in ['sound', 'volume', 'pitch']:
- if len(self.value_blocks[name]) > 0:
- self.tw.audiograb.set_sensor_type()
- return
- if len(self.value_blocks['resistance']) > 0:
- self.tw.audiograb.set_sensor_type(SENSOR_DC_BIAS)
- return
- elif len(self.value_blocks['voltage']) > 0:
- self.tw.audiograb.set_sensor_type(SENSOR_DC_NO_BIAS)
- return
+ 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 self.tw.hide or not self.tw.interactive_mode or \
- not hasattr(self, 'value_blocks'):
+ if self.tw.hide or not self.tw.interactive_mode:
return
if value is None:
- for block in self.value_blocks[name]:
- block.spr.set_label(BLOCK_NAMES[name][0])
+ 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:
@@ -1177,70 +532,10 @@ class LogoCode:
self.tw.decimal_point)
else:
valstring = str(value)
- for block in self.value_blocks[name]:
- block.spr.set_label(BLOCK_NAMES[name][0] + ' = ' + valstring)
+ for block in self.value_blocks_to_update[name]:
+ block.spr.set_label(block_names[name][0] + ' = ' + valstring)
block.resize()
- 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.update_label_value(name, value)
-
- def _prim_right(self, value):
- self.tw.canvas.right(float(value))
- self.update_label_value('heading', self.tw.canvas.heading)
-
- def _prim_move(self, cmd, value1, value2=None, pendown=True):
- if value2 is None:
- cmd(value1)
- else:
- cmd(float(value1), float(value2), pendown=pendown)
- self.update_label_value('xcor',
- self.tw.canvas.xcor / self.tw.coord_scale)
- self.update_label_value('ycor',
- self.tw.canvas.ycor / self.tw.coord_scale)
- if len(self.value_blocks['see']) > 0:
- self.see()
-
- def _prim_arc(self, cmd, value1, value2):
- cmd(float(value1), float(value2))
- self.update_label_value('xcor',
- self.tw.canvas.xcor / self.tw.coord_scale)
- self.update_label_value('ycor',
- self.tw.canvas.ycor / self.tw.coord_scale)
- self.update_label_value('heading', self.tw.canvas.heading)
- if len(self.value_blocks['see']) > 0:
- self.see()
-
- 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.boxes[name + str(x)] = val
- return
-
- self.boxes[name] = val
- self.update_label_value(name, val)
-
- def _prim_push(self, val):
- """ Push value onto FILO """
- self.heap.append(val)
- self.update_label_value('pop', val)
-
- def _prim_pop(self):
- """ Pop value off of FILO """
- if len(self.heap) == 0:
- raise logoerror("#emptyheap")
- else:
- if len(self.heap) == 1:
- self.update_label_value('pop')
- else:
- self.update_label_value('pop', self.heap[-2])
- return self.heap.pop(-1)
-
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)
@@ -1249,188 +544,59 @@ class LogoCode:
self.heap.append(val)
self.update_label_value('pop', self.heap[-1])
- def _empty_heap(self):
- """ Empty FILO """
- self.heap = []
-
- def _save_picture(self, name):
- """ Save canvas to file as PNG """
- self.tw.save_as_image(name)
-
- def _save_svg(self, name):
- """ Save SVG to file """
- self.tw.canvas.svg_close()
- self.tw.save_as_image(name, True)
-
- def _show_list(self, sarray):
- """ Display list of media objects """
- x = self.tw.canvas.xcor / self.tw.coord_scale
- y = self.tw.canvas.ycor / self.tw.coord_scale
- for s in sarray:
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(s)
- y -= int(self.tw.canvas.textsize * self.tw.lead)
-
- def _set_scale(self, x):
- """ Set scale used by media object display """
- self.scale = x
-
- def _reskin(self, media):
- """ Reskin the turtle with an image from a file """
- scale = int(ICON_SIZE * float(self.scale) / DEFAULT_SCALE)
- if scale < 1:
- return
- self.filepath = None
- dsobject = None
- if os.path.exists(media[6:]): # is it a path?
- self.filepath = media[6:]
- elif self.tw.running_sugar: # is it a datastore object?
- try:
- dsobject = datastore.get(media[6:])
- except:
- _logger.debug("Couldn't open skin %s" % (media[6:]))
- if dsobject is not None:
- self.filepath = dsobject.file_path
- if self.filepath == None:
- self.tw.showlabel('nojournal', self.filepath)
- return
- pixbuf = None
- try:
- pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.filepath, scale,
- scale)
- except:
- self.tw.showlabel('nojournal', self.filepath)
- _logger.debug("Couldn't open skin %s" % (self.filepath))
- if pixbuf is not None:
- self.tw.active_turtle.set_shapes([pixbuf])
- pen_state = self.tw.active_turtle.get_pen_state()
- if pen_state:
- self.tw.canvas.setpen(False)
- self.tw.canvas.forward(0)
- if pen_state:
- self.tw.canvas.setpen(True)
-
- def _x(self):
+ def x2tx(self):
""" Convert screen coordinates to turtle coordinates """
return int(self.tw.canvas.width / 2) + int(self.tw.canvas.xcor)
- def _y(self):
+ def y2ty(self):
""" Convert screen coordinates to turtle coordinates """
return int(self.tw.canvas.height / 2) - int(self.tw.canvas.ycor)
- def _w(self):
- """ Convert screen coordinates to turtle coordinates """
+ def wpercent(self):
+ """ width as a percentage of screen coordinates """
return int((self.tw.canvas.width * self.scale) / 100.)
- def _h(self):
- """ Convert screen coordinates to turtle coordinates """
+ def hpercent(self):
+ """ height as a percentage of screen coordinates """
return int((self.tw.canvas.height * self.scale) / 100.)
- def _show(self, string, center=False):
- """ Show is the general-purpose media-rendering block. """
- if type(string) == str or type(string) == unicode:
- if string in ['media_', 'descr_', 'audio_', 'video_',
- 'media_None', 'descr_None', 'audio_None',
- 'video_None']:
- pass
- elif string[0:6] in ['media_', 'descr_', 'audio_', 'video_']:
- self.filepath = None
- self.dsobject = None
- if string[6:] == 'CAMERA':
- if self.tw.camera_available:
- self.camera.save_camera_input_to_file()
- self.camera.stop_camera_input()
- self.filepath = self.imagepath
- elif os.path.exists(string[6:]): # is it a path?
- self.filepath = string[6:]
- elif self.tw.running_sugar: # is it a datastore object?
- try:
- self.dsobject = datastore.get(string[6:])
- except:
- _logger.debug("Couldn't find dsobject %s" % (
- string[6:]))
- if self.dsobject is not None:
- self.filepath = self.dsobject.file_path
- if self.filepath == None:
- if self.dsobject is not None:
- self.tw.showlabel('nojournal',
- self.dsobject.metadata['title'])
- else:
- self.tw.showlabel('nojournal', string[6:])
- _logger.debug("Couldn't open %s" % (string[6:]))
- elif string[0:6] == 'media_':
- self._insert_image(center)
- elif string[0:6] == 'descr_':
- mimetype = None
- if self.dsobject is not None and \
- 'mime_type' in self.dsobject.metadata:
- mimetype = self.dsobject.metadata['mime_type']
- description = None
- if self.dsobject is not None and \
- 'description' in self.dsobject.metadata:
- description = self.dsobject.metadata['description']
- self._insert_desc(mimetype, description)
- elif string[0:6] == 'audio_':
- self._play_sound()
- elif string[0:6] == 'video_':
- self._play_video()
- if self.dsobject is not None:
- self.dsobject.destroy()
- else: # assume it is text to display
- x = self._x()
- y = self._y()
- if center:
- y -= self.tw.canvas.textsize
- self.tw.canvas.draw_text(string, x, y,
- int(self.tw.canvas.textsize * \
- self.scale / 100.),
- self.tw.canvas.width - x)
- elif type(string) == float or type(string) == int:
- string = round_int(string)
- x = self._x()
- y = self._y()
- if center:
- y -= self.tw.canvas.textsize
- self.tw.canvas.draw_text(string, x, y,
- int(self.tw.canvas.textsize * \
- self.scale / 100.),
- self.tw.canvas.width - x)
-
- def _insert_image(self, center=False, filepath=None):
+ def insert_image(self, center=False, filepath=None):
""" Image only (at current x, y) """
if filepath is not None:
self.filepath = filepath
pixbuf = None
- w = self._w()
- h = self._h()
+ w, h = self.wpercent(), self.hpercent()
if w < 1 or h < 1:
return
- if self.filepath is not None and self.filepath != '':
+ if self.dsobject is not None:
+ try:
+ pixbuf = get_pixbuf_from_journal(self.dsobject, w, h)
+ except:
+ debug_output("Couldn't open dsobject %s" % (self.dsobject),
+ self.tw.running_sugar)
+ if pixbuf is None and \
+ self.filepath is not None and \
+ self.filepath != '':
try:
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.filepath,
w, h)
except:
self.tw.showlabel('nojournal', self.filepath)
- _logger.debug("Couldn't open filepath %s" % (self.filepath))
- elif self.dsobject is not None:
- try:
- pixbuf = get_pixbuf_from_journal(self.dsobject, w, h)
- except:
- self.tw.showlabel('nojournal', self.dsobject)
- _logger.debug("Couldn't open dsobject %s" % (self.dsobject))
+ debug_output("Couldn't open filepath %s" % (self.filepath),
+ self.tw.running_sugar)
if pixbuf is not None:
if center:
self.tw.canvas.draw_pixbuf(pixbuf, 0, 0,
- self._x() - int(w / 2),
- self._y() - int(h / 2), w, h,
+ self.x2tx() - int(w / 2),
+ self.y2ty() - int(h / 2), w, h,
self.filepath)
else:
- self.tw.canvas.draw_pixbuf(pixbuf, 0, 0, self._x(), self._y(),
- w, h, self.filepath)
+ self.tw.canvas.draw_pixbuf(pixbuf, 0, 0, self.x2tx(),
+ self.y2ty(), w, h, self.filepath)
- def _insert_desc(self, mimetype=None, description=None):
+ def insert_desc(self, mimetype=None, description=None):
""" Description text only (at current x, y) """
- w = self._w()
+ w = self.wpercent()
if w < 1:
return
text = None
@@ -1448,378 +614,38 @@ class LogoCode:
f.close()
except IOError:
self.tw.showlabel('nojournal', self.filepath)
- _logger.debug("Couldn't open filepath %s" % \
- (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._x(), self._y(),
+ self.tw.canvas.draw_text(text, self.x2tx(), self.y2ty(),
self.body_height, w)
- def _media_wait(self):
+ def media_wait(self):
""" Wait for media to stop playing """
- while(media_playing(self)):
- yield True
- self._ireturn()
+ if self.tw.gst_available:
+ from tagplay import media_playing
+ while(media_playing(self)):
+ yield True
+ self.ireturn()
yield True
- def _play_sound(self):
+ def play_sound(self):
""" Sound file from Journal """
- play_audio_from_file(self, self.filepath)
+ if self.tw.gst_available:
+ from tagplay import play_audio_from_file
+ play_audio_from_file(self, self.filepath)
- def _play_video(self):
+ def play_video(self):
""" Movie file from Journal """
- w = self._w()
- h = self._h()
+ w, h = self.wpercent(), self.hpercent()
if w < 1 or h < 1:
return
- play_movie_from_file(self, self.filepath, self._x(), self._y(),
- self._w(), self._h())
-
- def see(self):
- """ Read r, g, b from the canvas and return a corresponding palette
- color """
- r, g, b, a = self.tw.canvas.get_pixel()
- color_index = self.tw.canvas.get_color_index(r, g, b)
- self.update_label_value('see', color_index)
- return color_index
-
- def _read_pixel(self):
- """ Read r, g, b, a from the canvas and push b, g, r to the stack """
- r, g, b, a = self.tw.canvas.get_pixel()
- self.heap.append(b)
- self.heap.append(g)
- self.heap.append(r)
-
- def _read_camera(self, luminance_only=False):
- """ Read average pixel from camera and push b, g, r to the stack """
-
- if not self.tw.camera_available:
- if not luminance_only:
- self.heap.append(-1)
- self.heap.append(-1)
- self.heap.append(-1)
- return -1
-
- pixbuf = None
- array = None
- w = self._w()
- if w < 1:
- w = 1
- h = self._h()
- if h < 1:
- h = 1
-
- self._video_capture_device = None
- try:
- self._video_capture_device = open('/dev/video0', 'rw')
- except:
- _logger.debug('video capture device not available')
- if self._video_capture_device is not None:
- self._set_ag(0) # disable autogain
-
- self.camera.save_camera_input_to_file()
- self.camera.stop_camera_input()
-
- if self._video_capture_device is not None:
- self._set_ag(1) # restore autogain and close device
- self._video_capture_device.close()
- self._video_capture_device = None
-
- pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.imagepath, w, h)
- array = pixbuf.get_pixels()
-
- array_length = len(array) / 3
- r = 0
- g = 0
- b = 0
- i = 0
- for j in range(array_length):
- r += ord(array[i])
- i += 1
- g += ord(array[i])
- i += 1
- b += ord(array[i])
- i += 1
- if luminance_only:
- lum = int((r * 0.3 + g * 0.6 + b * 0.1) / array_length)
- self.update_label_value('luminance', lum)
- return lum
- else:
- self.heap.append(int((b / array_length)))
- self.heap.append(int((g / array_length)))
- self.heap.append(int((r / array_length)))
-
- def _set_ag(self, value):
- """ set camera autogain (0==off, 1==on) """
- self._ag_control = v4l2.v4l2_control(v4l2.V4L2_CID_AUTOGAIN)
- try:
- ioctl(self._video_capture_device, v4l2.VIDIOC_G_CTRL,
- self._ag_control)
- self._ag_control.value = value
- ioctl(self._video_capture_device, v4l2.VIDIOC_S_CTRL,
- self._ag_control)
- ioctl(self._video_capture_device, v4l2.VIDIOC_G_CTRL,
- self._ag_control)
- except:
- _logger.debug('AUTOGAIN control not available')
-
- def _get_volume(self):
- """ return mic in value """
- #TODO: Adjust gain for different HW
- buf = self.ringbuffer.read(None, self.input_step)
- if len(buf) > 0:
- volume = float(_avg(buf, abs_value=True))
- self.update_label_value('volume', volume)
- return volume
- else:
- return 0
-
- def _get_sound(self):
- """ return raw mic in value """
- buf = self.ringbuffer.read(None, self.input_step)
- if len(buf) > 0:
- sound = float(buf[0])
- self.update_label_value('sound', sound)
- return sound
- else:
- return 0
-
- def _get_pitch(self):
- """ return index of max value in fft of mic in values """
- buf = []
- for i in range(4):
- buf = append(buf, self.ringbuffer.read(None, self.input_step))
- if len(buf) > 0:
- r = []
- for j in rfft(buf):
- r.append(abs(j))
- # Convert output to Hertz
- pitch = r.index(max(r)) * 48000 / len(buf)
- self.update_label_value('pitch', pitch)
- return pitch
- else:
- return 0
-
- def _get_resistance(self):
- """ return resistance sensor value """
- buf = self.ringbuffer.read(None, self.input_step)
- if len(buf) > 0:
- # See <http://bugs.sugarlabs.org/ticket/552#comment:7>
- # TODO: test this calibration on XO 1.5
- if self.tw.hw == XO1:
- resistance = 2.718 ** ((float(_avg(buf)) * 0.000045788) + \
- 8.0531)
- else:
- avg_buf = float(_avg(buf))
- if avg_buf > 0:
- resistance = (420000000 / avg_buf) - 13500
- else:
- resistance = 420000000
- self.update_label_value('resistance', resistance)
- return resistance
- else:
- return 0
-
- def _get_voltage(self):
- """ return voltage sensor value """
- buf = self.ringbuffer.read(None, self.input_step)
- if len(buf) > 0:
- # See <http://bugs.sugarlabs.org/ticket/552#comment:7>
- voltage = float(_avg(buf)) * self.voltage_gain + self.voltage_bias
- self.update_label_value('voltage', voltage)
- return voltage
- else:
- return 0
-
- # Depreciated block methods
-
- def _show_template1x1(self, title, media):
- """ title, one image, and description """
- xo = self.tw.calc_position('t1x1')[2]
- x = -(self.tw.canvas.width / 2) + xo
- y = self.tw.canvas.height / 2
- self.tw.canvas.setxy(x, y, pendown=False)
- # save the text size so we can restore it later
- save_text_size = self.tw.canvas.textsize
- # set title text
- self.tw.canvas.settextsize(self.title_height)
- self._show(title)
- # calculate and set scale for media blocks
- myscale = 45 * (self.tw.canvas.height - self.title_height * 2) \
- / self.tw.canvas.height
- self._set_scale(myscale)
- # set body text size
- self.tw.canvas.settextsize(self.body_height)
- # render media object
- # leave some space below the title
- y -= int(self.title_height * 2 * self.tw.lead)
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media)
- if self.tw.running_sugar:
- x = 0
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media.replace('media_', 'descr_'))
- # restore text size
- self.tw.canvas.settextsize(save_text_size)
-
- def _show_template2x1(self, title, media1, media2):
- """ title, two images (horizontal), two descriptions """
- xo = self.tw.calc_position('t2x1')[2]
- x = -(self.tw.canvas.width / 2) + xo
- y = self.tw.canvas.height / 2
- self.tw.canvas.setxy(x, y, pendown=False)
- # save the text size so we can restore it later
- save_text_size = self.tw.canvas.textsize
- # set title text
- self.tw.canvas.settextsize(self.title_height)
- self._show(title)
- # calculate and set scale for media blocks
- myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \
- self.tw.canvas.height
- self._set_scale(myscale)
- # set body text size
- self.tw.canvas.settextsize(self.body_height)
- # render four quadrents
- # leave some space below the title
- y -= int(self.title_height * 2 * self.tw.lead)
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media1)
- x = 0
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media2)
- y = -self.title_height
- if self.tw.running_sugar:
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media2.replace('media_', 'descr_'))
- x = -(self.tw.canvas.width / 2) + xo
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media1.replace('media_', 'descr_'))
- # restore text size
- self.tw.canvas.settextsize(save_text_size)
-
- def _show_bullets(self, sarray):
- """ title and varible number of bullets """
- xo = self.tw.calc_position('bullet')[2]
- x = -(self.tw.canvas.width / 2) + xo
- y = self.tw.canvas.height / 2
- self.tw.canvas.setxy(x, y, pendown=False)
- # save the text size so we can restore it later
- save_text_size = self.tw.canvas.textsize
- # set title text
- self.tw.canvas.settextsize(self.title_height)
- self._show(sarray[0])
- # set body text size
- self.tw.canvas.settextsize(self.bullet_height)
- # leave some space below the title
- y -= int(self.title_height * 2 * self.tw.lead)
- for s in sarray[1:]:
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(s)
- y -= int(self.bullet_height * 2 * self.tw.lead)
- # restore text size
- self.tw.canvas.settextsize(save_text_size)
-
- def _show_template1x2(self, title, media1, media2):
- """ title, two images (vertical), two desciptions """
- xo = self.tw.calc_position('t1x2')[2]
- x = -(self.tw.canvas.width / 2) + xo
- y = self.tw.canvas.height / 2
- self.tw.canvas.setxy(x, y, pendown=False)
- # save the text size so we can restore it later
- save_text_size = self.tw.canvas.textsize
- # set title text
- self.tw.canvas.settextsize(self.title_height)
- self._show(title)
- # calculate and set scale for media blocks
- myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \
- self.tw.canvas.height
- self._set_scale(myscale)
- # set body text size
- self.tw.canvas.settextsize(self.body_height)
- # render four quadrents
- # leave some space below the title
- y -= int(self.title_height * 2 * self.tw.lead)
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media1)
- if self.tw.running_sugar:
- x = 0
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media1.replace('media_', 'descr_'))
- y = -self.title_height
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media2.replace('media_', 'descr_'))
- x = -(self.tw.canvas.width / 2) + xo
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media2)
- # restore text size
- self.tw.canvas.settextsize(save_text_size)
-
- def _show_template2x2(self, title, media1, media2, media3, media4):
- """ title and four images """
- xo = self.tw.calc_position('t2x2')[2]
- x = -(self.tw.canvas.width / 2) + xo
- y = self.tw.canvas.height / 2
- self.tw.canvas.setxy(x, y, pendown=False)
- # save the text size so we can restore it later
- save_text_size = self.tw.canvas.textsize
- # set title text
- self.tw.canvas.settextsize(self.title_height)
- self._show(title)
- # calculate and set scale for media blocks
- myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \
- self.tw.canvas.height
- self._set_scale(myscale)
- # set body text size
- self.tw.canvas.settextsize(self.body_height)
- # render four quadrents
- # leave some space below the title
- y -= int(self.title_height * 2 * self.tw.lead)
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media1)
- x = 0
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media2)
- y = -self.title_height
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media4)
- x = -(self.tw.canvas.width / 2) + xo
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media3)
- # restore text size
- self.tw.canvas.settextsize(save_text_size)
-
- def _show_template1x1a(self, title, media1):
- """ title, one media object """
- xo = self.tw.calc_position('t1x1a')[2]
- x = -(self.tw.canvas.width / 2) + xo
- y = self.tw.canvas.height / 2
- self.tw.canvas.setxy(x, y, pendown=False)
- # save the text size so we can restore it later
- save_text_size = self.tw.canvas.textsize
- # set title text
- self.tw.canvas.settextsize(self.title_height)
- self._show(title)
- # calculate and set scale for media blocks
- myscale = 90 * (self.tw.canvas.height - self.title_height * 2) / \
- self.tw.canvas.height
- self._set_scale(myscale)
- # set body text size
- self.tw.canvas.settextsize(self.body_height)
- # render media object
- # leave some space below the title
- y -= int(self.title_height * 2 * self.tw.lead)
- self.tw.canvas.setxy(x, y, pendown=False)
- self._show(media1)
- # restore text size
- self.tw.canvas.settextsize(save_text_size)
-
- def _write(self, string, fsize):
- """ Write string at size """
- x = self.tw.canvas.width / 2 + int(self.tw.canvas.xcor)
- y = self.tw.canvas.height / 2 - int(self.tw.canvas.ycor)
- self.tw.canvas.draw_text(string, x, y - 15, int(fsize),
- self.tw.canvas.width)
+ 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)
diff --git a/TurtleArt/tapalette.py b/TurtleArt/tapalette.py
new file mode 100644
index 0000000..61fcb14
--- /dev/null
+++ b/TurtleArt/tapalette.py
@@ -0,0 +1,299 @@
+#!/usr/bin/env python
+#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.
+
+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']
+value_blocks = [] # blocks whose labels are updated get added here
+special_block_colors = {}
+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': []}
+
+from taconstants import EXPANDABLE_STYLE
+from tautils import debug_output
+
+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._help = None
+
+ 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:
+ # debug_output('Palette %s already defined' % (self._name))
+ 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):
+ self._help = help
+
+ 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):
+ """ 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 colors is not None:
+ block.set_colors(colors)
+ block.set_value_block(value_block)
+ block.set_content_block(content_block)
+ if not hidden:
+ block.set_palette(self._name)
+ block.add_block()
+
+
+def make_palette(palette_name, colors=None, help_string=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()
+ 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. '''
+ 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
+
+ def add_block(self, position=None):
+ if self._name is None:
+ debug_output('You must specify a name for your block')
+ return
+
+ 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 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
+
+ def set_colors(self, colors=None):
+ self._colors = colors
+
+ 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
index fe9166d..c9fc4c4 100755
--- a/TurtleArt/tasprite_factory.py
+++ b/TurtleArt/tasprite_factory.py
@@ -24,7 +24,6 @@ import pygtk
pygtk.require('2.0')
import gtk
import os
-from gettext import gettext as _
from taconstants import HIT_RED, HIT_GREEN, HIDE_WHITE, SHOW_WHITE
@@ -80,6 +79,15 @@ class SVG:
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):
self.reset_min_max()
(x, y) = self._calculate_x_y()
@@ -334,12 +342,8 @@ class SVG:
yoffset = self._radius * 2 + 2 * self._innie_y2 + \
self._innie_spacer + self._stroke_width / 2.0 + \
self._expand_y
- if self._porch is True:
- yoffset += self._porch_y
svg = self._start_boolean(self._stroke_width / 2.0, yoffset)
yoffset = -2 * self._innie_y2 - self._innie_spacer - self._stroke_width
- if self._porch is True:
- yoffset -= self._porch_y
svg += self._rline_to(0, yoffset)
self._hide_x = self._x + self._radius / 2 + self._stroke_width
@@ -353,12 +357,13 @@ class SVG:
svg += self._do_innie()
svg += self._rline_to(0, self._expand_y)
if self._porch is True:
- svg += self._do_porch()
+ 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
@@ -369,7 +374,6 @@ class SVG:
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 turtle(self, colors):
@@ -591,7 +595,7 @@ class SVG:
def set_gradient(self, flag=False, color='#FFFFFF'):
self._gradient = flag
- self._gradient_color = color
+ self._gradient_color = color
def set_innie(self, innie_array=[False]):
self._innie = innie_array
diff --git a/TurtleArt/taturtle.py b/TurtleArt/taturtle.py
index a7a3205..0ca72e4 100644
--- a/TurtleArt/taturtle.py
+++ b/TurtleArt/taturtle.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-#Copyright (c) 2010 Walter Bender
+#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
@@ -19,14 +19,15 @@
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
-from taconstants import TURTLE_LAYER
+from random import uniform
+from math import sin, cos, pi
+from gettext import gettext as _
+
+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
-
-import logging
-_logger = logging.getLogger('turtleart-activity')
-
+from tautils import debug_output
SHAPES = 36
@@ -59,6 +60,8 @@ class Turtles:
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]
@@ -120,11 +123,25 @@ class Turtle:
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)
@@ -149,9 +166,16 @@ class Turtle:
self.shapes = generate_turtle_pixbufs(self.colors)
else:
if turtles is not None:
- self.colors = ['#008000', '#00A000']
+ 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):
""" Reskin the turtle """
n = len(shapes)
@@ -159,7 +183,8 @@ class Turtle:
self.shapes = shapes[:]
else:
if n != 1:
- _logger.debug("%d images passed to set_shapes: ignoring" % (n))
+ debug_output("%d images passed to set_shapes: ignoring" % (n),
+ self.tw.running_sugar)
images = [shapes[0]]
if self.heading == 0:
for i in range(3):
@@ -213,6 +238,8 @@ class Turtle:
""" 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):
@@ -222,14 +249,25 @@ class Turtle:
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)
diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py
index c66b322..0c6fe64 100644
--- a/TurtleArt/tautils.py
+++ b/TurtleArt/tautils.py
@@ -1,5 +1,5 @@
#Copyright (c) 2007-8, Playful Invention Company.
-#Copyright (c) 2008-10, Walter Bender
+#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
@@ -23,6 +23,8 @@ import gtk
import pickle
import subprocess
import dbus
+import os.path
+from gettext import gettext as _
try:
OLD_SUGAR_SYSTEM = False
@@ -37,17 +39,31 @@ except (ImportError, AttributeError):
from simplejson import dump as jdump
except:
OLD_SUGAR_SYSTEM = True
-
-from taconstants import STRING_OR_NUMBER_ARGS, HIDE_LAYER, CONTENT_ARGS, \
- COLLAPSIBLE, BLOCK_LAYER, CONTENT_BLOCKS, HIT_HIDE, \
- HIT_SHOW, XO1, XO15, UNKNOWN
from StringIO import StringIO
-import os.path
-from gettext import gettext as _
+
+from taconstants import HIDE_LAYER, COLLAPSIBLE, BLOCK_LAYER, HIT_HIDE, \
+ HIT_SHOW, XO1, XO15, UNKNOWN
+
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):
@@ -105,15 +121,22 @@ def json_load(text):
if OLD_SUGAR_SYSTEM is True:
_listdata = json.read(text)
else:
- # strip out leading and trailing whitespace, nulls, and newlines
+ # 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', '')
- _io = StringIO(clean_text.rstrip())
+ 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
+ # Assume that text is ascii list
_listdata = text.split()
for i, value in enumerate(_listdata):
_listdata[i] = convert(value, float)
@@ -147,7 +170,7 @@ def json_dump(data):
def get_load_name(suffix, load_save_folder):
""" Open a load file dialog. """
- _dialog = gtk.FileChooserDialog("Load...", None,
+ _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)
@@ -156,7 +179,7 @@ def get_load_name(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,
+ _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)
@@ -285,24 +308,36 @@ def get_path(activity, subpath):
"org.laptop.TurtleArtActivity", subpath))
-def image_to_base64(pixbuf, activity):
- """ Convert an image to base64 """
- _file_name = os.path.join(get_path(activity, 'instance'), 'imagetmp.png')
+def image_to_base64(pixbuf, path_name):
+ """ Convert an image to base64-encoded data """
+ file_name = os.path.join(path_name, 'imagetmp.png')
if pixbuf != None:
- pixbuf.save(_file_name, "png")
- _base64 = os.path.join(get_path(activity, 'instance'), 'base64tmp')
- _cmd = "base64 <" + _file_name + " >" + _base64
- subprocess.check_call(_cmd, shell=True)
- _file_handle = open(_base64, 'r')
- _data = _file_handle.read()
- _file_handle.close()
- return _data
+ pixbuf.save(file_name, "png")
+ base64 = os.path.join(path_name, 'base64tmp')
+ cmd = "base64 <" + file_name + " >" + base64
+ subprocess.check_call(cmd, shell=True)
+ file_handle = open(base64, 'r')
+ data = file_handle.read()
+ file_handle.close()
+ 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'))
+ '.mpeg', '.ogg', '.webm'))
def audio_media_type(name):
@@ -318,7 +353,7 @@ def image_media_type(name):
def text_media_type(name):
""" Is it text media? """
- return name.lower().endswith(('.txt', '.py', '.lg', '.rtf', '.ta'))
+ return name.lower().endswith(('.txt', '.py', '.lg', '.rtf'))
def round_int(num):
@@ -326,7 +361,6 @@ def round_int(num):
try:
float(num)
except TypeError:
- _logger.debug("error trying to convert %s to number" % (str(num)))
raise pythonerror("#syntaxerror")
if int(float(num)) == num:
@@ -613,39 +647,6 @@ def neg_arg(value):
return False
-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))
-
-
def journal_check(blk1, blk2, dock1, dock2):
""" Dock blocks only if arg is Journal block """
if blk1 == None or blk2 == None:
@@ -796,22 +797,29 @@ def find_blk_below(blk, name):
def get_hardware():
""" Determine whether we are using XO 1.0, 1.5, or "unknown" hardware """
- bus = dbus.SystemBus()
-
- comp_obj = bus.get_object('org.freedesktop.Hal',
- '/org/freedesktop/Hal/devices/computer')
- dev = dbus.Interface(comp_obj, 'org.freedesktop.Hal.Device')
- if dev.PropertyExists('system.hardware.vendor') and \
- dev.PropertyExists('system.hardware.version'):
- if dev.GetProperty('system.hardware.vendor') == 'OLPC':
- if dev.GetProperty('system.hardware.version') == '1.5':
- return XO15
- else:
- return XO1
+ product = _get_dmi('product_name')
+ if product is None:
+ if os.path.exists('/etc/olpc-release') or \
+ os.path.exists('/sys/power/olpc-pm'):
+ return XO1
else:
return UNKNOWN
- elif os.path.exists('/etc/olpc-release') or \
- os.path.exists('/sys/power/olpc-pm'):
+ if product != 'XO':
+ return UNKNOWN
+ version = _get_dmi('product_version')
+ if version == '1':
return XO1
+ elif version == '1.5':
+ return XO15
else:
return UNKNOWN
+
+
+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
index ad830d9..64a32fd 100644
--- a/TurtleArt/tawindow.py
+++ b/TurtleArt/tawindow.py
@@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-
#Copyright (c) 2007, Playful Invention Company
-#Copyright (c) 2008-10, Walter Bender
-#Copyright (c) 2009-10 Raúl Gutiérrez Segalés
-#Copyright (C) 2010 Emiliano Pastorino <epastorino@plan.ceibal.edu.uy>
+#Copyright (c) 2008-11, 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
@@ -27,7 +26,15 @@ import pygtk
pygtk.require('2.0')
import gtk
import gobject
-import gst
+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 os.path
import dbus
@@ -36,7 +43,6 @@ from math import atan2, pi
DEGTOR = 2 * pi / 360
import locale
-from gettext import gettext as _
try:
from sugar.datastore import datastore
@@ -45,84 +51,74 @@ except ImportError:
pass
from taconstants import HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE, \
- PALETTE_NAMES, TITLEXY, MEDIA_SHAPES, STATUS_SHAPES, \
- OVERLAY_SHAPES, TOOLBAR_SHAPES, TAB_LAYER, RETURN, \
- OVERLAY_LAYER, CATEGORY_LAYER, BLOCKS_WITH_SKIN, \
- ICON_SIZE, PALETTES, PALETTE_SCALE, BOX_STYLE_MEDIA, \
- PALETTE_WIDTH, MACROS, TOP_LAYER, BLOCK_LAYER, \
- CONTENT_BLOCKS, DEFAULTS, SPECIAL_NAMES, \
- HELP_STRINGS, CURSOR, EXPANDABLE, COLLAPSIBLE, \
- DEAD_DICTS, DEAD_KEYS, TEMPLATES, PYTHON_SKIN, \
- PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK, OLD_NAMES, \
- BOOLEAN_STYLE, BLOCK_NAMES, DEFAULT_TURTLE, \
- TURTLE_LAYER, EXPANDABLE_BLOCKS, COMPARE_STYLE, \
- BOOLEAN_STYLE, EXPANDABLE_ARGS, NUMBER_STYLE, \
- NUMBER_STYLE_PORCH, NUMBER_STYLE_BLOCK, \
- NUMBER_STYLE_VAR_ARG, CONSTANTS, XO1, XO15, UNKNOWN, \
- BASIC_STYLE_VAR_ARG
-from talogo import LogoCode, stop_logo
+ MEDIA_SHAPES, STATUS_SHAPES, OVERLAY_SHAPES, STRING_OR_NUMBER_ARGS, \
+ TOOLBAR_SHAPES, TAB_LAYER, RETURN, OVERLAY_LAYER, CATEGORY_LAYER, \
+ BLOCKS_WITH_SKIN, ICON_SIZE, PALETTE_SCALE, PALETTE_WIDTH, \
+ MACROS, TOP_LAYER, BLOCK_LAYER, OLD_NAMES, DEFAULT_TURTLE, TURTLE_LAYER, \
+ CURSOR, EXPANDABLE, COLLAPSIBLE, DEAD_DICTS, DEAD_KEYS, \
+ TEMPLATES, PYTHON_SKIN, PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK, \
+ EXPANDABLE_ARGS, XO1, XO15, UNKNOWN, TITLEXY, CONTENT_ARGS, CONSTANTS
+from tapalette import palette_names, palette_blocks, expandable_blocks, \
+ block_names, content_blocks, default_values, special_names, block_styles, \
+ help_strings
+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, save_svg, 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, \
- arithmetic_check, xy, find_block_to_run, find_top_block, \
- find_start_stack, find_group, find_blk_below, \
- dock_dx_dy, data_to_string, journal_check, chooser, \
- get_hardware
+ data_to_file, round_int, get_id, get_pixbuf_from_journal, \
+ movie_media_type, audio_media_type, image_media_type, save_picture, \
+ save_svg, 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
from tasprite_factory import SVG, svg_str_to_pixbuf, svg_from_file
-from tagplay import stop_media
from sprites import Sprites, Sprite
-from audiograb import AudioGrab_Unknown, AudioGrab_XO1, AudioGrab_XO15
-from rfidutils import strhex2bin, strbin2dec, find_device
from dbus.mainloop.glib import DBusGMainLoop
-HAL_SERVICE = 'org.freedesktop.Hal'
-HAL_MGR_PATH = '/org/freedesktop/Hal/Manager'
-HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager'
-HAL_DEV_IFACE = 'org.freedesktop.Hal.Device'
-REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\
- 'a-z,A-Z,0-9,_]*serial_usb_[0-9]'
-
-import logging
-_logger = logging.getLogger('turtleart-activity')
+if GST_AVAILABLE:
+ from tagplay import stop_media
class TurtleArtWindow():
""" TurtleArt Window class abstraction """
timeout_tag = [0]
+ _PLUGIN_SUBPATH = 'plugins'
def __init__(self, win, path, parent=None, mycolors=None, mynick=None):
self._loaded_project = ''
- self.win = None
self._sharing = False
self.parent = parent
- self.send_event = None # method to send events over the network
+ self.send_event = None # method to send events over the network
+ self.gst_available = GST_AVAILABLE
if type(win) == gtk.DrawingArea:
self.interactive_mode = True
self.window = win
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.window.show_all()
self.running_sugar = False
self.area = self.window.window
- self.gc = self.area.new_gc()
+ if self.area is not None:
+ self.gc = self.area.new_gc()
+ else:
+ # We lose...
+ debug_output('drawable area is None... punting')
+ exit()
self._setup_events()
elif type(win) == gtk.gdk.Pixmap:
self.interactive_mode = False
self.window = win
self.running_sugar = False
- self.gc = self.window.new_gc()
+ if self.window is not None:
+ self.gc = self.window.new_gc()
else:
- _logger.debug("bad win type %s" % (type(win)))
+ debug_output("bad win type %s" % (type(win)), False)
if self.running_sugar:
self.activity = parent
@@ -147,7 +143,10 @@ class TurtleArtWindow():
self.mouse_x = 0
self.mouse_y = 0
- locale.setlocale(locale.LC_NUMERIC, '')
+ 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 = '.'
@@ -155,7 +154,6 @@ class TurtleArtWindow():
self.orientation = HORIZONTAL_PALETTE
self.hw = get_hardware()
- _logger.debug('running on %s hardware' % (self.hw))
if self.hw in (XO1, XO15):
self.lead = 1.0
self.scale = 0.67
@@ -163,14 +161,14 @@ class TurtleArtWindow():
self.color_mode = '565'
else:
self.color_mode = '888'
- if self.running_sugar and not self.activity.new_sugar_system:
+ if self.running_sugar and not self.activity.has_toolbarbox:
self.orientation = VERTICAL_PALETTE
else:
self.lead = 1.0
self.scale = 1.0
self.color_mode = '888' # TODO: Read visual mode from gtk image
- self.block_scale = BLOCK_SCALE
+ self.block_scale = BLOCK_SCALE[3]
self.trash_scale = 0.5
self.myblock = {}
self.python_code = None
@@ -187,6 +185,7 @@ class TurtleArtWindow():
self.media_shapes = {}
self.cartesian = False
self.polar = False
+ self.metric = False
self.overlay_shapes = {}
self.toolbar_shapes = {}
self.toolbar_offset = 0
@@ -196,7 +195,6 @@ class TurtleArtWindow():
self.palette_sprs = []
self.palettes = []
self.palette_button = []
- self.trash_index = PALETTE_NAMES.index('trash')
self.trash_stack = []
self.selected_palette = None
self.previous_palette = None
@@ -219,10 +217,10 @@ class TurtleArtWindow():
self.sprite_list = None
self.turtles = Turtles(self.sprite_list)
- if mynick is None:
+ if self.nick is None:
self.default_turtle_name = DEFAULT_TURTLE
else:
- self.default_turtle_name = mynick
+ self.default_turtle_name = self.nick
if mycolors is None:
Turtle(self.turtles, self.default_turtle_name)
else:
@@ -252,99 +250,100 @@ class TurtleArtWindow():
CONSTANTS['width'] = int(self.canvas.width / self.coord_scale)
CONSTANTS['height'] = int(self.canvas.height / self.coord_scale)
- if self.interactive_mode:
- self._setup_misc()
- self._show_toolbar_palette(0, False)
-
- # setup sound/sensor grab
- if self.hw in [XO1, XO15]:
- PALETTES[PALETTE_NAMES.index('sensor')].append('resistance')
- PALETTES[PALETTE_NAMES.index('sensor')].append('voltage')
- self.audio_started = False
-
- self.camera_available = False
- v4l2src = gst.element_factory_make('v4l2src')
- if v4l2src.props.device_name is not None:
- PALETTES[PALETTE_NAMES.index('sensor')].append('readcamera')
- PALETTES[PALETTE_NAMES.index('sensor')].append('luminance')
- PALETTES[PALETTE_NAMES.index('sensor')].append('camera')
- self.camera_available = True
+ self._icon_paths = [os.path.join(self.path, 'icons')]
+ self._plugins = []
+ self._init_plugins()
self.lc = LogoCode(self)
- self.saved_pictures = []
-
- self.block_operation = ''
- """
- The following code will initialize a USB RFID reader. Please note that
- in order to make this initialization function work, it is necessary to
- set the permission for the ttyUSB device to 0666. You can do this by
- adding a rule to /etc/udev/rules.d
-
- As root (using sudo or su), copy the following text into a new file in
- /etc/udev/rules.d/94-ttyUSB-rules
-
- KERNEL=="ttyUSB[0-9]",MODE="0666"
-
- You only have to do this once.
- """
-
- self.rfid_connected = False
- self.rfid_device = find_device()
- self.rfid_idn = ''
-
- if self.rfid_device is not None:
- _logger.info("RFID device found")
- self.rfid_connected = self.rfid_device.do_connect()
- if self.rfid_connected:
- self.rfid_device.connect("tag-read", self._tag_read_cb)
- self.rfid_device.connect("disconnected", self._disconnected_cb)
-
- loop = DBusGMainLoop()
- bus = dbus.SystemBus(mainloop=loop)
- hmgr_iface = dbus.Interface(bus.get_object(HAL_SERVICE,
- HAL_MGR_PATH), HAL_MGR_IFACE)
+ from tabasics import Palettes
+ p = Palettes(self)
+ self._setup_plugins()
- hmgr_iface.connect_to_signal('DeviceAdded', self._device_added_cb)
+ if self.interactive_mode:
+ self._setup_misc()
+ self.show_toolbar_palette(0, False)
- PALETTES[PALETTE_NAMES.index('sensor')].append('rfid')
+ self.saved_pictures = []
+ self.block_operation = ''
- def _device_added_cb(self, path):
- """
- Called from hal connection when a new device is plugged.
- """
- if not self.rfid_connected:
- self.rfid_device = find_device()
- _logger.debug("DEVICE_ADDED: %s"%self.rfid_device)
- if self.rfid_device is not None:
- _logger.debug("DEVICE_ADDED: RFID device is not None!")
- self.rfid_connected = self._device.do_connect()
- if self.rfid_connected:
- _logger.debug("DEVICE_ADDED: Connected!")
- self.rfid_device.connect("tag-read", self._tag_read_cb)
- self.rfid_device.connect("disconnected", self._disconnected_cb)
-
- def _disconnected_cb(self, device, text):
- """
- Called when the device is disconnected.
- """
- self.rfid_connected = False
- self.rfid_device = None
+ 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 _tag_read_cb(self, device, tagid):
- """
- Callback for "tag-read" signal. Receives the read tag id.
- """
- idbin = strhex2bin(tagid)
- self.rfid_idn = strbin2dec(idbin[26:64])
- while self.rfid_idn.__len__() < 9:
- self.rfid_idn = '0' + self.rfid_idn
- print tagid, idbin, self.rfid_idn
-
- def new_buffer(self, buf):
- """ Append a new buffer to the ringbuffer """
- self.lc.ringbuffer.append(buf)
- return True
+ 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()):
+ 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))
+ except ImportError:
+ debug_output('failed to import %s' % (plugin_class))
+
+ # Add the icon dir for each plugin to the icon_theme search path
+ for plugin_dir in self._get_plugins_from_plugins_dir(
+ self._get_plugin_home()):
+ self._add_plugin_icon_dir(os.path.join(self._get_plugin_home(),
+ plugin_dir))
+
+ 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 _setup_plugins(self):
+ """ Initial setup -- call 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 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. """
@@ -361,41 +360,41 @@ class TurtleArtWindow():
def _setup_misc(self):
""" Misc. sprites for status, overlays, etc. """
# media blocks get positioned into other blocks
- for _name in MEDIA_SHAPES:
- if _name[0:7] == 'journal' and not self.running_sugar:
- file_name = 'file' + _name[7:]
+ for name in MEDIA_SHAPES:
+ if name[0:7] == 'journal' and not self.running_sugar:
+ filename = 'file' + name[7:]
else:
- file_name = _name
- self.media_shapes[_name] = svg_str_to_pixbuf(svg_from_file(
- "%s/images/%s.svg" % (self.path, file_name)))
+ filename = name
+ self.media_shapes[name] = svg_str_to_pixbuf(svg_from_file(
+ "%s/images/%s.svg" % (self.path, filename)))
- for i, _name in enumerate(STATUS_SHAPES):
- self.status_shapes[_name] = svg_str_to_pixbuf(svg_from_file(
- "%s/images/%s.svg" % (self.path, _name)))
+ for i, name in enumerate(STATUS_SHAPES):
+ self.status_shapes[name] = svg_str_to_pixbuf(svg_from_file(
+ "%s/images/%s.svg" % (self.path, name)))
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,
+ 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'
+ 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 = self.width - 55 * len(TOOLBAR_SHAPES)
- 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("%s/icons/%s.svg" % (self.path, _name))))
- self.toolbar_shapes[_name].set_layer(TAB_LAYER)
- self.toolbar_shapes[_name].name = _name
- self.toolbar_shapes[_name].type = 'toolbar'
+ 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()
def set_sharing(self, shared):
@@ -420,35 +419,17 @@ class TurtleArtWindow():
self.lc.prim_clear()
self.display_coordinates()
- def _start_audiograb(self):
- """ Start grabbing audio if there is an audio block in use """
- if len(self.block_list.get_similar_blocks('block',
- ['volume', 'sound', 'pitch', 'resistance', 'voltage'])) > 0:
- if self.audio_started:
- self.audiograb.resume_grabbing()
- else:
- if self.hw == XO15:
- self.audiograb = AudioGrab_XO15(self.new_buffer, self)
- elif self.hw == XO1:
- self.audiograb = AudioGrab_XO1(self.new_buffer, self)
- else:
- self.audiograb = AudioGrab_Unknown(self.new_buffer, self)
- self.audiograb.start_grabbing()
- self.audio_started = True
-
def run_button(self, time):
""" Run turtle! """
if self.running_sugar:
self.activity.recenter()
- if self.interactive_mode:
- self._start_audiograb()
-
# Look for a 'start' block
for blk in self.just_blocks():
if find_start_stack(blk):
self.step_time = time
- _logger.debug("running stack starting from %s" % (blk.name))
+ debug_output("running stack starting from %s" % (blk.name),
+ self.running_sugar)
self._run_stack(blk)
return
@@ -456,15 +437,15 @@ class TurtleArtWindow():
for blk in self.just_blocks():
if find_block_to_run(blk):
self.step_time = time
- _logger.debug("running stack starting from %s" % (blk.name))
+ debug_output("running stack starting from %s" % (blk.name),
+ self.running_sugar)
self._run_stack(blk)
return
def stop_button(self):
""" Stop button """
- stop_logo(self)
- if self.audio_started:
- self.audiograb.pause_grabbing()
+ self.lc.stop_logo()
+ self._stop_plugins()
def set_userdefined(self, blk=None):
""" Change icon for user-defined blocks after loading Python code. """
@@ -505,28 +486,38 @@ class TurtleArtWindow():
self.overlay_shapes['polar'].hide()
self.polar = False
+ def set_metric(self, flag):
+ """ Turn on/off metric coordinates """
+ if flag:
+ self.overlay_shapes['metric'].set_layer(OVERLAY_LAYER)
+ self.metric = True
+ else:
+ self.overlay_shapes['metric'].hide()
+ self.metric = False
+
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]
+ 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,
+ 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))))
+ svg_from_file("%s/images/%s.svg" % (self.path, name))))
if showing:
- self.overlay_shapes[_name].set_layer(OVERLAY_LAYER)
+ self.overlay_shapes[name].set_layer(OVERLAY_LAYER)
else:
- self.overlay_shapes[_name].hide()
- self.overlay_shapes[_name].type = 'overlay'
+ 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()
@@ -543,9 +534,9 @@ class TurtleArtWindow():
if blk.status != 'collapsed':
blk.spr.set_layer(BLOCK_LAYER)
self.show_palette()
- if self.activity is not None and self.activity.new_sugar_system:
+ if self.activity is not None and self.activity.has_toolbarbox:
self.activity.palette_buttons[0].set_icon(
- PALETTE_NAMES[0] + 'on')
+ palette_names[0] + 'on')
self.hide = False
if self.running_sugar:
self.activity.recenter()
@@ -568,10 +559,10 @@ class TurtleArtWindow():
def show_palette(self, n=0):
""" Show palette """
- self._show_toolbar_palette(n)
+ self.show_toolbar_palette(n)
self.palette_button[self.orientation].set_layer(TAB_LAYER)
self.palette_button[2].set_layer(TAB_LAYER)
- if self.activity is None or not self.activity.new_sugar_system:
+ if self.activity is None or not self.activity.has_toolbarbox:
self.toolbar_spr.set_layer(CATEGORY_LAYER)
self.palette = True
@@ -580,7 +571,7 @@ class TurtleArtWindow():
self._hide_toolbar_palette()
self.palette_button[self.orientation].hide()
self.palette_button[2].hide()
- if self.activity is None or not self.activity.new_sugar_system:
+ if self.activity is None or not self.activity.has_toolbarbox:
self.toolbar_spr.hide()
self.palette = False
@@ -591,7 +582,7 @@ class TurtleArtWindow():
self.hide = False
self.hideshow_button()
if self.running_sugar:
- self.activity.do_hide()
+ self.activity.do_hide_blocks()
def showblocks(self):
""" Callback from 'show blocks' block """
@@ -600,7 +591,7 @@ class TurtleArtWindow():
self.hide = True
self.hideshow_button()
if self.running_sugar:
- self.activity.do_show()
+ self.activity.do_show_blocks()
def resize_blocks(self, blocks=None):
""" Resize blocks or if blocks is None, all of the blocks """
@@ -635,65 +626,21 @@ class TurtleArtWindow():
if blk.name in BLOCKS_WITH_SKIN:
self._resize_skin(blk)
- def _show_toolbar_palette(self, n, init_only=False):
+ def show_toolbar_palette(self, n, init_only=False, regenerate=False):
""" Show the toolbar palettes, creating them on init_only """
- if (self.activity is None or not self.activity.new_sugar_system) and\
+ # 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 == []:
- # Create the selectors
- svg = SVG()
- x, y = 50, 0
- for i, name in enumerate(PALETTE_NAMES):
- a = svg_str_to_pixbuf(svg_from_file("%s/icons/%soff.svg" % (
- self.path, name)))
- b = svg_str_to_pixbuf(svg_from_file("%s/icons/%son.svg" % (
- self.path, name)))
- self.selector_shapes.append([a, b])
- self.selectors.append(Sprite(self.sprite_list, x, y, a))
- 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)
-
- # Create the toolbar background
- self.toolbar_offset = ICON_SIZE
- self.toolbar_spr = Sprite(self.sprite_list, 0, 0,
- svg_str_to_pixbuf(svg.toolbar(self.width, ICON_SIZE)))
- self.toolbar_spr.type = 'toolbar'
- self.toolbar_spr.set_layer(CATEGORY_LAYER)
+ # First, create the selector buttons
+ self._create_the_selectors()
+ # Create the empty palettes that we'll then populate with prototypes.
if self.palette_sprs == []:
- # Create the empty palettes
- if len(self.palettes) == 0:
- for i in range(len(PALETTES)):
- 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)
+ self._create_the_empty_palettes()
+ # At initialization of the program, we don't actually populate
+ # the palettes.
if init_only:
return
@@ -703,27 +650,121 @@ class TurtleArtWindow():
self.selected_palette = n
self.previous_palette = self.selected_palette
- if self.activity is None or not self.activity.new_sugar_system:
+ # 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 self.activity is None or not self.activity.has_toolbarbox:
self.selected_selector = self.selectors[n]
- # Make sure all of the selectors are visible.
self.selectors[n].set_shape(self.selector_shapes[n][1])
- for i in range(len(PALETTES)):
+ 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)
+ # Create 'proto' blocks for each palette entry
+ self._create_proto_blocks(n, regenerate=regenerate)
+
+ self._layout_palette(n, regenerate=regenerate)
+ for blk in self.palettes[n]:
+ blk.spr.set_layer(TAB_LAYER)
+ 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)
+
+ def _create_the_selectors(self):
+ ''' Create the palette selector buttons. '''
+ 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(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)
+
+ def _create_proto_blocks(self, n, regenerate=False):
+ ''' Create the protoblocks that will populate a palette. '''
+ if regenerate:
+ for blk in self.palettes[n]:
+ blk.type = 'trash'
+ self.palettes[n] = []
+
if self.palettes[n] == []:
- # Create 'proto' blocks for each palette entry
- for i, name in enumerate(PALETTES[n]):
+ for i, name in enumerate(palette_blocks[n]):
self.palettes[n].append(Block(self.block_list,
self.sprite_list, name, 0, 0, 'proto', [], PALETTE_SCALE))
self.palettes[n][i].spr.set_layer(TAB_LAYER)
self.palettes[n][i].unhighlight()
# Some proto blocks get a skin.
- if name in BOX_STYLE_MEDIA:
+ if name in block_styles['box-style-media']:
self._proto_skin(name + 'small', n, i)
elif name[:8] == 'template':
self._proto_skin(name[8:], n, i)
@@ -732,25 +773,16 @@ class TurtleArtWindow():
elif name in PYTHON_SKIN:
self._proto_skin('pythonsmall', n, i)
- self._layout_palette(n)
- for blk in self.palettes[n]:
- blk.spr.set_layer(TAB_LAYER)
- if n == self.trash_index:
- for blk in self.trash_stack:
- for gblk in find_group(blk):
- if gblk.status != 'collapsed':
- gblk.spr.set_layer(TAB_LAYER)
-
def _hide_toolbar_palette(self):
""" Hide the toolbar palettes """
self._hide_previous_palette()
- if self.activity is None or not self.activity.new_sugar_system:
+ if self.activity is None or not self.activity.has_toolbarbox:
# Hide the selectors
- for i in range(len(PALETTES)):
+ for i in range(len(palette_blocks)):
self.selectors[i].hide()
elif self.selected_palette is not None:
self.activity.palette_buttons[self.selected_palette].set_icon(
- PALETTE_NAMES[self.selected_palette] + 'off')
+ palette_names[self.selected_palette] + 'off')
self.selected_palette = None
self.previous_palette = None
@@ -758,119 +790,117 @@ class TurtleArtWindow():
""" Hide just the previously viewed toolbar palette """
# Hide previous palette
if self.previous_palette is not None:
- for i in range(len(PALETTES[self.previous_palette])):
- self.palettes[self.previous_palette][i].spr.hide()
- self.palette_sprs[self.previous_palette][
- self.orientation].hide()
- if self.activity is None or not self.activity.new_sugar_system:
+ for proto in self.palettes[self.previous_palette]:
+ proto.spr.hide()
+ self.palette_sprs[self.previous_palette][self.orientation].hide()
+ if self.activity is None or not self.activity.has_toolbarbox:
self.selectors[self.previous_palette].set_shape(
self.selector_shapes[self.previous_palette][0])
elif self.previous_palette is not None and \
self.previous_palette != self.selected_palette:
self.activity.palette_buttons[self.previous_palette].set_icon(
- PALETTE_NAMES[self.previous_palette] + 'off')
- if self.previous_palette == self.trash_index:
+ palette_names[self.previous_palette] + 'off')
+ if self.previous_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
+ max_w = 0
for blk in blocks:
- _w, _h = self._width_and_height(blk)
- if y + _h > PALETTE_HEIGHT + self.toolbar_offset:
- x += int(_max_w + 3)
+ 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
+ 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)))
- y += int(_h + 3)
- if _w > _max_w:
- _max_w = _w
- return x, y, _max_w
+ g.spr.move_relative((int(dx), int(dy)))
+ 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 _b in blocks:
- _w, _h = self._width_and_height(_b)
- if x + _w > PALETTE_WIDTH:
+ row = []
+ row_w = 0
+ max_h = 0
+ for b in blocks:
+ w, h = self._width_and_height(b)
+ 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))
- _row = []
- _row_w = 0
+ dx = int((PALETTE_WIDTH - row_w) / 2)
+ for r in row:
+ for g in find_group(r):
+ g.spr.move_relative((dx, 0))
+ row = []
+ row_w = 0
x = 4
- y += int(_max_h + 3)
- _max_h = 0
- _row.append(_b)
- _row_w += (4 + _w)
- (_bx, _by) = _b.spr.get_xy()
- _dx = int(x - _bx)
- _dy = int(y - _by)
- for _g in find_group(_b):
- _g.spr.move_relative((_dx, _dy))
- x += int(_w + 4)
- if _h > _max_h:
- _max_h = _h
+ y += int(max_h + 3)
+ max_h = 0
+ row.append(b)
+ row_w += (4 + w)
+ (bx, by) = b.spr.get_xy()
+ dx = int(x - bx)
+ dy = int(y - by)
+ for g in find_group(b):
+ g.spr.move_relative((dx, dy))
+ 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))
- return x, y, _max_h
+ dx = int((PALETTE_WIDTH - row_w) / 2)
+ for r in row:
+ for g in find_group(r):
+ g.spr.move_relative((dx, 0))
+ return x, y, max_h
- def _layout_palette(self, n):
+ def _layout_palette(self, n, regenerate=False):
""" 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 = self._horizontal_layout(_x, _y,
- self.palettes[n])
- if n == self.trash_index:
- _x, _y, _max = self._horizontal_layout(_x + _max, _y,
- self.trash_stack)
- _w = _x + _max + 25
- if self.palette_sprs[n][self.orientation] is None:
- svg = SVG()
- self.palette_sprs[n][self.orientation] = Sprite(
- self.sprite_list, 0, self.toolbar_offset,
- svg_str_to_pixbuf(svg.palette(_w, PALETTE_HEIGHT)))
- 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, PALETTE_HEIGHT)))
- self.palette_button[2].move((_w - 20, self.toolbar_offset))
+ 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)
+ self.palette_button[2].move((w - 20, self.toolbar_offset))
else:
- _x, _y = 5, self.toolbar_offset + 15
- _x, _y, _max = self._vertical_layout(_x, _y, self.palettes[n])
- if n == PALETTE_NAMES.index('trash'):
- _x, _y, _max = self._vertical_layout(_x, _y + _max,
- self.trash_stack)
- _h = _y + _max + 25 - self.toolbar_offset
- if self.palette_sprs[n][self.orientation] is None:
- svg = SVG()
- self.palette_sprs[n][self.orientation] = \
- Sprite(self.sprite_list, 0, self.toolbar_offset,
- svg_str_to_pixbuf(svg.palette(PALETTE_WIDTH, _h)))
- 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(PALETTE_WIDTH, _h)))
+ 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)
self.palette_button[2].move((PALETTE_WIDTH - 20,
self.toolbar_offset))
self.palette_sprs[n][self.orientation].set_layer(CATEGORY_LAYER)
+ 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].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()
@@ -916,6 +946,10 @@ class TurtleArtWindow():
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:
@@ -925,7 +959,7 @@ class TurtleArtWindow():
'block', blk.name)) > 0:
self.showlabel('dupstack')
return True
- # You cannot miz and match sensor blocks
+ # 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'])) > 0:
@@ -954,6 +988,9 @@ class TurtleArtWindow():
# 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)
@@ -972,18 +1009,18 @@ class TurtleArtWindow():
elif spr.type == 'palette':
if spr.name == _('next'):
i = self.selected_palette + 1
- if i == len(PALETTE_NAMES):
+ if i == len(palette_names):
i = 0
if self.activity is None or \
- not self.activity.new_sugar_system:
+ not self.activity.has_toolbarbox:
self._select_category(self.selectors[i])
else:
if self.selected_palette is not None:
self.activity.palette_buttons[
self.selected_palette].set_icon(
- PALETTE_NAMES[self.selected_palette] + 'off')
+ palette_names[self.selected_palette] + 'off')
self.activity.palette_buttons[i].set_icon(
- PALETTE_NAMES[i] + 'on')
+ palette_names[i] + 'on')
self.show_palette(i)
else:
self.orientation = 1 - self.orientation
@@ -1018,7 +1055,7 @@ class TurtleArtWindow():
self.lc.trace = 0
self.run_button(0)
elif spr.name == 'run-slowoff':
- self.lc.trace = 0
+ self.lc.trace = 1
self.run_button(3)
elif spr.name == 'debugoff':
self.lc.trace = 1
@@ -1060,8 +1097,8 @@ class TurtleArtWindow():
if gblk.name in BLOCKS_WITH_SKIN:
self._resize_skin(gblk)
- # self.show_palette(self.trash_index)
- if self.selected_palette != self.trash_index:
+ # self.show_palette(palette_names.index('trash'))
+ if self.selected_palette != palette_names.index('trash'):
for gblk in group:
gblk.spr.hide()
@@ -1113,8 +1150,8 @@ class TurtleArtWindow():
def _in_the_trash(self, x, y):
""" Is x, y over the trash can? """
"""
- if self.selected_palette == self.trash_index and \
- self.palette_sprs[self.trash_index][self.orientation].hit((x, y)):
+ if self.selected_palette == palette_names.index('trash') and \
+ self.palette_sprs[palette_names.index('trash')][self.orientation].hit((x, y)):
return True
"""
if self.selected_palette is not None and \
@@ -1146,14 +1183,18 @@ class TurtleArtWindow():
self.selected_blk.unhighlight()
self.selected_blk = None
- def _new_block(self, name, x, y):
+ def _new_block(self, name, x, y, defaults=None):
""" Make a new block. """
- if name in CONTENT_BLOCKS:
- newblk = Block(self.block_list, self.sprite_list, name, x - 20,
- y - 20, 'block', DEFAULTS[name], self.block_scale)
+ 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 - 20,
- y - 20, 'block', [], self.block_scale)
+ 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:
@@ -1161,15 +1202,17 @@ class TurtleArtWindow():
self._block_skin('pythonon', newblk)
else:
self._block_skin('pythonoff', newblk)
- elif name in BOX_STYLE_MEDIA:
+ elif name in block_styles['box-style-media']:
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 DEFAULTS:
- for i, argvalue in enumerate(DEFAULTS[newblk.name]):
+ 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]
@@ -1186,7 +1229,7 @@ class TurtleArtWindow():
argname = argvalue
(sx, sy) = newspr.get_xy()
if argname is not None:
- if argname in CONTENT_BLOCKS:
+ if argname in content_blocks:
argblk = Block(self.block_list, self.sprite_list,
argname, 0, 0, 'block', [argvalue],
self.block_scale)
@@ -1218,8 +1261,7 @@ class TurtleArtWindow():
def process_data(self, block_data, offset=0):
""" Process block_data (from a macro, a file, or the clipboard). """
- if offset != 0:
- _logger.debug("offset is %d" % (offset))
+
# Create the blocks (or turtle).
blocks = []
for blk in block_data:
@@ -1238,7 +1280,8 @@ class TurtleArtWindow():
else:
cons.append(blocks[c])
else:
- _logger.debug("connection error %s" % (str(block_data[i])))
+ 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
@@ -1249,7 +1292,7 @@ class TurtleArtWindow():
else:
cons.append(blocks[c])
# If the boolean op was connected, readjust the plumbing.
- if blocks[i].name in BOOLEAN_STYLE:
+ 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]]
@@ -1262,7 +1305,8 @@ class TurtleArtWindow():
blocks[c].connections[3] = None
else:
# Connection was to a block we haven't seen yet.
- _logger.debug("Warning: dock to the future")
+ 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]
@@ -1276,10 +1320,12 @@ class TurtleArtWindow():
blocks[c].connections[1] = None
else:
# Connection was to a block we haven't seen yet.
- _logger.debug("Warning: dock to the future")
+ debug_output("Warning: dock to the future",
+ self.running_sugar)
else:
- _logger.debug("Warning: unknown connection state %s" % \
- (str(blocks[i].connections)))
+ 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.
@@ -1310,7 +1356,7 @@ class TurtleArtWindow():
return
(sx, sy) = blk.spr.get_xy()
for i, c in enumerate(blk.connections):
- if i > 0 and c is not None:
+ 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:
@@ -1322,8 +1368,8 @@ class TurtleArtWindow():
def _turtle_pressed(self, x, y):
(tx, ty) = self.selected_turtle.get_xy()
- w = self.active_turtle.spr.rect.width / 2
- h = self.active_turtle.spr.rect.height / 2
+ 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
@@ -1349,17 +1395,29 @@ class TurtleArtWindow():
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
- dy = y - dragy - sy
+ dx = x - dragx - sx + self.selected_turtle.spr.rect.width / 2
+ dy = y - dragy - sy + self.selected_turtle.spr.rect.width / 2
self.selected_turtle.spr.set_layer(TOP_LAYER)
- self.selected_turtle.move((sx + dx, sy + dy))
+ 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)
+ self.canvas.setpen(True)
+ else:
+ self.canvas.setxy(tx, ty)
else:
- dx = x - sx - self.active_turtle.spr.rect.width / 2
- dy = y - sy - self.active_turtle.spr.rect.height / 2
+ 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)
self.lc.update_label_value('heading', self.canvas.heading)
+ if self.sharing(): # share turtle rotation
+ self.send_event("r|%s" % (
+ data_to_string([self.selected_turtle.get_name(),
+ round_int(self.canvas.heading)])))
# If we are hoving, show popup help.
elif self.drag_group is None:
@@ -1468,27 +1526,27 @@ class TurtleArtWindow():
def _do_show_popup(self, block_name):
""" Fetch the help text and display it. """
- if block_name in SPECIAL_NAMES:
- block_name_s = SPECIAL_NAMES[block_name]
- elif block_name in BLOCK_NAMES:
- block_name_s = BLOCK_NAMES[block_name][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:
- block_name_s = ''
+ special_block_name = ''
else:
- block_name_s = _(block_name)
- if block_name in HELP_STRINGS:
- if block_name_s == '':
- label = HELP_STRINGS[block_name]
+ special_block_name = _(block_name)
+ if block_name in help_strings:
+ if special_block_name == '':
+ label = help_strings[block_name]
else:
- label = block_name_s + ": " + HELP_STRINGS[block_name]
+ label = special_block_name + ": " + help_strings[block_name]
else:
- label = block_name_s
+ label = special_block_name
if self.running_sugar:
self.activity.hover_help_label.set_text(label)
self.activity.hover_help_label.show()
else:
if self.interactive_mode:
- self.win.set_title(_("Turtle Art") + " — " + label)
+ self.parent.set_title(_("Turtle Art") + " — " + label)
return 0
def _buttonrelease_cb(self, win, event):
@@ -1514,14 +1572,15 @@ class TurtleArtWindow():
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
- self.active_turtle = self.turtles.get_turtle(
- self.default_turtle_name)
+ 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.
@@ -1557,6 +1616,30 @@ class TurtleArtWindow():
if self.block_operation == 'click':
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). """
(cx, cy) = self.canvas.canvas.get_xy()
@@ -1570,8 +1653,6 @@ class TurtleArtWindow():
self.canvas.xcor / self.coord_scale)
self.lc.update_label_value('ycor',
self.canvas.ycor / self.coord_scale)
- if len(self.lc.value_blocks['see']) > 0:
- self.lc.see()
def _click_block(self, x, y):
""" Click block: lots of special cases to handle... """
@@ -1584,7 +1665,9 @@ class TurtleArtWindow():
self.saved_string = blk.spr.labels[0]
blk.spr.labels[0] += CURSOR
- elif blk.name in BOX_STYLE_MEDIA and blk.name != 'camera':
+ elif blk.name in block_styles['box-style-media'] and \
+ blk.name != 'camera':
+ # TODO: isolate reference to camera
self._import_from_journal(self.selected_blk)
if blk.name == 'journal' and self.running_sugar:
self._load_description_block(blk)
@@ -1597,7 +1680,8 @@ class TurtleArtWindow():
dx = 20
blk.expand_in_x(dx)
else:
- dx = 0
+ self._run_stack(blk)
+ return
for gblk in group:
if gblk != blk:
gblk.spr.move_relative((dx * blk.scale, 0))
@@ -1610,13 +1694,14 @@ class TurtleArtWindow():
dy = 20
blk.expand_in_y(dy)
else:
- dy = 0
+ 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:
+ elif blk.name in expandable_blocks:
# Connection may be lost during expansion, so store it...
blk0 = blk.connections[0]
if blk0 is not None:
@@ -1628,11 +1713,10 @@ class TurtleArtWindow():
dy = 20
blk.expand_in_y(dy)
else:
- self._start_audiograb()
self._run_stack(blk)
return
- if blk.name in BOOLEAN_STYLE:
+ if blk.name in block_styles['boolean-style']:
self._expand_boolean(blk, blk.connections[1], dy)
else:
self._expand_expandable(blk, blk.connections[1], dy)
@@ -1664,17 +1748,20 @@ class TurtleArtWindow():
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 = DEFAULTS[blk.name][len(DEFAULTS[blk.name]) - 1]
+ 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]
@@ -1685,28 +1772,26 @@ class TurtleArtWindow():
argblk.spr.set_layer(TOP_LAYER)
argblk.connections = [blk, None]
blk.connections[n - 1] = argblk
- if blk.name in NUMBER_STYLE_VAR_ARG:
+ 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._start_audiograb()
self._run_stack(blk)
- elif blk.name in ['sandwichtop_no_arm_no_label',
+ elif blk.name in ['sandwichtop_no_arm_no_label',
'sandwichtop_no_arm']:
restore_stack(blk)
elif blk.name in COLLAPSIBLE:
top = find_sandwich_top(blk)
if collapsed(blk):
- restore_stack(top) # depreciated (bottom block is invisible)
+ restore_stack(top) # deprecated (bottom block is invisible)
elif top is not None:
collapse_stack(top)
else:
- self._start_audiograb()
self._run_stack(blk)
def _expand_boolean(self, blk, blk2, dy):
@@ -1726,19 +1811,27 @@ class TurtleArtWindow():
for gblk in find_group(blk):
if gblk not in group:
gblk.spr.move_relative((0, dy * blk.scale))
- if blk.name in COMPARE_STYLE:
+ if blk.name in block_styles['compare-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 blk.name in NUMBER_STYLE or \
- blk.name in NUMBER_STYLE_PORCH or \
- blk.name in NUMBER_STYLE_BLOCK or \
- blk.name in NUMBER_STYLE_VAR_ARG:
+ 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].name in expandable_blocks:
if blk.connections[0].connections.index(blk) != 1:
break
blk = blk.connections[0]
@@ -1755,7 +1848,7 @@ class TurtleArtWindow():
for gblk in find_group(blk):
if gblk not in group:
gblk.spr.move_relative((0, dy * blk.scale))
- if blk.name in COMPARE_STYLE:
+ if blk.name in block_styles['compare-style']:
for gblk in find_group(blk):
gblk.spr.move_relative((0, -dy * blk.scale))
else:
@@ -1790,7 +1883,8 @@ class TurtleArtWindow():
""" Run a stack of blocks. """
if blk is None:
return
- self.lc.ag = None
+ self.lc.find_value_blocks() # Are there blocks to update?
+ 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:
@@ -1848,17 +1942,18 @@ class TurtleArtWindow():
selected_block.connections[best_selected_block_dockn] = \
best_destination
- if best_destination.name in BOOLEAN_STYLE:
+ if best_destination.name in block_styles['boolean-style']:
if best_destination_dockn == 2 and \
- selected_block.name in COMPARE_STYLE:
+ selected_block.name in block_styles['compare-style']:
dy = selected_block.ey - best_destination.ey
best_destination.expand_in_y(dy)
self._expand_boolean(best_destination, selected_block, dy)
- elif best_destination.name in EXPANDABLE_BLOCKS and \
+ 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 NUMBER_STYLE_VAR_ARG):
+ 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':
@@ -1877,6 +1972,8 @@ class TurtleArtWindow():
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):
@@ -1886,12 +1983,12 @@ class TurtleArtWindow():
c = blk2.connections.index(blk)
blk2.connections[c] = None
- if blk2.name in BOOLEAN_STYLE:
+ 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:
+ elif blk2.name in expandable_blocks and c == 1:
if blk2.ey > 0:
dy = blk2.reset_y()
if dy != 0:
@@ -1976,7 +2073,6 @@ class TurtleArtWindow():
""" 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'
@@ -1996,9 +2092,9 @@ class TurtleArtWindow():
if keyname == "p":
self.hideshow_button()
elif keyname == 'q':
- if self.audio_started:
- self.audiograb.stop_grabbing()
- stop_media(self.lc)
+ self._plugins_quit()
+ if self.gst_available:
+ stop_media(self.lc)
exit()
elif keyname == 'g':
self._align_to_grid()
@@ -2077,7 +2173,8 @@ class TurtleArtWindow():
oldleft, oldright = \
self.selected_blk.spr.labels[0].split(CURSOR)
except ValueError:
- _logger.debug("[%s]" % self.selected_blk.spr.labels[0])
+ debug_output("[%s]" % self.selected_blk.spr.labels[0],
+ self.running_sugar)
oldleft = self.selected_blk.spr.labels[0]
oldright = ''
else:
@@ -2286,7 +2383,8 @@ class TurtleArtWindow():
f.close()
id = fname
except IOError:
- _logger.error("Unable to read Python code from %s" % (fname))
+ 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
@@ -2302,7 +2400,8 @@ class TurtleArtWindow():
datastore.write(dsobject)
id = dsobject.object_id
except IOError:
- _logger.error("Error copying %s to the datastore" % (fname))
+ error_output("Error copying %s to the datastore" % (fname),
+ self.running_sugar)
id = None
dsobject.destroy()
@@ -2327,12 +2426,14 @@ class TurtleArtWindow():
""" Read the Python code from the Journal object """
self.python_code = None
try:
- _logger.debug("opening %s " % dsobject.file_path)
+ 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:
- _logger.debug("couldn't open %s" % dsobject.file_path)
+ debug_output("couldn't open %s" % dsobject.file_path,
+ self.running_sugar)
if blk is None:
blk = self.selected_blk
if blk is not None:
@@ -2356,7 +2457,7 @@ class TurtleArtWindow():
def new_project(self):
""" Start a new project """
- stop_logo(self)
+ self.lc.stop_logo()
self._loaded_project = ""
# Put current project in the trash.
while len(self.just_blocks()) > 0:
@@ -2377,8 +2478,9 @@ class TurtleArtWindow():
saved_project_data = f.read()
f.close()
except:
- _logger.debug("problem loading saved project data from %s" % \
- (self._loaded_project))
+ 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())
@@ -2409,14 +2511,13 @@ class TurtleArtWindow():
if blk[1] == 'turtle':
self.load_turtle(blk)
return True
- elif type(blk[1]) == list and blk[1][0] == 'turtle':
- self.load_turtle(blk, blk[1][1])
+ 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
- elif type(blk[1]) == tuple:
- _btype, _key = blk[1]
- if _btype == 'turtle':
- self.load_turtle(blk, _key)
- return True
return False
def load_turtle(self, blk, key=1):
@@ -2438,7 +2539,7 @@ class TurtleArtWindow():
btype, value = btype
elif type(btype) == list:
btype, value = btype[0], btype[1]
- if btype in CONTENT_BLOCKS or btype in COLLAPSIBLE:
+ if btype in content_blocks or btype in COLLAPSIBLE:
if btype == 'number':
try:
values = [round_int(value)]
@@ -2467,16 +2568,17 @@ class TurtleArtWindow():
'block', values, self.block_scale)
# Some blocks get transformed.
- if btype in BASIC_STYLE_VAR_ARG and value is not None:
+ 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 depreciated format (#2501)
+ if value > 0: # catch deprecated format (#2501)
self.python_code = None
if self.running_sugar:
try:
dsobject = datastore.get(value)
- except: # Should be IOError, but dbus error is raised
+ except: # Should be IOError, but dbus error is raised
dsobject = None
- _logger.debug("couldn't get dsobject %s" % value)
+ 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:
@@ -2493,9 +2595,9 @@ class TurtleArtWindow():
elif btype == 'start': # block size is saved in start block
if value is not None:
self.block_scale = value
- elif btype in EXPANDABLE or btype in EXPANDABLE_BLOCKS or \
+ 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 btype == 'vspace' or btype in expandable_blocks:
if value is not None:
blk.expand_in_y(value)
elif btype == 'hspace' or btype == 'identity2':
@@ -2514,7 +2616,8 @@ class TurtleArtWindow():
self._block_skin('pythonon', blk)
else:
self._block_skin('pythonoff', blk)
- elif btype in BOX_STYLE_MEDIA and blk.spr is not None:
+ elif btype in block_styles['box-style-media'] and blk.spr is not None:
+ # TODO: isolate reference to camera
if len(blk.values) == 0 or blk.values[0] == 'None' or \
blk.values[0] is None or btype == 'camera':
self._block_skin(btype + 'off', blk)
@@ -2541,8 +2644,8 @@ class TurtleArtWindow():
x, y = self._calc_image_offset('', blk.spr)
blk.set_image(pixbuf, x, y)
except:
- _logger.debug("Couldn't open dsobject (%s)" % \
- (blk.values[0]))
+ 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:]):
@@ -2605,14 +2708,15 @@ class TurtleArtWindow():
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 _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 BASIC_STYLE_VAR_ARG and len(_blk.values) > 0:
+ 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\
+ 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:
@@ -2637,12 +2741,16 @@ class TurtleArtWindow():
_data.append((_blk.id, _name, _sx - self.canvas.cx,
_sy - self.canvas.cy, connections))
if save_turtle:
- for _turtle in iter(self.turtles.dict):
- self.canvas.set_turtle(_turtle)
- _data.append((-1, ['turtle', _turtle],
- self.canvas.xcor, self.canvas.ycor,
- self.canvas.heading, self.canvas.color,
- self.canvas.shade, self.canvas.pensize))
+ 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):
@@ -2655,13 +2763,13 @@ class TurtleArtWindow():
(_("xcor"), x, _("ycor"), y, _("heading"), h))
self.activity.coordinates_label.show()
elif self.interactive_mode:
- self.win.set_title("%s — %s: %d %s: %d %s: %d" % \
+ 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:
- _logger.debug(label)
+ debug_output(label, self.running_sugar)
return
if shp == 'syntaxerror' and str(label) != '':
if str(label)[1:] in self.status_shapes:
@@ -2683,7 +2791,7 @@ class TurtleArtWindow():
self.status_spr.move((PALETTE_WIDTH, self.height - 200))
def calc_position(self, template):
- """ Relative placement of portfolio objects (depreciated) """
+ """ Relative placement of portfolio objects (deprecated) """
w, h, x, y, dx, dy = TEMPLATES[template]
x *= self.canvas.width
y *= self.canvas.height
@@ -2705,49 +2813,30 @@ class TurtleArtWindow():
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] + ".png")
+ save_picture(self.canvas, name[:-3] + suffix)
return
- """
- self.color_map = self.window.get_colormap()
- new_pix = pixbuf.get_from_drawable(self.window, self.color_map,
- 0, 0, 0, 0,
- self.width, self.height)
- new_pix.save(name[:-3] + ".png", "png")
- """
-
if self.running_sugar:
- if svg:
- if len(name) == 0:
- filename = "ta.svg"
- else:
- filename = name + ".svg"
+ if len(name) == 0:
+ filename = 'ta' + suffix
else:
- if len(name) == 0:
- filename = "ta.png"
- else:
- filename = name + ".png"
+ filename = name + suffix
datapath = get_path(self.activity, 'instance')
elif len(name) == 0:
- name = "ta"
+ name = 'ta'
if self.save_folder is not None:
self.load_save_folder = self.save_folder
- if svg:
- filename, self.load_save_folder = get_save_name('.svg',
- self.load_save_folder,
- name)
- else:
- filename, self.load_save_folder = get_save_name('.png',
- self.load_save_folder,
- name)
+ filename, self.load_save_folder = get_save_name(
+ suffix, self.load_save_folder, name)
datapath = self.load_save_folder
else:
datapath = os.getcwd()
- if svg:
- filename = name + ".svg"
- else:
- filename = name + ".png"
+ filename = name + suffix
if filename is None:
return
@@ -2864,3 +2953,36 @@ class TurtleArtWindow():
w, h = self._calc_w_h('descriptionoff', blk.spr)
x, y = self._calc_image_offset('descriptionoff', 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/TurtleArtActivity.py b/TurtleArtActivity.py
index 98968a8..69bb506 100644
--- a/TurtleArtActivity.py
+++ b/TurtleArtActivity.py
@@ -1,6 +1,6 @@
#Copyright (c) 2007, Playful Invention Company
-#Copyright (c) 2008-10, Walter Bender
-#Copyright (c) 2009,10 Raul Gutierrez Segales
+#Copyright (c) 2008-11, 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
@@ -32,22 +32,26 @@ from sugar.activity import activity
try: # 0.86 toolbar widgets
from sugar.activity.widgets import ActivityToolbarButton, StopButton
from sugar.graphics.toolbarbox import ToolbarBox, ToolbarButton
- NEW_SUGAR_SYSTEM = True
+ HAS_TOOLBARBOX = True
except ImportError:
- NEW_SUGAR_SYSTEM = False
+ HAS_TOOLBARBOX = False
from sugar.graphics.toolbutton import ToolButton
from sugar.datastore import datastore
from sugar import profile
-from gettext import gettext as _
import os.path
import tarfile
-from TurtleArt.taconstants import PALETTE_NAMES, OVERLAY_LAYER, HELP_STRINGS
+# installs the global _() magic (reverted as it is broken)
+# import TurtleArt.tagettext
+from gettext import gettext as _
+
+from TurtleArt.tapalette import palette_names, help_strings
+from TurtleArt.taconstants import ICON_SIZE, BLOCK_SCALE, XO1, XO15
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_path, chooser, get_hardware
from TurtleArt.tawindow import TurtleArtWindow
from TurtleArt.tacollaboration import Collaboration
@@ -55,39 +59,37 @@ from TurtleArt.tacollaboration import Collaboration
class TurtleArtActivity(activity.Activity):
def __init__(self, handle):
- """ Activity subclass for Turtle Art """
+ ''' Activity subclass for Turtle Art '''
super(TurtleArtActivity, self).__init__(handle)
- datapath = get_path(activity, 'data')
+ self._check_ver_change(get_path(activity, 'data'))
self._setup_visibility_handler()
- self.new_sugar_system = NEW_SUGAR_SYSTEM
+ self.has_toolbarbox = HAS_TOOLBARBOX
self._setup_toolbar()
- canvas = self._setup_scrolled_window()
+ self._setup_canvas(self._setup_scrolled_window())
- self._check_ver_change(datapath)
-
- self._setup_canvas(canvas)
+ self._setup_palette_toolbar()
self._setup_sharing()
# 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
+ ''' 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
- # grab code from stacks
+ # 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
+ # Save the HTML code to the instance directory.
datapath = get_path(activity, 'instance')
save_type = '.html'
@@ -95,17 +97,17 @@ class TurtleArtActivity(activity.Activity):
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")
+ html_file = os.path.join(datapath, 'portfolio' + save_type)
+ f = file(html_file, 'w')
f.write(html)
f.close()
- if embed_flag == False:
- # need to make a tarball that includes the images
+ 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")
+ tar_fd.add(html_file, 'portfolio.html')
import glob
image_list = glob.glob(os.path.join(datapath, 'image*'))
for i in image_list:
@@ -113,15 +115,11 @@ class TurtleArtActivity(activity.Activity):
finally:
tar_fd.close()
- # Create a datastore object
dsobject = datastore.create()
-
- # Write any metadata (here we specifically set the title of the file
- # and specify that this is a plain text file).
- dsobject.metadata['title'] = self.metadata['title'] + " " + \
- _("presentation")
+ dsobject.metadata['title'] = self.metadata['title'] + ' ' + \
+ _('presentation')
dsobject.metadata['icon-color'] = profile.get_color().to_string()
- if embed_flag == True:
+ if embed_flag:
if save_type == '.xml':
dsobject.metadata['mime_type'] = 'application/xml'
else:
@@ -130,82 +128,72 @@ class TurtleArtActivity(activity.Activity):
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 = []
+ 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 logo code out to datastore. """
- self.save_as_logo.set_icon("logo-saveon")
+ ''' 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
- # Create a datastore object
dsobject = datastore.create()
-
- # Write any metadata (here we specifically set the title of the file
- # and specify that this is a plain text file).
- dsobject.metadata['title'] = self.metadata['title'] + ".lg"
+ dsobject.metadata['title'] = self.metadata['title'] + '.lg'
dsobject.metadata['mime_type'] = 'text/plain'
dsobject.metadata['icon-color'] = profile.get_color().to_string()
-
- # Set the file_path in the datastore.
dsobject.set_file_path(logo_code_path)
-
datastore.write(dsobject)
- gobject.timeout_add(250, self.save_as_logo.set_icon, "logo-saveoff")
+ dsobject.destroy()
+
+ gobject.timeout_add(250, self.save_as_logo.set_icon, 'logo-saveoff')
return
def do_load_ta_project_cb(self, button):
- """ Load a project from the Journal """
- chooser(self, SERVICE, self._load_ta_project)
+ ''' 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 """
+ ''' Load a TA project from the datastore. '''
try:
- _logger.debug("opening %s " % dsobject.file_path)
+ _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")
+ ''' 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")
+ 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")
+ ''' 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")
+ 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. """
+ ''' Save a snapshot of the project to the Journal. '''
tmpfile = self._dump_ta_code()
if tmpfile is not None:
- # Create a datastore object
dsobject = datastore.create()
-
- # Write any metadata
- dsobject.metadata['title'] = self.metadata['title'] + " " + \
- _("snapshot")
+ 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.TurtleArtActivity'
dsobject.set_file_path(tmpfile)
datastore.write(dsobject)
-
- # Clean up
dsobject.destroy()
os.remove(tmpfile)
return
@@ -213,120 +201,119 @@ class TurtleArtActivity(activity.Activity):
# Main/palette toolbar button callbacks
def do_palette_cb(self, button):
- """ Show/hide palette """
- if self.tw.palette == True:
+ ''' Show/hide palette '''
+ if self.tw.palette:
self.tw.hideshow_palette(False)
self.do_hidepalette()
- if self.new_sugar_system and self.tw.selected_palette is not None:
+ 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')
+ palette_names[self.tw.selected_palette] + 'off')
else:
self.tw.hideshow_palette(True)
self.do_showpalette()
- if self.new_sugar_system:
- self.palette_buttons[0].set_icon(PALETTE_NAMES[0] + 'on')
+ 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 """
+ ''' Palette selector buttons '''
if self.tw.selected_palette is not None:
self.palette_buttons[self.tw.selected_palette].set_icon(
- PALETTE_NAMES[self.tw.selected_palette] + 'off')
+ palette_names[self.tw.selected_palette] + 'off')
if self.tw.selected_palette == i:
- # second click so hide the palette (#2505)
+ # Hide the palette if it is already selected.
self.tw.hideshow_palette(False)
self.do_hidepalette()
return
- self.palette_buttons[i].set_icon(PALETTE_NAMES[i] + 'on')
+ self.palette_buttons[i].set_icon(palette_names[i] + 'on')
self.tw.show_palette(i)
self.do_showpalette()
- # These methods are called both from buttons and palette.
+ # These methods are called both from toolbar buttons and blocks.
def do_hidepalette(self):
- """ Hide the palette. """
+ ''' Hide the palette. '''
if hasattr(self, 'palette_button'):
- self.palette_button.set_icon("paletteon")
+ self.palette_button.set_icon('paletteon')
self.palette_button.set_tooltip(_('Show palette'))
def do_showpalette(self):
- """ Show the palette. """
+ ''' Show the palette. '''
if hasattr(self, 'palette_button'):
- self.palette_button.set_icon("paletteoff")
+ self.palette_button.set_icon('paletteoff')
self.palette_button.set_tooltip(_('Hide palette'))
def do_hideshow_cb(self, button):
- """ Toggle visibility. """
+ ''' Toggle visibility. '''
self.tw.hideshow_button()
- if self.tw.hide == True: # we just hid the blocks
- self.blocks_button.set_icon("hideshowon")
+ 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_icon('hideshowoff')
self.blocks_button.set_tooltip(_('Hide blocks'))
- # update palette buttons too
- if self.tw.palette == False:
+ # Update palette buttons too.
+ if not self.tw.palette:
self.do_hidepalette()
else:
self.do_showpalette()
- def do_hide(self):
- """ Hide blocks. """
- self.blocks_button.set_icon("hideshowon")
+ 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(self):
- """ Show blocks. """
- self.blocks_button.set_icon("hideshowoff")
+ 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")
+ ''' 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")
+ gobject.timeout_add(250, self.eraser_button.set_icon, 'eraseron')
def do_run_cb(self, button):
- """ Callback for run button (rabbit). """
- self.run_button.set_icon("run-faston")
+ ''' 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")
+ 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")
+ ''' 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")
+ 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")
+ ''' 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")
+ gobject.timeout_add(1000, self.debug_button.set_icon, 'debugoff')
def do_stop_cb(self, button):
- """ Callback for stop button. """
- self.stop_turtle_button.set_icon("stopitoff")
+ ''' Callback for stop button. '''
+ 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")
+ self.step_button.set_icon('run-slowoff')
+ self.run_button.set_icon('run-fastoff')
def do_samples_cb(self, button):
- """ Sample projects open dialog """
- # FIXME: encapsulation!
+ ''' Sample-projects open dialog '''
self.tw.load_file(True)
- # run the activity
- self.stop_turtle_button.set_icon("stopiton")
self.tw.run_button(0)
def recenter(self):
- """ Recenter scrolled window around canvas. """
+ ''' Recenter scrolled window around canvas. '''
hadj = self.sw.get_hadjustment()
hadj.set_value(0)
self.sw.set_hadjustment(hadj)
@@ -335,39 +322,55 @@ class TurtleArtActivity(activity.Activity):
self.sw.set_vadjustment(vadj)
def do_fullscreen_cb(self, button):
- """ Hide the Sugar toolbars. """
+ ''' Hide the Sugar toolbars. '''
self.fullscreen()
self.recenter()
def do_grow_blocks_cb(self, button):
- """ Grow the blocks. """
- self.do_resize_blocks(1.5)
+ ''' Grow the blocks. '''
+ self.do_resize_blocks(1)
def do_shrink_blocks_cb(self, button):
- """ Shrink the blocks. """
- self.do_resize_blocks(0.67)
+ ''' Shrink the blocks. '''
+ self.do_resize_blocks(-1)
- def do_resize_blocks(self, scale_factor):
- """ Scale the blocks. """
- self.tw.block_scale *= scale_factor
+ 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. """
+ ''' 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. """
+ ''' 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). """
+ ''' Rescale coordinate system (100==height/2 or 100 pixels). '''
if self.tw.cartesian:
cartesian = True
self.tw.set_cartesian(False)
@@ -378,28 +381,35 @@ class TurtleArtActivity(activity.Activity):
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_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_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 get_document_path(self, async_cb, async_err_cb):
- """ View TA code as part of view source. """
+ ''' 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. """
+ ''' Save Logo code to temporary file. '''
datapath = get_path(activity, 'instance')
tmpfile = os.path.join(datapath, 'tmpfile.lg')
code = save_logo(self.tw)
@@ -407,47 +417,41 @@ class TurtleArtActivity(activity.Activity):
_logger.debug('save_logo returned None')
return None
try:
- f = file(tmpfile, "w")
+ f = file(tmpfile, 'w')
f.write(code)
f.close()
except Exception, e:
- _logger.error("Couldn't dump code to view source: " + str(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. """
+ ''' 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:
- _logger.debug("couldn't save snapshot to journal")
+ 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. """
+ ''' Callback method for when the activity's visibility changes. '''
if event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED:
- self.tw.lc.ag = None
+ self.tw.background_plugins()
elif event.state in \
[gtk.gdk.VISIBILITY_UNOBSCURED, gtk.gdk.VISIBILITY_PARTIAL]:
- pass
-
- def update_title_cb(self, widget, event, toolbox):
- """ Update the title. """
- toolbox._activity_toolbar._update_title_cb()
- toolbox._activity_toolbar._update_title_sid = True
+ self.tw.foreground_plugins()
def _keep_clicked_cb(self, button):
- """ Keep button clicked. """
+ ''' Keep-button clicked. '''
self.jobject_new_patch()
def _setup_toolbar(self):
- """ Setup toolbar according to Sugar version """
- if self.new_sugar_system:
- # Use 0.86 toolbar design
- # Create toolbox and secondary toolbars
- toolbox = ToolbarBox()
+ ''' Setup toolbar according to Sugar version. '''
+ if self.has_toolbarbox:
+ self._toolbox = ToolbarBox()
activity_toolbar_button = ActivityToolbarButton(self)
@@ -459,61 +463,60 @@ class TurtleArtActivity(activity.Activity):
view_toolbar_button = ToolbarButton(label=_('View'),
page=view_toolbar,
icon_name='toolbar-view')
- palette_toolbar = gtk.Toolbar()
- palette_toolbar_button = ToolbarButton(page=palette_toolbar,
- icon_name='palette')
+ self._palette_toolbar = gtk.Toolbar()
+ self._palette_toolbar_button = ToolbarButton(
+ page=self._palette_toolbar, icon_name='palette')
help_toolbar = gtk.Toolbar()
- help_toolbar_button = ToolbarButton(label=_("Help"),
+ help_toolbar_button = ToolbarButton(label=_('Help'),
page=help_toolbar,
icon_name='help-toolbar')
journal_toolbar = gtk.Toolbar()
journal_toolbar_button = ToolbarButton(page=journal_toolbar,
icon_name='activity-journal')
- # Add the toolbars and buttons to the toolbox
+
activity_toolbar_button.show()
- toolbox.toolbar.insert(activity_toolbar_button, -1)
+ self._toolbox.toolbar.insert(activity_toolbar_button, -1)
edit_toolbar_button.show()
- toolbox.toolbar.insert(edit_toolbar_button, -1)
+ self._toolbox.toolbar.insert(edit_toolbar_button, -1)
journal_toolbar_button.show()
- toolbox.toolbar.insert(journal_toolbar_button, -1)
+ self._toolbox.toolbar.insert(journal_toolbar_button, -1)
view_toolbar_button.show()
- toolbox.toolbar.insert(view_toolbar_button, -1)
- palette_toolbar_button.show()
- toolbox.toolbar.insert(palette_toolbar_button, -1)
+ self._toolbox.toolbar.insert(view_toolbar_button, -1)
+ self._palette_toolbar_button.show()
+ self._toolbox.toolbar.insert(self._palette_toolbar_button, -1)
help_toolbar_button.show()
- toolbox.toolbar.insert(help_toolbar_button, -1)
+ self._toolbox.toolbar.insert(help_toolbar_button, -1)
- self._add_separator(toolbox.toolbar)
+ self._add_separator(self._toolbox.toolbar)
- self._make_project_buttons(toolbox.toolbar)
+ self._make_project_buttons(self._toolbox.toolbar)
- self._add_separator(toolbox.toolbar, True)
+ self._add_separator(self._toolbox.toolbar, True)
stop_button = StopButton(self)
stop_button.props.accelerator = '<Ctrl>Q'
- toolbox.toolbar.insert(stop_button, -1)
+ self._toolbox.toolbar.insert(stop_button, -1)
stop_button.show()
else:
- # Use pre-0.86 toolbar design
- toolbox = activity.ActivityToolbox(self)
- self.set_toolbox(toolbox)
+ self._toolbox = activity.ActivityToolbox(self)
+ self.set_toolbox(self._toolbox)
project_toolbar = gtk.Toolbar()
- toolbox.add_toolbar(_('Project'), project_toolbar)
+ self._toolbox.add_toolbar(_('Project'), project_toolbar)
view_toolbar = gtk.Toolbar()
- toolbox.add_toolbar(_('View'), view_toolbar)
+ self._toolbox.add_toolbar(_('View'), view_toolbar)
view_toolbar_button = view_toolbar
edit_toolbar = gtk.Toolbar()
- toolbox.add_toolbar(_('Edit'), edit_toolbar)
+ self._toolbox.add_toolbar(_('Edit'), edit_toolbar)
edit_toolbar_button = edit_toolbar
journal_toolbar = gtk.Toolbar()
- toolbox.add_toolbar(_('Import/Export'), journal_toolbar)
+ self._toolbox.add_toolbar(_('Import/Export'), journal_toolbar)
journal_toolbar_button = journal_toolbar
help_toolbar = gtk.Toolbar()
- toolbox.add_toolbar(_('Help'), help_toolbar)
+ self._toolbox.add_toolbar(_('Help'), help_toolbar)
help_toolbar_button = help_toolbar
self._make_palette_buttons(project_toolbar, palette_button=True)
@@ -522,193 +525,208 @@ class TurtleArtActivity(activity.Activity):
self._make_project_buttons(project_toolbar)
- self.keep_button = self._add_button('filesaveoff', _("Save snapshot"),
- self.do_keep_cb,
- journal_toolbar_button)
- self.save_as_html = self._add_button('htmloff', _("Save as HTML"),
- self.do_save_as_html_cb,
- journal_toolbar_button)
- self.save_as_logo = self._add_button('logo-saveoff', _("Save as Logo"),
- self.do_save_as_logo_cb,
- journal_toolbar_button)
- self.save_as_image = self._add_button('image-saveoff', _("Save as image"),
- self.do_save_as_image_cb,
- journal_toolbar_button)
- self.load_ta_project = self._add_button('load-from-journal',
- _("Import project from the Journal"), self.do_load_ta_project_cb,
- journal_toolbar_button)
+ self.keep_button = self._add_button(
+ 'filesaveoff', _('Save snapshot'), self.do_keep_cb,
+ journal_toolbar_button)
+ self.save_as_html = self._add_button(
+ 'htmloff', _('Save as HTML'), self.do_save_as_html_cb,
+ journal_toolbar_button)
+ self.save_as_logo = self._add_button(
+ 'logo-saveoff', _('Save as Logo'), self.do_save_as_logo_cb,
+ journal_toolbar_button)
+ self.save_as_image = self._add_button(
+ 'image-saveoff', _('Save as image'), self.do_save_as_image_cb,
+ journal_toolbar_button)
+ self.load_ta_project = self._add_button(
+ 'load-from-journal', _('Import project from the Journal'),
+ self.do_load_ta_project_cb, journal_toolbar_button)
self._add_separator(journal_toolbar)
- self.load_python = self._add_button('pippy-openoff', _("Load Python block"),
- self.do_load_python_cb,
- journal_toolbar_button)
- self.samples_button = self._add_button("ta-open", _('Load example'),
- self.do_samples_cb, journal_toolbar_button)
- copy = self._add_button('edit-copy', _('Copy'), self._copy_cb,
- edit_toolbar_button, '<Ctrl>c')
- paste = self._add_button('edit-paste', _('Paste'), self._paste_cb,
- edit_toolbar_button, '<Ctrl>v')
- fullscreen_button = self._add_button('view-fullscreen', _("Fullscreen"),
- self.do_fullscreen_cb,
- view_toolbar_button, '<Alt>Return')
- cartesian_button = self._add_button('view-Cartesian',
- _("Cartesian coordinates"),
- self.do_cartesian_cb,
- view_toolbar_button)
- polar_button = self._add_button('view-polar', _("Polar coordinates"),
- self.do_polar_cb, view_toolbar_button)
+ self.load_python = self._add_button(
+ 'pippy-openoff', _('Load Python block'), self.do_load_python_cb,
+ journal_toolbar_button)
+ self.samples_button = self._add_button(
+ 'ta-open', _('Load example'), self.do_samples_cb,
+ journal_toolbar_button)
+ 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]:
+ self._add_button('view-metric', _('Metric coordinates'),
+ self.do_metric_cb, view_toolbar_button)
self._add_separator(view_toolbar)
- self.coordinates_label = self._add_label(
- _("xcor") + " = 0 " + _("ycor") + " = 0 " + _("heading") + " = 0",
- view_toolbar)
+ self.coordinates_label = self._add_label(_('xcor') + ' = 0 ' + \
+ _('ycor') + ' = 0 ' + _('heading') + ' = 0', view_toolbar)
self._add_separator(view_toolbar, True)
- 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_label = self._add_label(
- _("Move the cursor over the orange palette for help."),
- help_toolbar)
-
- # The palette toolbar is only used with 0.86+
- if self.new_sugar_system:
- self.palette_buttons = []
- for i, name in enumerate(PALETTE_NAMES):
- if i > 0:
- suffix = 'off'
- else:
- suffix = 'on'
- self.palette_buttons.append(self._add_button(name + suffix,
- HELP_STRINGS[name], self.do_palette_buttons_cb,
- palette_toolbar_button, None, i))
- self._add_separator(palette_toolbar, True)
-
- self._make_palette_buttons(palette_toolbar_button)
-
- self.set_toolbar_box(toolbox)
- palette_toolbar.show()
+ 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)
+ if gtk.gtk_version[0] > 2 or gtk.gtk_version[1] > 16:
+ self.hover_help_label = self._add_label(
+ _('Move the cursor over the orange palette for help.'),
+ help_toolbar, gtk.gdk.screen_width() - 2 * ICON_SIZE)
+ else:
+ self.hover_help_label = self._add_label(
+ _('Move the cursor over the orange palette for help.'),
+ help_toolbar)
edit_toolbar.show()
view_toolbar.show()
help_toolbar.show()
- toolbox.show()
+ self._toolbox.show()
+ # Setup palette toolbar only *after* initializing the plugins
- if self.new_sugar_system:
+ if self.has_toolbarbox:
# Hack as a workaround for #2050
edit_toolbar_button.set_expanded(True)
edit_toolbar_button.set_expanded(False)
- palette_toolbar_button.set_expanded(True)
+ self._palette_toolbar_button.set_expanded(True)
else:
- toolbox.set_current_toolbar(1)
+ 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, name in enumerate(palette_names):
+ if i > 0:
+ suffix = 'off'
+ else:
+ suffix = 'on'
+ self.palette_buttons.append(self._add_button(name + suffix,
+ help_strings[name], self.do_palette_buttons_cb,
+ self._palette_toolbar_button, None, i))
+ self._add_separator(self._palette_toolbar, True)
+
+ self._make_palette_buttons(self._palette_toolbar_button)
+
+ self.set_toolbar_box(self._toolbox)
+ self._palette_toolbar.show()
def _make_palette_buttons(self, toolbar, palette_button=False):
- """ Creates the palette and block buttons for both toolbar types"""
+ ''' Creates the palette and block buttons for both toolbar types'''
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'))
+ 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.debug_button = self._add_button("debugoff", _('Debug'),
- self.do_debug_cb, toolbar, _('<Ctrl>d'))
- self.stop_turtle_button = self._add_button("stopitoff", _('Stop turtle'),
- self.do_stop_cb, toolbar, _('<Ctrl>s'))
-
- 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()
- width = gtk.gdk.screen_width() * 2
- height = gtk.gdk.screen_height() * 2
- canvas.set_size_request(width, height)
- self.sw.add_with_viewport(canvas)
- canvas.show()
- return canvas
+ ''' 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.debug_button = self._add_button(
+ 'debugoff', _('Debug'), self.do_debug_cb, toolbar, _('<Ctrl>d'))
+ self.stop_turtle_button = self._add_button(
+ 'stopitoff', _('Stop turtle'), self.do_stop_cb, toolbar,
+ _('<Ctrl>s'))
def _check_ver_change(self, datapath):
- """ To be replaced with date checking. """
- # Check to see if the version has changed
+ ''' 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"
+ version = 'unknown'
- filename = "version.dat"
+ filename = 'version.dat'
version_data = []
new_version = True
try:
- file_handle = open(os.path.join(datapath, filename), "r")
+ 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")
+ _logger.debug("Couldn't read version number.")
version_data.append(version)
try:
- file_handle = open(os.path.join(datapath, filename), "w")
+ 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")
+ _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)
+ canvas.show()
+ self.sw.show()
+ self.show_all()
+ return canvas
+
def _setup_canvas(self, canvas):
- """ Initialize the turtle art canvas. """
+ ''' Initialize the turtle art canvas. '''
bundle_path = activity.get_bundle_path()
self.tw = TurtleArtWindow(canvas, bundle_path, self,
profile.get_color().to_string(),
profile.get_nick_name())
- # self.tw.activity = self
self.tw.window.grab_focus()
path = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'data')
self.tw.save_folder = path
+ # Try restoring an existing project...
if self._jobject and self._jobject.file_path:
self.read_file(self._jobject.file_path)
- else: # if new, load a start brick onto the canvas
+ 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 when the visibility state changes """
+ ''' Notify me when the visibility state changes. '''
self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
- self.connect("visibility-notify-event", self.__visibility_notify_cb)
+ 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)
+ ''' 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. """
- import os
- import tempfile
- import shutil
-
+ ''' Read a project in and then run it. '''
if hasattr(self, 'tw'):
- _logger.debug("Read file: %s" % (file_path))
- # Could be a gtar (newer builds) or tar (767) file
+ _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:
@@ -716,56 +734,54 @@ class TurtleArtActivity(activity.Activity):
# 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) # create a new project flag
+ 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
+ # ...otherwise, assume it is a .ta file.
else:
- _logger.debug("trying to open a .ta file:" + file_path)
+ _logger.debug('Trying to open a .ta file:' + file_path)
self.tw.load_files(file_path, run_it)
- # run the activity
+ # Finally, run the project.
if run_it:
- self.stop_turtle_button.set_icon("stopiton")
self.tw.run_button(0)
else:
- _logger.debug("Deferring reading file %s" % (file_path))
+ _logger.debug('Deferring reading file %s' % (file_path))
def jobject_new_patch(self):
- """ Save instance to Journal. """
+ ''' 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'] = self.get_service_name()
self._jobject.metadata['activity_id'] = self.get_id()
self._jobject.metadata['keep'] = '0'
- # Is this the correct syntax for saving the buddies list?
- # self._jobject.metadata['buddies'] = self.tw.buddies
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)
+ 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):
- clipBoard = gtk.Clipboard()
- _logger.debug("serialize the project and copy to clipboard")
+ ''' 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)
+ clipboard.set_text(text)
self.tw.paste_offset = 20
def _paste_cb(self, button):
- clipBoard = gtk.Clipboard()
- _logger.debug("paste to the project")
- text = clipBoard.wait_for_text()
+ ''' 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':
@@ -777,10 +793,12 @@ class TurtleArtActivity(activity.Activity):
self.tw.paste_offset)
self.tw.paste_offset += 20
- def _add_label(self, string, toolbar):
- """ add a label to a toolbar """
+ 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)
@@ -789,15 +807,16 @@ class TurtleArtActivity(activity.Activity):
return label
def _add_separator(self, toolbar, expand=False):
- """ add a separator to a toolbar """
+ ''' Add a separator to a toolbar. '''
separator = gtk.SeparatorToolItem()
separator.props.draw = True
separator.set_expand(expand)
toolbar.insert(separator, -1)
separator.show()
- def _add_button(self, name, tooltip, callback, toolbar, accelerator=None, arg=None):
- """ add a button to a toolbar """
+ 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:
@@ -810,8 +829,11 @@ class TurtleArtActivity(activity.Activity):
except AttributeError:
pass
button.show()
- if hasattr(toolbar, 'insert'): # the main toolbar
+ if hasattr(toolbar, 'insert'): # Add button to the main toolbar...
toolbar.insert(button, -1)
- else: # or a secondary toolbar
+ else: # ...or a secondary toolbar.
toolbar.props.page.insert(button, -1)
+
+ if not name in help_strings:
+ help_strings[name] = tooltip
return button
diff --git a/activity/activity.info b/activity/activity.info
index 151339c..14b5b59 100755..100644
--- a/activity/activity.info
+++ b/activity/activity.info
@@ -1,6 +1,6 @@
[Activity]
name = Turtle Art
-activity_version = 106
+activity_version = 109
license = MIT
bundle_id = org.laptop.TurtleArtActivity
exec = sugar-activity TurtleArtActivity.TurtleArtActivity
diff --git a/collaboration/neighborhood.py b/collaboration/neighborhood.py
index b587bc8..192b66f 100755
--- a/collaboration/neighborhood.py
+++ b/collaboration/neighborhood.py
@@ -1,8 +1,21 @@
#!/usr/bin/python
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2010-11 Collabora Ltd. <http://www.collabora.co.uk/>
#
-# neighborhood.py
+# 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
diff --git a/collaboration/presenceservice.py b/collaboration/presenceservice.py
index 142fc4a..33caadd 100644
--- a/collaboration/presenceservice.py
+++ b/collaboration/presenceservice.py
@@ -95,7 +95,7 @@ class PresenceService(gobject.GObject):
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)
+ "%s" % (activity_id))
else:
raise
else:
@@ -166,8 +166,8 @@ class PresenceService(gobject.GObject):
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)
+ raise ValueError('Unknown buddy in connection %s with handle %d' % \
+ (tp_conn_path, handle))
def get_owner(self):
"""Retrieves the laptop Buddy object."""
@@ -203,8 +203,8 @@ class PresenceService(gobject.GObject):
properties['private'] = private
if self._activity_cache is not None:
- raise ValueError('Activity %s is already tracked',
- activity.get_id())
+ raise ValueError('Activity %s is already tracked' % \
+ (activity.get_id()))
connection_manager = get_connection_manager()
account_path, connection = \
@@ -220,8 +220,8 @@ class PresenceService(gobject.GObject):
self._activity_cache = shared_activity
if shared_activity.props.joined:
- raise RuntimeError('Activity %s is already shared.' %
- activity.props.id)
+ raise RuntimeError('Activity %s is already shared.' % \
+ (activity.props.id))
shared_activity.share(self.__share_activity_cb,
self.__share_activity_error_cb)
diff --git a/collaboration/telepathyclient.py b/collaboration/telepathyclient.py
index 5491530..e00f053 100644
--- a/collaboration/telepathyclient.py
+++ b/collaboration/telepathyclient.py
@@ -18,10 +18,11 @@ import logging
import dbus
from dbus import PROPERTIES_IFACE
+
from telepathy.interfaces import CLIENT, \
- CLIENT_APPROVER, \
- CLIENT_HANDLER, \
- CLIENT_INTERFACE_REQUESTS
+ CLIENT_APPROVER, \
+ CLIENT_HANDLER, \
+ CLIENT_INTERFACE_REQUESTS
from telepathy.server import DBusProperties
import dispatch
@@ -34,6 +35,7 @@ _instance = None
class TelepathyClient(dbus.service.Object, DBusProperties):
+
def __init__(self):
self._interfaces = set([CLIENT, CLIENT_HANDLER,
CLIENT_INTERFACE_REQUESTS, PROPERTIES_IFACE,
diff --git a/extra/plugin.py b/extra/plugin.py
deleted file mode 100644
index fe95f95..0000000
--- a/extra/plugin.py
+++ /dev/null
@@ -1,10 +0,0 @@
-
-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.")
-
diff --git a/devices/__init__.py b/gnome_plugins/__init__.py
index e69de29..e69de29 100644
--- a/devices/__init__.py
+++ b/gnome_plugins/__init__.py
diff --git a/extra/collaborationplugin.py b/gnome_plugins/collaboration_plugin.py
index 56fcae5..19c084b 100644
--- a/extra/collaborationplugin.py
+++ b/gnome_plugins/collaboration_plugin.py
@@ -1,28 +1,56 @@
+#!/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
-import traceback
+
from TurtleArt.tacollaboration import Collaboration
+import traceback
+
CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \
'org.laptop.Telepathy.ActivityProperties'
-class CollaborationPlugin(Plugin):
+
+class Collaboration_plugin(Plugin):
__gsignals__ = {
'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
@@ -31,56 +59,76 @@ class CollaborationPlugin(Plugin):
()),
}
- # Using activity here is kinda misleading cause the
- # obj we are receiving is not a Sugar activity.
- def __init__(self, activity, config_file_path):
+ def __init__(self, parent):
Plugin.__init__(self)
- self._activity = activity
+ self._parent = parent
self._neighborhood = None
- self._title = "My Turtle Art session"
+ self._title = _('My Turtle Art session')
self._bundle_id = "org.laptop.TurtleArt"
- self._activity_id = "1234567" # This could be hashed from the file path (if resuming)
+ # This could be hashed from the file path (if resuming)
+ self._activity_id = "1234567"
self._nick = ""
- self._config_file_path = config_file_path
+ 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)
- valid_config_values = {
- "nick" : { "type" : "text"},
- "account_id" : { "type" : "text"},
- "password" : { "type" : "text"},
- "server" : { "type" : "text"},
- "port" : { "type" : "integer"},
- "register": { "type" : "boolean"},
- "turtle_color" : { "type" : "text"},
- "colors" : { "type" : "text"}
+ 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'}
}
- self._collaboration_config_values.set_valid_keys(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)
+
+ 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'))
+ 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'))
+ 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)
-
+ MenuBuilder.make_menu_item(menu, _('Configuration'),
+ self._config_neighborhood_cb)
+
neighborhood_menu = MenuBuilder.make_sub_menu(menu, _('Neighborhood'))
return neighborhood_menu
@@ -99,40 +147,42 @@ class CollaborationPlugin(Plugin):
def _get_title(self):
return self._title
-
- def _get_turtle_color(self):
- return self._turtle_color
def _connect_to_neighborhood(self, config_file_obj):
if self._neighborhood is not None:
return
- print "_connect_to_neighborhood has been called"
-
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")
- self._colors = self._collaboration_config_values.get("colors")
- self._turtle_color = self._collaboration_config_values.get("turtle_color")
+ 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 = {}
- print "connecting to the neighborhood"
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('activity-removed',
+ self._activity_removed_cb)
self._neighborhood.connect('buddy-added', self._buddy_added_cb)
self._neighborhood.connect('buddy-removed', self._buddy_removed_cb)
@@ -147,7 +197,7 @@ class CollaborationPlugin(Plugin):
try:
self._activities.pop(activity_model.props.name)
except:
- print "Failed to remove activity %s" % activity_model.props.name
+ print 'Failed to remove activity %s' % activity_model.props.name
self._recreate_available_activities_menu()
@@ -162,7 +212,7 @@ class CollaborationPlugin(Plugin):
print "Couldn't remove buddy %s" % buddy.get_key()
self._recreate_available_buddies_menu()
- # TODO: we should have a list of available actions over
+ # 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
#
@@ -173,26 +223,28 @@ class CollaborationPlugin(Plugin):
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)
+ 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()
+ print 'do something with %s' % buddy.get_nick()
- # TODO
- # - we need an extra menu branch with a) 'Join' button b) List of buddies
+ # 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)
+ 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..."
-
+ print 'Lets try to join...'
+
connection_manager = get_connection_manager()
account_path, connection = \
connection_manager.get_preferred_connection()
@@ -201,17 +253,17 @@ class CollaborationPlugin(Plugin):
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
+ 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)
+ 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:
@@ -224,32 +276,42 @@ class CollaborationPlugin(Plugin):
_join_id = self._joined_activity.connect('joined', self.__joined_cb)
self._joined_activity.join()
- def __joined_cb(self,activity, success, err):
+ 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" },
- { "item_label" : _("Turtle Color"), "item_type" : "text", "item_name" : "turtle_color" }
+ {'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_turtle_color()
+ properties['color'] = self.get_colors()
properties['private'] = False
connection_manager = get_connection_manager()
@@ -261,18 +323,19 @@ class CollaborationPlugin(Plugin):
return
try:
- self._activity._shared_activity = Activity(account_path, connection,
- properties=properties)
+ 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._activity._shared_activity
+ self._shared_activity = self._parent._shared_activity
except:
traceback.print_exc(file=sys.stdout)
- if self._activity._shared_activity.props.joined:
+ if self._parent._shared_parent.props.joined:
raise RuntimeError('Activity %s is already shared.' %
- self._activity._get_activity_id())
+ self._parent._get_activity_id())
- self._activity._shared_activity.share(self.__share_activity_cb,
+ self._parent._shared_parent.share(self.__share_activity_cb,
self.__share_activity_error_cb)
def __share_activity_cb(self, activity):
@@ -280,10 +343,8 @@ class CollaborationPlugin(Plugin):
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"
+ """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/TurtleArt/tacamera.py b/gnome_plugins/plugin.py
index 2177288..590c9bc 100644
--- a/TurtleArt/tacamera.py
+++ b/gnome_plugins/plugin.py
@@ -1,6 +1,6 @@
-# -*- coding: utf-8 -*-
-#Copyright (c) 2010, Walter Bender
-#Copyright (c) 2010, Tony Forster
+#!/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
@@ -20,23 +20,15 @@
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
-import gst, time
+import gobject
-GST_PIPE = ['v4l2src', 'ffmpegcolorspace', 'pngenc']
-class Camera():
- """ A class for representing the video camera """
+class Plugin(gobject.GObject):
+ def __init__(self):
+ gobject.GObject.__init__(self)
- def __init__(self, imagepath):
- GST_PIPE.append('filesink location=%s' % imagepath)
- self.pipe = gst.parse_launch('!'.join(GST_PIPE))
- self.bus = self.pipe.get_bus()
-
- def save_camera_input_to_file(self):
- """ Grab a frame from the camera """
- self.pipe.set_state(gst.STATE_PLAYING)
- self.bus.poll(gst.MESSAGE_EOS, -1)
-
- def stop_camera_input(self):
- self.pipe.set_state(gst.STATE_NULL)
+ 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/extra/upload.py b/gnome_plugins/uploader_plugin.py
index e39d099..06224fa 100644
--- a/extra/upload.py
+++ b/gnome_plugins/uploader_plugin.py
@@ -1,3 +1,26 @@
+#!/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:
@@ -8,14 +31,21 @@ 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():
+
+class Uploader_plugin(Plugin):
MAX_FILE_SIZE = 950000
UPLOAD_SERVER = 'http://turtleartsite.appspot.com'
- def __init__(self, upload_server = None, max_file_size = 0):
+ def __init__(self, parent, upload_server=None, max_file_size=None):
+ self._parent = parent
self.uploading = False
if upload_server is None:
@@ -23,14 +53,23 @@ class Uploader():
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):
+ def do_upload_to_web(self, widget=None):
if self.uploading:
return
@@ -127,14 +166,14 @@ http://turtleartsite.sugarlabs.org to upload your project.'))
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:
+ 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)
+ smaller_file.save(imagefile, quality=100)
c = pycurl.Curl()
c.setopt(c.POST, 1)
diff --git a/icons/blocksoff.svg b/icons/blocksoff.svg
index f0e10b3..e7db290 100644
--- a/icons/blocksoff.svg
+++ b/icons/blocksoff.svg
@@ -2,13 +2,29 @@
<!-- 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="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" />
<rect
width="55"
height="55"
@@ -16,12 +32,23 @@
y="0"
id="rect3269"
style="fill:#282828;fill-opacity:1;fill-rule:nonzero;stroke:none" />
- <path
- d="m 31.1663,14.065103 c 5.3328,0 5.3328,0 5.3328,0 0,0 1.377128,0.828768 1.83315,1.3332 0.465777,0.515223 1.16655,1.9998 1.16655,1.9998 l 0,6.332701 c 0,0 -0.738943,1.231094 -1.16655,1.6665 -0.476472,0.485161 -1.83315,1.3332 -1.83315,1.3332 l -5.6661,0 0,0 0,1.333199 -6.666,0 0,-1.333199 -5.6661,0 c 0,0 -1.356677,-0.848039 -1.83315,-1.3332 -0.427607,-0.435406 -1.16655,-1.6665 -1.16655,-1.6665 l 0,-6.332701 c 0,0 0.700773,-1.484577 1.16655,-1.9998 0.456022,-0.504432 1.83315,-1.3332 1.83315,-1.3332 l 5.3328,0 0,1.6665 7.3326,0 0,-1.6665 z"
- id="path9"
- style="fill:none;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 31.1663,26.936297 c 5.3328,0 5.3328,0 5.3328,0 0,0 1.377128,0.828767 1.83315,1.333199 0.465777,0.515223 1.16655,1.9998 1.16655,1.9998 l 0,6.332701 c 0,0 -0.738943,1.231095 -1.16655,1.666499 -0.476472,0.485162 -1.83315,1.333201 -1.83315,1.333201 l -5.6661,0 0,0 0,1.3332 -6.666,0 0,-1.3332 -5.6661,0 c 0,0 -1.356677,-0.848039 -1.83315,-1.333201 -0.427607,-0.435404 -1.16655,-1.666499 -1.16655,-1.666499 l 0,-6.332701 c 0,0 0.700773,-1.484577 1.16655,-1.9998 0.456022,-0.504432 1.83315,-1.333199 1.83315,-1.333199 l 5.3328,0 0,1.666499 7.3326,0 0,-1.666499 z"
- id="path2559"
- style="fill:none;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <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
index df19fdb..4435cc2 100644
--- a/icons/blockson.svg
+++ b/icons/blockson.svg
@@ -2,89 +2,29 @@
<!-- 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="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="defs5">
- <linearGradient
- x1="0"
- y1="22"
- x2="74"
- y2="22"
- id="linearGradient2431"
- xlink:href="#linearGradient3166"
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
- <linearGradient
- x1="0"
- y1="22"
- x2="74"
- y2="22"
- id="linearGradient2428"
- xlink:href="#linearGradient3166"
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
- <linearGradient
- x1="0"
- y1="22"
- x2="74"
- 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="22"
- x2="74"
- y2="22"
- id="linearGradient2557"
- xlink:href="#linearGradient3166"
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(0.3333,0,0,0.3333,9.2560985,9.9123239)" />
- <linearGradient
- x1="0"
- y1="22"
- x2="74"
- y2="22"
- id="linearGradient2561"
- xlink:href="#linearGradient3166"
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(0.3333,0,0,0.3333,8.962951,22.783517)" />
- <linearGradient
- x1="0"
- y1="22"
- x2="74"
- y2="22"
- id="linearGradient2461"
- xlink:href="#linearGradient3166"
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
- <linearGradient
- x1="0"
- y1="22"
- x2="74"
- y2="22"
- id="linearGradient2463"
- xlink:href="#linearGradient3166"
- gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
- </defs>
+ id="defs4" />
<rect
width="55"
height="55"
@@ -94,15 +34,22 @@
id="rect2839"
style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
<g
- transform="translate(-11.863578,-18.461929)"
- id="g2457">
+ id="g3788">
<path
- d="m 43.029878,32.527032 c 5.3328,0 5.3328,0 5.3328,0 0,0 1.377128,0.828768 1.83315,1.3332 0.465777,0.515223 1.16655,1.9998 1.16655,1.9998 l 0,6.332701 c 0,0 -0.738943,1.231094 -1.16655,1.6665 -0.476472,0.485161 -1.83315,1.3332 -1.83315,1.3332 l -5.6661,0 0,0 0,1.333199 -6.666,0 0,-1.333199 -5.6661,0 c 0,0 -1.356677,-0.848039 -1.83315,-1.3332 -0.427607,-0.435406 -1.16655,-1.6665 -1.16655,-1.6665 l 0,-6.332701 c 0,0 0.700773,-1.484577 1.16655,-1.9998 0.456022,-0.504432 1.83315,-1.3332 1.83315,-1.3332 l 5.3328,0 0,1.6665 7.3326,0 0,-1.6665 z"
- id="path9"
- style="fill:url(#linearGradient2461);fill-opacity:1;stroke:#c0a000;stroke-width:0.66659999;stroke-opacity:1" />
+ 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 43.029878,45.398226 c 5.3328,0 5.3328,0 5.3328,0 0,0 1.377128,0.828767 1.83315,1.333199 0.465777,0.515223 1.16655,1.9998 1.16655,1.9998 l 0,6.332701 c 0,0 -0.738943,1.231095 -1.16655,1.666499 -0.476472,0.485162 -1.83315,1.333201 -1.83315,1.333201 l -5.6661,0 0,0 0,1.3332 -6.666,0 0,-1.3332 -5.6661,0 c 0,0 -1.356677,-0.848039 -1.83315,-1.333201 -0.427607,-0.435404 -1.16655,-1.666499 -1.16655,-1.666499 l 0,-6.332701 c 0,0 0.700773,-1.484577 1.16655,-1.9998 0.456022,-0.504432 1.83315,-1.333199 1.83315,-1.333199 l 5.3328,0 0,1.666499 7.3326,0 0,-1.666499 z"
- id="path2559"
- style="fill:url(#linearGradient2463);fill-opacity:1;stroke:#c0a000;stroke-width:0.66659999;stroke-opacity:1" />
+ 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
index 19f2cd5..c023cf3 100644
--- a/icons/colorsoff.svg
+++ b/icons/colorsoff.svg
@@ -2,12 +2,27 @@
<!-- 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" />
<rect
@@ -17,39 +32,36 @@
y="0"
id="rect3269"
style="fill:#282828;fill-opacity:1;fill-rule:nonzero;stroke:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,-2.8452066,0.4706725)"
- id="path2824"
- style="fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,13.785302,8.1401646)"
- id="path2824-4"
- style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:#ff00ff;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,3.185302,13.640165)"
- id="path2824-6"
- style="fill:#00ff00;fill-opacity:1;fill-rule:evenodd;stroke:#00ff00;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,23.985302,19.740165)"
- id="path2824-69"
- style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,9.085302,26.940165)"
- id="path2824-7"
- style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#ffd000;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,20.485302,-2.2598352)"
- id="path2824-5"
- style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#ffff00;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,-3.6146981,22.540165)"
- id="path2824-54"
- style="fill:#00ffff;fill-opacity:1;fill-rule:evenodd;stroke:#00ffff;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray: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/colorson.svg b/icons/colorson.svg
index 9cd4ef1..42ce7c7 100644
--- a/icons/colorson.svg
+++ b/icons/colorson.svg
@@ -2,12 +2,27 @@
<!-- 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
@@ -19,41 +34,35 @@
id="rect2839"
style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
<g
- id="g3702">
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,-2.8452066,0.4706725)"
- id="path2824"
- style="fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,13.785302,8.1401646)"
- id="path2824-4"
- style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:#ff00ff;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,3.185302,13.640165)"
- id="path2824-6"
- style="fill:#00ff00;fill-opacity:1;fill-rule:evenodd;stroke:#00ff00;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,23.985302,19.740165)"
- id="path2824-69"
- style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,9.085302,26.940165)"
- id="path2824-7"
- style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#ffd000;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,20.485302,-2.2598352)"
- id="path2824-5"
- style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#ffff00;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="m 17.245763,13.050847 a 2.9131355,3.0296609 0 1 1 -5.826271,0 2.9131355,3.0296609 0 1 1 5.826271,0 z"
- transform="matrix(1.2080617,0,0,1.1615978,-3.6146981,22.540165)"
- id="path2824-54"
- style="fill:#00ffff;fill-opacity:1;fill-rule:evenodd;stroke:#00ffff;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ 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/extrasoff.svg b/icons/extrasoff.svg
deleted file mode 100644
index 47547fc..0000000
--- a/icons/extrasoff.svg
+++ /dev/null
@@ -1,41 +0,0 @@
-<?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="defs4" />
- <rect
- width="55"
- height="55"
- x="0"
- y="0"
- id="rect3269"
- style="fill:#282828;fill-opacity:1;fill-rule:nonzero;stroke:none" />
- <g
- transform="translate(51.861336,-0.235324)"
- id="g3247"
- style="fill:none;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
- <path
- d="M -36.1417,31.566802 l 0.114372,5.261133 11.437247,6.290486 0.228745,-13.495951"
- id="path2463"
- 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 -24.132592,29.736843 l 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.29999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="M -41.860324,27.563765 l 5.604251,-5.146761 -5.832996,-2.973684 12.123482,-7.091093 5.718623,3.316801 5.489879,-3.088056 12.1234818,7.319838 -5.8329958,2.401822 5.4898784,5.032388 -11.3228744,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.29999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <path
- d="M -35.455466,22.188259 l 10.751013,-6.747976 11.780364,6.862349 -11.322875,6.747975 -11.208502,-6.862348 z"
- id="path2461"
- style="fill:none;stroke:#ffffff;stroke-width:2.29999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- </g>
-</svg>
diff --git a/icons/extrason.svg b/icons/extrason.svg
deleted file mode 100644
index 3d2cd85..0000000
--- a/icons/extrason.svg
+++ /dev/null
@@ -1,42 +0,0 @@
-<?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="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
- transform="translate(51.861336,-0.235324)"
- id="g3247"
- style="stroke:#800000;stroke-opacity:1">
- <path
- d="M -36.1417,31.566802 l 0.114372,5.261133 11.437247,6.290486 0.228745,-13.495951"
- id="path2463"
- style="fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#800000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
- <path
- d="M -24.132592,29.736843 l 0.05719,13.381578 11.723178,-7.548583 0,-4.689272 -5.947368,3.545548"
- id="path2465"
- style="fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#800000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
- <path
- d="M -41.860324,27.563765 l 5.604251,-5.146761 -5.832996,-2.973684 12.123482,-7.091093 5.718623,3.316801 5.489879,-3.088056 12.1234818,7.319838 -5.8329958,2.401822 5.4898784,5.032388 -11.3228744,7.205466 -5.947369,-5.261134 -6.290485,5.261134 -11.322875,-6.976721 z"
- id="path2459"
- style="fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#800000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
- <path
- d="M -35.455466,22.188259 l 10.751013,-6.747976 11.780364,6.862349 -11.322875,6.747975 -11.208502,-6.862348 z"
- id="path2461"
- style="fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:#800000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;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/images/cameraoff.svg b/images/cameraoff.svg
index 3c1fb7b..54d9c86 100755..100644
--- a/images/cameraoff.svg
+++ b/images/cameraoff.svg
@@ -1,37 +1,15 @@
-<?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="42"
- viewBox="0 0 55 42"
- 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(3,0,0,3,-55.015,-67.788)"
- id="g3016"><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:#010101;display:inline" /><circle
- cx="27.504999"
- cy="29.597"
- r="1.5140001"
- id="circle15"
- style="fill:#010101;display:inline" /></g></svg> \ No newline at end of file
+<?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
index 0c3aa44..690e940 100755
--- a/images/camerasmall.svg
+++ b/images/camerasmall.svg
@@ -8,9 +8,9 @@
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
- width="26.163"
- height="14.262004"
- viewBox="0 0 26.163 14.262004"
+ width="26"
+ height="18.668091"
+ viewBox="0 0 26 18.668091"
id="svg2"
xml:space="preserve"><metadata
id="metadata29"><rdf:RDF><cc:Work
@@ -27,14 +27,25 @@
</defs><g
- transform="matrix(3,0,0,3,-69.2385,-78.514498)"
- id="g2995"><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"
+ 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/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/extra/__init__.py b/plugins/__init__.py
index e69de29..e69de29 100644
--- a/extra/__init__.py
+++ b/plugins/__init__.py
diff --git a/devices/__init__.py b/plugins/audio_sensors/__init__.py
index e69de29..e69de29 100644
--- a/devices/__init__.py
+++ b/plugins/audio_sensors/__init__.py
diff --git a/plugins/audio_sensors/audio_sensors.py b/plugins/audio_sensors/audio_sensors.py
new file mode 100644
index 0000000..22298a0
--- /dev/null
+++ b/plugins/audio_sensors/audio_sensors.py
@@ -0,0 +1,321 @@
+#!/usr/bin/env python
+#Copyright (c) 2011 Walter Bender
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 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
+
+from gettext import gettext as _
+
+try:
+ from numpy import append
+ from numpy.fft import rfft
+ PITCH_AVAILABLE = True
+except:
+ PITCH_AVAILABLE = False
+
+from plugins.plugin import Plugin
+
+from plugins.audio_sensors.audiograb import AudioGrab_Unknown, AudioGrab_XO1, \
+ AudioGrab_XO15, SENSOR_DC_NO_BIAS, SENSOR_DC_BIAS
+
+from plugins.audio_sensors.ringbuffer import RingBuffer1d
+
+from TurtleArt.tapalette import make_palette
+from TurtleArt.taconstants import XO1, XO15
+from TurtleArt.talogo import primitive_dictionary
+from TurtleArt.tautils import debug_output
+
+import logging
+_logger = logging.getLogger('turtleart-activity audio sensors plugin')
+
+
+def _avg(array, abs_value=False):
+ ''' Calc. the average value of an array '''
+ if len(array) == 0:
+ return 0
+ array_sum = 0
+ if abs_value:
+ for a in array:
+ array_sum += abs(a)
+ else:
+ for a in array:
+ array_sum += a
+ return float(array_sum) / len(array)
+
+
+class Audio_sensors(Plugin):
+
+ def __init__(self, parent):
+ self._parent = parent
+ self._status = True # TODO: test for audio device
+ # These flags are referenced by audiograb
+ self.hw = self._parent.hw
+ self.running_sugar = self._parent.running_sugar
+
+ def setup(self):
+ # set up audio-sensor-specific blocks
+ self.max_samples = 1500
+ self.input_step = 1
+
+ self.ringbuffer = RingBuffer1d(self.max_samples, dtype='int16')
+
+ palette = make_palette('sensor',
+ colors=["#FF6060", "#A06060"],
+ help_string=_('Palette of sensor blocks'))
+
+ primitive_dictionary['sound'] = self.prim_sound
+ primitive_dictionary['volume'] = self.prim_volume
+ if self._status:
+ palette.add_block('sound',
+ style='box-style',
+ label=_('sound'),
+ help_string=_('raw microphone input signal'),
+ value_block=True,
+ prim_name='sound')
+
+ palette.add_block('volume',
+ style='box-style',
+ label=_('loudness'),
+ help_string=_('microphone input volume'),
+ value_block=True,
+ prim_name='volume')
+ else:
+ palette.add_block('sound',
+ hidden=True,
+ style='box-style',
+ label=_('sound'),
+ help_string=_('raw microphone input signal'),
+ value_block=True,
+ prim_name='sound')
+ palette.add_block('volume',
+ hidden=True,
+ style='box-style',
+ label=_('loudness'),
+ help_string=_('microphone input volume'),
+ value_block=True,
+ prim_name='volume')
+
+ self._parent.lc.def_prim(
+ 'sound', 0, lambda self: primitive_dictionary['sound']())
+ self._parent.lc.def_prim(
+ 'volume', 0, lambda self: primitive_dictionary['volume']())
+
+ primitive_dictionary['pitch'] = self.prim_pitch
+ if PITCH_AVAILABLE and self._status:
+ palette.add_block('pitch',
+ style='box-style',
+ label=_('pitch'),
+ help_string=_('microphone input pitch'),
+ value_block=True,
+ prim_name='pitch')
+ else:
+ palette.add_block('pitch',
+ hidden=True,
+ style='box-style',
+ label=_('pitch'),
+ help_string=_('microphone input pitch'),
+ value_block=True,
+ prim_name='pitch')
+ self._parent.lc.def_prim('pitch', 0,
+ lambda self: primitive_dictionary['pitch']())
+
+ primitive_dictionary['resistance'] = self.prim_resistance
+ primitive_dictionary['voltage'] = self.prim_voltage
+ if self.hw in [XO1, XO15] and self._status:
+ if self.hw == XO1:
+ self.voltage_gain = 0.00002225
+ self.voltage_bias = 1.140
+ elif self.hw == XO15:
+ self.voltage_gain = -0.0001471
+ self.voltage_bias = 1.695
+ palette.add_block('resistance',
+ style='box-style',
+ label=_('resistance'),
+ help_string=_('microphone input resistance'),
+ value_block=True,
+ prim_name='resistance')
+ palette.add_block('voltage',
+ style='box-style',
+ label=_('voltage'),
+ help_string=_('microphone input voltage'),
+ value_block=True,
+ prim_name='voltage')
+ else:
+ palette.add_block('resistance',
+ hidden=True,
+ style='box-style',
+ label=_('resistance'),
+ help_string=_('microphone input resistance'),
+ value_block=True,
+ prim_name='resistance')
+ palette.add_block('voltage',
+ hidden=True,
+ style='box-style',
+ label=_('voltage'),
+ help_string=_('microphone input voltage'),
+ value_block=True,
+ prim_name='resistance')
+ self._parent.lc.def_prim(
+ 'resistance', 0, lambda self: primitive_dictionary['resistance']())
+ self._parent.lc.def_prim(
+ 'voltage', 0, lambda self: primitive_dictionary['voltage']())
+
+ self.audio_started = False
+
+ def start(self):
+ # This gets called by the start button
+ if not self._status:
+ return
+ ''' Start grabbing audio if there is an audio block in use '''
+ if len(self._parent.block_list.get_similar_blocks('block',
+ ['volume', 'sound', 'pitch', 'resistance', 'voltage'])) > 0:
+ if self.audio_started:
+ self.audiograb.resume_grabbing()
+ else:
+ if self.hw == XO15:
+ self.audiograb = AudioGrab_XO15(self.new_buffer, self)
+ elif self.hw == XO1:
+ self.audiograb = AudioGrab_XO1(self.new_buffer, self)
+ else:
+ self.audiograb = AudioGrab_Unknown(self.new_buffer, self)
+ self.audiograb.start_grabbing()
+ self.audio_started = True
+ self._update_audio_mode()
+
+ def new_buffer(self, buf):
+ ''' Append a new buffer to the ringbuffer '''
+ self.ringbuffer.append(buf)
+ return True
+
+ def _update_audio_mode(self):
+ ''' If there are sensor blocks, set the appropriate audio mode '''
+ if not hasattr(self._parent.lc, 'value_blocks_to_update'):
+ return
+ for name in ['sound', 'volume', 'pitch']:
+ if name in self._parent.lc.value_blocks_to_update:
+ if len(self._parent.lc.value_blocks_to_update[name]) > 0:
+ self.audiograb.set_sensor_type()
+ return
+ if 'resistance' in self._parent.lc.value_blocks_to_update:
+ if len(self._parent.lc.value_blocks_to_update['resistance']) > 0:
+ self.audiograb.set_sensor_type(SENSOR_DC_BIAS)
+ return
+ if 'voltage' in self._parent.lc.value_blocks_to_update:
+ if len(self._parent.lc.value_blocks_to_update['voltage']) > 0:
+ self.audiograb.set_sensor_type(SENSOR_DC_NO_BIAS)
+ return
+
+ def stop(self):
+ # This gets called by the stop button
+ if self._status and self.audio_started:
+ self.audiograb.pause_grabbing()
+
+ def goto_background(self):
+ # This gets called when your process is sent to the background
+ # TODO: handle this case
+ pass
+
+ def return_to_foreground(self):
+ # This gets called when your process returns from the background
+ # TODO: handle this case
+ pass
+
+ def quit(self):
+ # This gets called by the quit button
+ if self._status and self.audio_started:
+ self.audiograb.stop_grabbing()
+
+ def _status_report(self):
+ debug_output('Reporting audio sensor status: %s' % (str(self._status)))
+ return self._status
+
+ # Block primitives used in talogo
+
+ def prim_volume(self):
+ ''' return mic in value '''
+ #TODO: Adjust gain for different HW
+ if not self._status:
+ return 0
+ buf = self.ringbuffer.read(None, self.input_step)
+ if len(buf) > 0:
+ volume = float(_avg(buf, abs_value=True))
+ self._parent.lc.update_label_value('volume', volume)
+ return volume
+ else:
+ return 0
+
+ def prim_sound(self):
+ ''' return raw mic in value '''
+ if not self._status:
+ return 0
+ buf = self.ringbuffer.read(None, self.input_step)
+ if len(buf) > 0:
+ sound = float(buf[0])
+ self._parent.lc.update_label_value('sound', sound)
+ return sound
+ else:
+ return 0
+
+ def prim_pitch(self):
+ ''' return index of max value in fft of mic in values '''
+ if not PITCH_AVAILABLE or not self._status:
+ return 0
+ buf = []
+ for i in range(4):
+ buf = append(buf, self.ringbuffer.read(None, self.input_step))
+ if len(buf) > 0:
+ r = []
+ for j in rfft(buf):
+ r.append(abs(j))
+ # Convert output to Hertz
+ pitch = r.index(max(r)) * 48000 / len(buf)
+ self._parent.lc.update_label_value('pitch', pitch)
+ return pitch
+ else:
+ return 0
+
+ def prim_resistance(self):
+ ''' return resistance sensor value '''
+ if not self.hw in [XO1, XO15] or not self._status:
+ return 0
+ buf = self.ringbuffer.read(None, self.input_step)
+ if len(buf) > 0:
+ # See <http://bugs.sugarlabs.org/ticket/552#comment:7>
+ # TODO: test this calibration on XO 1.5
+ if self.hw == XO1:
+ resistance = 2.718 ** ((float(_avg(buf)) * 0.000045788) + \
+ 8.0531)
+ else:
+ avg_buf = float(_avg(buf))
+ if avg_buf > 0:
+ resistance = (420000000 / avg_buf) - 13500
+ else:
+ resistance = 420000000
+ self._parent.lc.update_label_value('resistance', resistance)
+ return resistance
+ else:
+ return 0
+
+ def prim_voltage(self):
+ ''' return voltage sensor value '''
+ if not self.hw in [XO1, XO15] or not self._status:
+ return 0
+ buf = self.ringbuffer.read(None, self.input_step)
+ if len(buf) > 0:
+ # See <http://bugs.sugarlabs.org/ticket/552#comment:7>
+ voltage = float(_avg(buf)) * self.voltage_gain + self.voltage_bias
+ self._parent.lc.update_label_value('voltage', voltage)
+ return voltage
+ else:
+ return 0
diff --git a/TurtleArt/audiograb.py b/plugins/audio_sensors/audiograb.py
index 3ecdc11..84e0c8d 100644
--- a/TurtleArt/audiograb.py
+++ b/plugins/audio_sensors/audiograb.py
@@ -21,6 +21,11 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+SENSOR_AC_NO_BIAS = 'external'
+SENSOR_AC_BIAS = 'sound'
+SENSOR_DC_NO_BIAS = 'voltage'
+SENSOR_DC_BIAS = 'resistance'
+
import pygst
import gst
import gst.interfaces
@@ -43,25 +48,19 @@ QUIT_DC_MODE_ENABLE = False
QUIT_CAPTURE_GAIN = 100
QUIT_BIAS = True
-import logging
-
-_logger = logging.getLogger('TurtleArt')
-_logger.setLevel(logging.DEBUG)
-logging.basicConfig()
-
-from taconstants import SENSOR_AC_NO_BIAS, SENSOR_AC_BIAS, SENSOR_DC_NO_BIAS, \
- SENSOR_DC_BIAS, XO1
+from TurtleArt.taconstants import XO1
+from TurtleArt.tautils import debug_output
class AudioGrab:
""" The interface between measure and the audio device """
- def __init__(self, callable1, activity):
+ def __init__(self, callable1, parent):
""" Initialize the class: callable1 is a data buffer;
- activity is the parent class"""
+ parent is the parent class"""
self.callable1 = callable1
- self.activity = activity
+ self.parent = parent
self.sensor = None
self.temp_buffer = [0]
@@ -105,8 +104,9 @@ class AudioGrab:
# Query the available controls
try: # F11+
- _logger.debug('controls: %r', [t.props.untranslated_label \
- for t in self._mixer.list_tracks()])
+ debug_output('controls: %r' % \
+ ([t.props.untranslated_label for t in self._mixer.list_tracks()]),
+ self.parent.running_sugar)
self._dc_control = self._find_control(['dc mode'])
self._mic_bias_control = self._find_control(['mic bias',
'dc input bias',
@@ -149,7 +149,7 @@ class AudioGrab:
"""The function that is called whenever new data is available
This is the signal handler for the handoff signal"""
if buffer is None:
- _logger.debug('audiograb buffer is None')
+ debug_output('audiograb buffer is None', self.parent.running_sugar)
return False
temp_buffer = fromstring(buffer, 'int16')
@@ -234,8 +234,9 @@ class AudioGrab:
controls.sort(key=lambda e: e[1])
if controls:
- _logger.debug("found control: %s" %\
- (str(controls[0][0].props.untranslated_label)))
+ debug_output('Found control: %s' % \
+ (str(controls[0][0].props.untranslated_label)),
+ self.parent.running_sugar)
return controls[0][0]
return None
@@ -262,8 +263,9 @@ class AudioGrab:
return default
value = bool(control.flags & gst.interfaces.MIXER_TRACK_MUTE)
- _logger.debug('Getting %s (%s) mute status: %r', name,
- control.props.untranslated_label, value)
+ debug_output('Getting %s (%s) mute status: %r' % (name,
+ control.props.untranslated_label, value),
+ self.parent.running_sugar)
return value
def _set_mute(self, control, name, value):
@@ -272,8 +274,9 @@ class AudioGrab:
return
self._mixer.set_mute(control, value)
- _logger.debug('Set mute for %s (%s) to %r', name,
- control.props.untranslated_label, value)
+ debug_output('Set mute for %s (%s) to %r' % (name,
+ control.props.untranslated_label, value),
+ self.parent.running_sugar)
def _get_volume(self, control, name):
"""Get volume of a control and convert to a scale of 0-100"""
@@ -311,14 +314,14 @@ class AudioGrab:
def mute_master(self):
"""Mutes the Master Control"""
- if not self._hardwired and self.activity.hw != XO1:
+ if not self._hardwired and self.parent.hw != XO1:
self._set_mute(self._master_control, 'Master', True)
else:
self.amixer_set('Master', False)
def unmute_master(self):
"""Unmutes the Master Control"""
- if not self._hardwired and self.activity.hw != XO1:
+ if not self._hardwired and self.parent.hw != XO1:
self._set_mute(self._master_control, 'Master', True)
else:
self.amixer_set('Master', True)
@@ -347,7 +350,7 @@ class AudioGrab:
def set_bias(self, bias_state=False):
"""Enables / disables bias voltage."""
- if not self._hardwired and self.activity.hw != XO1:
+ if not self._hardwired and self.parent.hw != XO1:
if self._mic_bias_control is None:
return
# if not isinstance(self._mic_bias_control,
@@ -403,7 +406,7 @@ class AudioGrab:
def set_dc_mode(self, dc_mode=False):
"""Sets the DC Mode Enable control
pass False to mute and True to unmute"""
- if not self._hardwired and self.activity.hw != XO1:
+ if not self._hardwired and self.parent.hw != XO1:
if self._dc_control is not None:
self._set_mute(self._dc_control, 'DC mode', not dc_mode)
else:
@@ -461,7 +464,8 @@ class AudioGrab:
return self._get_mute(self._mic_boost_control, 'Mic Boost',
False)
current = self._mixer.get_volume(self._mic_boost_control)
- _logger.debug('current: %s' % (str(current)))
+ debug_output('current: %s' % (str(current)),
+ self.parent.running_sugar)
if current != self._mic_boost_control.min_volume:
return True
return False
@@ -480,7 +484,7 @@ class AudioGrab:
"""Sets the Capture gain slider settings
capture_val must be given as an integer between 0 and 100 indicating the
percentage of the slider to be set"""
- if not self._hardwired and self.activity.hw != XO1:
+ if not self._hardwired and self.parent.hw != XO1:
if self._capture_control is not None:
self._set_volume(self._capture_control, 'Capture', capture_val)
else:
@@ -506,7 +510,7 @@ class AudioGrab:
"""Sets the MIC gain slider settings
mic_val must be given as an integer between 0 and 100 indicating the
percentage of the slider to be set"""
- if not self._hardwired and self.activity.hw != XO1:
+ if not self._hardwired and self.parent.hw != XO1:
self._set_volume(self._mic_gain_control, 'Mic', mic_val)
else:
os.system("amixer set Mic " + str(mic_val) + "%")
@@ -546,10 +550,9 @@ class AudioGrab:
SENSOR_DC_BIAS: (True, True, 0, False)
}
mode, bias, gain, boost = PARAMETERS[sensor_type]
- _logger.debug("====================================")
- _logger.debug("Set Sensor Type to %s" % (str(sensor_type)))
+ debug_output('Set Sensor Type to %s' % (str(sensor_type)),
+ self.parent.running_sugar)
self._set_sensor_type(mode, bias, gain, boost)
- _logger.debug("====================================")
def _set_sensor_type(self, mode=None, bias=None, gain=None, boost=None):
"""Helper to modify (some) of the sensor settings."""
@@ -598,11 +601,10 @@ class AudioGrab_XO15(AudioGrab):
SENSOR_DC_NO_BIAS: (True, False, 80, False),
SENSOR_DC_BIAS: (True, True, 90, False)
}
- _logger.debug("====================================")
- _logger.debug("Set Sensor Type to %s" % (str(sensor_type)))
+ debug_output('Set Sensor Type to %s' % (str(sensor_type)),
+ self.parent.running_sugar)
mode, bias, gain, boost = PARAMETERS[sensor_type]
self._set_sensor_type(mode, bias, gain, boost)
- _logger.debug("====================================")
class AudioGrab_Unknown(AudioGrab):
@@ -615,8 +617,7 @@ class AudioGrab_Unknown(AudioGrab):
SENSOR_DC_NO_BIAS: (True, False, 80, False),
SENSOR_DC_BIAS: (True, True, 90, False)
}
- _logger.debug("====================================")
- _logger.debug("Set Sensor Type to %s" % (str(sensor_type)))
+ debug_output('Set Sensor Type to %s' % (str(sensor_type)),
+ self.parent.running_sugar)
mode, bias, gain, boost = PARAMETERS[sensor_type]
self._set_sensor_type(mode, bias, gain, boost)
- _logger.debug("====================================")
diff --git a/icons/sensoroff.svg b/plugins/audio_sensors/icons/sensoroff.svg
index ec55f03..ec55f03 100644
--- a/icons/sensoroff.svg
+++ b/plugins/audio_sensors/icons/sensoroff.svg
diff --git a/icons/sensoron.svg b/plugins/audio_sensors/icons/sensoron.svg
index e902656..e902656 100644
--- a/icons/sensoron.svg
+++ b/plugins/audio_sensors/icons/sensoron.svg
diff --git a/TurtleArt/ringbuffer.py b/plugins/audio_sensors/ringbuffer.py
index 2afb5c9..2afb5c9 100644
--- a/TurtleArt/ringbuffer.py
+++ b/plugins/audio_sensors/ringbuffer.py
diff --git a/devices/__init__.py b/plugins/camera_sensor/__init__.py
index e69de29..e69de29 100644
--- a/devices/__init__.py
+++ b/plugins/camera_sensor/__init__.py
diff --git a/plugins/camera_sensor/camera_sensor.py b/plugins/camera_sensor/camera_sensor.py
new file mode 100644
index 0000000..a9f8684
--- /dev/null
+++ b/plugins/camera_sensor/camera_sensor.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+#Copyright (c) 2011 Walter Bender
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 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 gst
+import gtk
+from fcntl import ioctl
+import os
+from gettext import gettext as _
+
+from plugins.camera_sensor.tacamera import Camera
+from plugins.camera_sensor.v4l2 import v4l2_control, V4L2_CID_AUTOGAIN, \
+ VIDIOC_G_CTRL, VIDIOC_S_CTRL
+
+from plugins.plugin import Plugin
+
+from TurtleArt.tapalette import make_palette
+from TurtleArt.talogo import media_blocks_dictionary, primitive_dictionary
+from TurtleArt.tautils import get_path, debug_output
+
+
+class Camera_sensor(Plugin):
+
+ def __init__(self, parent):
+ self._parent = parent
+ self._status = False
+
+ v4l2src = gst.element_factory_make('v4l2src')
+ if v4l2src.props.device_name is not None:
+
+ if self._parent.running_sugar:
+ self._imagepath = get_path(self._parent.activity,
+ 'data/turtlepic.png')
+ else:
+ self._imagepath = '/tmp/turtlepic.png'
+
+ self._camera = None
+ self._status = True
+
+ def setup(self):
+ palette = make_palette('sensor',
+ colors=["#FF6060", "#A06060"],
+ help_string=_('Palette of sensor blocks'))
+
+ # set up camera-specific blocks
+ primitive_dictionary['luminance'] = self.prim_read_camera
+ primitive_dictionary['read_camera'] = self.prim_read_camera
+ media_blocks_dictionary['camera'] = self.prim_take_picture
+
+ if self._status:
+ palette.add_block('luminance',
+ style='box-style',
+ label=_('brightness'),
+ help_string=_('light level detected by camera'),
+ value_block=True,
+ prim_name='luminance')
+ self._parent.lc.def_prim('luminance', 0,
+ lambda self: primitive_dictionary['luminance'](
+ luminance_only=True))
+
+ # Depreciated block
+ palette.add_block('read_camera',
+ hidden=True,
+ style='box-style',
+ label=_('brightness'),
+ help_string=_('Average RGB color from camera \
+is pushed to the stack'),
+ value_block=True,
+ prim_name='luminance')
+ self._parent.lc.def_prim('read_camera', 0,
+ lambda self: primitive_dictionary['read_camera']())
+
+ palette.add_block('camera',
+ style='box-style-media',
+ label=' ',
+ default='CAMERA',
+ help_string=_('camera output'),
+ content_block=True)
+ else: # No camera, so blocks should do nothing
+ palette.add_block('luminance',
+ hidden=True,
+ style='box-style',
+ label=_('brightness'),
+ help_string=_('light level detected by camera'),
+ value_block=True,
+ prim_name='luminance')
+ self._parent.lc.def_prim('luminance', 0,
+ lambda self: primitive_dictionary['luminance'](
+ luminance_only=True))
+
+ # Depreciated block
+ palette.add_block('read_camera',
+ hidden=True,
+ style='box-style',
+ label=_('brightness'),
+ help_string=_('Average RGB color from camera \
+is pushed to the stack'),
+ value_block=True,
+ prim_name='luminance')
+ self._parent.lc.def_prim('read_camera', 0,
+ lambda self: primitive_dictionary['read_camera']())
+
+ palette.add_block('camera',
+ hidden=True,
+ style='box-style-media',
+ label=' ',
+ default='CAMERA',
+ help_string=_('camera output'),
+ content_block=True)
+
+ def start(self):
+ ''' Initialize the camera if there is an camera block in use '''
+ if len(self._parent.block_list.get_similar_blocks('block',
+ ['camera', 'read_camera', 'luminance'])) > 0:
+ if self._camera is None:
+ self._camera = Camera(self._imagepath)
+
+ def stop(self):
+ ''' This gets called by the stop button '''
+ if self._status and self._camera is not None:
+ self._camera.stop_camera_input()
+
+ def _status_report(self):
+ debug_output('Reporting camera status: %s' % (str(self._status)))
+ return self._status
+
+ # Block primitives used in talogo
+
+ def prim_take_picture(self):
+ if self._status:
+ ''' method called by media block '''
+ self._camera.save_camera_input_to_file()
+ self._camera.stop_camera_input()
+ self._parent.lc.filepath = self._imagepath
+ else:
+ self._parent.lc.filepath = os.path.join(
+ self._parent.path, 'samples', 'images', 'me.jpg')
+
+ def prim_read_camera(self, luminance_only=False):
+ """ Read average pixel from camera and push b, g, r to the stack """
+ if not self._status:
+ if luminance_only:
+ return -1
+ else:
+ self._parent.lc.heap.append(-1)
+ self._parent.lc.heap.append(-1)
+ self._parent.lc.heap.append(-1)
+ return
+
+ pixbuf = None
+ array = None
+ w = 4
+ h = 3
+ if self._status:
+ try:
+ self._video_capture_device = open('/dev/video0', 'rw')
+ except:
+ self._video_capture_device = None
+ debug_output('video capture device not available')
+
+ if self._video_capture_device is not None:
+ self._ag_control = v4l2_control(V4L2_CID_AUTOGAIN)
+ try:
+ ioctl(self._video_capture_device, VIDIOC_G_CTRL,
+ self._ag_control)
+ self._ag_control.value = 0 # disable AUTOGAIN
+ ioctl(self._video_capture_device, VIDIOC_S_CTRL,
+ self._ag_control)
+ except:
+ # debug_output('AUTOGAIN control not available')
+ pass
+
+ if self._video_capture_device is not None:
+ self._video_capture_device.close()
+
+ self._camera.save_camera_input_to_file()
+ self._camera.stop_camera_input()
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self._imagepath,
+ w, h)
+ try:
+ array = pixbuf.get_pixels()
+ except:
+ array = None
+
+ if array is not None:
+ length = len(array) / 3
+ r, g, b, i = 0, 0, 0, 0
+ for j in range(length):
+ r += ord(array[i])
+ i += 1
+ g += ord(array[i])
+ i += 1
+ b += ord(array[i])
+ i += 1
+ if luminance_only:
+ lum = int((r * 0.3 + g * 0.6 + b * 0.1) / length)
+ self._parent.lc.update_label_value('luminance', lum)
+ return lum
+ else:
+ self._parent.lc.heap.append(int((b / length)))
+ self._parent.lc.heap.append(int((g / length)))
+ self._parent.lc.heap.append(int((r / length)))
+ else:
+ if luminance_only:
+ return -1
+ else:
+ self._parent.lc.heap.append(-1)
+ self._parent.lc.heap.append(-1)
+ self._parent.lc.heap.append(-1)
diff --git a/icons/sensoroff.svg b/plugins/camera_sensor/icons/sensoroff.svg
index ec55f03..ec55f03 100644
--- a/icons/sensoroff.svg
+++ b/plugins/camera_sensor/icons/sensoroff.svg
diff --git a/icons/sensoron.svg b/plugins/camera_sensor/icons/sensoron.svg
index e902656..e902656 100644
--- a/icons/sensoron.svg
+++ b/plugins/camera_sensor/icons/sensoron.svg
diff --git a/TurtleArt/tacamera.py b/plugins/camera_sensor/tacamera.py
index 2177288..15824a1 100644
--- a/TurtleArt/tacamera.py
+++ b/plugins/camera_sensor/tacamera.py
@@ -23,14 +23,25 @@
import gst, time
GST_PIPE = ['v4l2src', 'ffmpegcolorspace', 'pngenc']
+# GST_PIPE = ['v4l2src', 'ffmpegcolorspace', 'gdkpixbufsink']
class Camera():
""" A class for representing the video camera """
def __init__(self, imagepath):
+ # self.imagepath = imagepath
GST_PIPE.append('filesink location=%s' % imagepath)
self.pipe = gst.parse_launch('!'.join(GST_PIPE))
self.bus = self.pipe.get_bus()
+ # self.bus.add_signal_watch()
+ # self.bus.connect('message', self._on_message)
+
+ def _on_message(self, bus, message):
+ ''' We get a message if a pixbuf is available '''
+ if message.structure is not None:
+ print message.structure.get_name()
+ if message.structure.get_name() == 'pixbuf':
+ message.structure['pixbuf'].save(self.imagepath, 'png')
def save_camera_input_to_file(self):
""" Grab a frame from the camera """
diff --git a/TurtleArt/v4l2.py b/plugins/camera_sensor/v4l2.py
index 9c052fd..9c052fd 100644
--- a/TurtleArt/v4l2.py
+++ b/plugins/camera_sensor/v4l2.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/devices/__init__.py b/plugins/rfid/__init__.py
index e69de29..e69de29 100644
--- a/devices/__init__.py
+++ b/plugins/rfid/__init__.py
diff --git a/devices/device.py b/plugins/rfid/device.py
index 04a82b2..04a82b2 100644
--- a/devices/device.py
+++ b/plugins/rfid/device.py
diff --git a/icons/sensoroff.svg b/plugins/rfid/icons/sensoroff.svg
index ec55f03..ec55f03 100644
--- a/icons/sensoroff.svg
+++ b/plugins/rfid/icons/sensoroff.svg
diff --git a/icons/sensoron.svg b/plugins/rfid/icons/sensoron.svg
index e902656..e902656 100644
--- a/icons/sensoron.svg
+++ b/plugins/rfid/icons/sensoron.svg
diff --git a/plugins/rfid/rfid.py b/plugins/rfid/rfid.py
new file mode 100644
index 0000000..e607eba
--- /dev/null
+++ b/plugins/rfid/rfid.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python
+#Copyright (C) 2010,11 Emiliano Pastorino <epastorino@plan.ceibal.edu.uy>
+#Copyright (c) 2011 Walter Bender
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 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 dbus
+from dbus.mainloop.glib import DBusGMainLoop
+from gettext import gettext as _
+
+from plugins.rfid.rfidutils import strhex2bin, strbin2dec, find_device
+from plugins.plugin import Plugin
+
+from TurtleArt.tapalette import make_palette
+from TurtleArt.talogo import primitive_dictionary
+from TurtleArt.tautils import debug_output
+
+import logging
+_logger = logging.getLogger('turtleart-activity RFID plugin')
+
+HAL_SERVICE = 'org.freedesktop.Hal'
+HAL_MGR_PATH = '/org/freedesktop/Hal/Manager'
+HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager'
+HAL_DEV_IFACE = 'org.freedesktop.Hal.Device'
+REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\
+ 'a-z,A-Z,0-9,_]*serial_usb_[0-9]'
+
+
+class Rfid(Plugin):
+
+ def __init__(self, parent):
+ self._parent = parent
+ self._status = False
+
+ """
+ The following code will initialize a USB RFID reader. Please note that
+ in order to make this initialization function work, it is necessary to
+ set the permission for the ttyUSB device to 0666. You can do this by
+ adding a rule to /etc/udev/rules.d
+
+ As root (using sudo or su), copy the following text into a new file in
+ /etc/udev/rules.d/94-ttyUSB-rules
+
+ KERNEL=="ttyUSB[0-9]",MODE="0666"
+
+ You only have to do this once.
+ """
+
+ self.rfid_connected = False
+ self.rfid_device = find_device()
+ self.rfid_idn = ''
+
+ if self.rfid_device is not None:
+ _logger.info("RFID device found")
+ self.rfid_connected = self.rfid_device.do_connect()
+ if self.rfid_connected:
+ self.rfid_device.connect("tag-read", self._tag_read_cb)
+ self.rfid_device.connect("disconnected", self._disconnected_cb)
+
+ loop = DBusGMainLoop()
+ bus = dbus.SystemBus(mainloop=loop)
+ hmgr_iface = dbus.Interface(bus.get_object(HAL_SERVICE,
+ HAL_MGR_PATH), HAL_MGR_IFACE)
+
+ hmgr_iface.connect_to_signal('DeviceAdded', self._device_added_cb)
+
+ self._status = True
+
+ def setup(self):
+ # set up RFID-specific blocks
+ primitive_dictionary['rfid'] = self.prim_read_rfid
+ palette = make_palette('sensor',
+ colors=["#FF6060", "#A06060"],
+ help_string=_('Palette of sensor blocks'))
+
+ if self._status:
+ palette.add_block('rfid',
+ style='box-style',
+ label=_('RFID'),
+ help_string=_('read value from RFID device'),
+ value_block=True,
+ prim_name='rfid')
+ else:
+ palette.add_block('rfid',
+ hidden=True,
+ style='box-style',
+ label=_('RFID'),
+ help_string=_('read value from RFID device'),
+ value_block=True,
+ prim_name='rfid')
+
+ self._parent.lc.def_prim(
+ 'rfid', 0, lambda self: primitive_dictionary['rfid']())
+
+ def _status_report(self):
+ debug_output('Reporting RFID status: %s' % (str(self._status)))
+ return self._status
+
+ def _device_added_cb(self, path):
+ """
+ Called from hal connection when a new device is plugged.
+ """
+ if not self.rfid_connected:
+ self.rfid_device = find_device()
+ _logger.debug("DEVICE_ADDED: %s" % self.rfid_device)
+ if self.rfid_device is not None:
+ _logger.debug("DEVICE_ADDED: RFID device is not None!")
+ self.rfid_connected = self._device.do_connect()
+ if self.rfid_connected:
+ _logger.debug("DEVICE_ADDED: Connected!")
+ self.rfid_device.connect("tag-read", self._tag_read_cb)
+ self.rfid_device.connect("disconnected", self._disconnected_cb)
+
+ def _disconnected_cb(self, device, text):
+ """
+ Called when the device is disconnected.
+ """
+ self.rfid_connected = False
+ self.rfid_device = None
+
+ def _tag_read_cb(self, device, tagid):
+ """
+ Callback for "tag-read" signal. Receives the read tag id.
+ """
+ idbin = strhex2bin(tagid)
+ self.rfid_idn = strbin2dec(idbin[26:64])
+ while self.rfid_idn.__len__() < 9:
+ self.rfid_idn = '0' + self.rfid_idn
+ print tagid, idbin, self.rfid_idn
+ self.tw.lc.update_label_value('rfid', self.rfid_idn)
+
+ # Block primitives used in talogo
+
+ def prim_read_rfid(self):
+ if self._status:
+ return self.rfid_idn
+ else:
+ return '0'
diff --git a/devices/rfidrweusb.py b/plugins/rfid/rfidrweusb.py
index bd12631..bd12631 100644
--- a/devices/rfidrweusb.py
+++ b/plugins/rfid/rfidrweusb.py
diff --git a/TurtleArt/rfidutils.py b/plugins/rfid/rfidutils.py
index f2c74b4..4e02619 100644
--- a/TurtleArt/rfidutils.py
+++ b/plugins/rfid/rfidutils.py
@@ -23,10 +23,10 @@ def find_device():
Return a device instance or None.
"""
device = None
- for i in os.listdir(os.path.join('.', 'devices')):
- if not os.path.isdir(os.path.join('.', 'devices', i)):
+ for i in os.listdir(os.path.join('.', 'plugins/rfid')):
+ if not os.path.isdir(os.path.join('.', 'plugins/rfid', i)):
try:
- _tempmod = __import__('devices.%s'%i.split('.')[0], globals(),
+ _tempmod = __import__('rfid.%s'%i.split('.')[0], globals(),
locals(), ['RFIDReader'], -1)
devtemp = _tempmod.RFIDReader()
if devtemp.get_present() == True:
diff --git a/devices/serial/__init__.py b/plugins/rfid/serial/__init__.py
index 681ad5c..681ad5c 100644
--- a/devices/serial/__init__.py
+++ b/plugins/rfid/serial/__init__.py
diff --git a/devices/serial/serialposix.py b/plugins/rfid/serial/serialposix.py
index 174e2f7..174e2f7 100644
--- a/devices/serial/serialposix.py
+++ b/plugins/rfid/serial/serialposix.py
diff --git a/devices/serial/serialutil.py b/plugins/rfid/serial/serialutil.py
index fd466f2..fd466f2 100644
--- a/devices/serial/serialutil.py
+++ b/plugins/rfid/serial/serialutil.py
diff --git a/devices/tis2000.py b/plugins/rfid/tis2000.py
index 91d1991..91d1991 100644
--- a/devices/tis2000.py
+++ b/plugins/rfid/tis2000.py
diff --git a/devices/utils.py b/plugins/rfid/utils.py
index 94e5540..94e5540 100644
--- a/devices/utils.py
+++ b/plugins/rfid/utils.py
diff --git a/devices/__init__.py b/plugins/turtle_blocks_extras/__init__.py
index e69de29..e69de29 100644
--- a/devices/__init__.py
+++ b/plugins/turtle_blocks_extras/__init__.py
diff --git a/plugins/turtle_blocks_extras/icons/extrasoff.svg b/plugins/turtle_blocks_extras/icons/extrasoff.svg
new file mode 100644
index 0000000..7975a9d
--- /dev/null
+++ b/plugins/turtle_blocks_extras/icons/extrasoff.svg
@@ -0,0 +1,75 @@
+<?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" />
+ <rect
+ width="55"
+ height="55"
+ x="0"
+ y="0"
+ id="rect3269"
+ style="fill:#282828;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <g
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="g3">
+ <polygon
+ points="20.555,2.531 26.891,12.363 17.238,16.369 14.659,4.975 "
+ id="polygon5"
+ style="fill:#ffffff" />
+ <polygon
+ points="52.477,20.55 42.646,26.88 38.649,17.228 50.04,14.654 "
+ id="polygon7"
+ style="fill:#ffffff" />
+ <polygon
+ points="34.453,52.471 28.117,42.645 37.775,38.645 40.349,50.029 "
+ id="polygon9"
+ style="fill:#ffffff" />
+ <polygon
+ points="40.295,4.882 37.824,16.315 28.171,12.309 34.394,2.439 "
+ id="polygon11"
+ style="fill:#ffffff" />
+ <polygon
+ points="50.055,40.258 38.628,37.791 42.623,28.139 52.493,34.365 "
+ id="polygon13"
+ style="fill:#ffffff" />
+ <polygon
+ points="4.953,40.234 16.385,37.76 12.391,28.105 2.515,34.34 "
+ id="polygon15"
+ style="fill:#ffffff" />
+ <polygon
+ points="2.493,20.539 12.319,26.875 16.32,17.216 4.936,14.643 "
+ id="polygon17"
+ style="fill:#ffffff" />
+ <polygon
+ points="20.609,52.461 26.939,42.623 17.287,38.629 14.719,50.018 "
+ id="polygon19"
+ style="fill:#ffffff" />
+ <path
+ d="m 39.925,22.352 c 2.845,6.863 -0.412,14.728 -7.274,17.574 -6.867,2.85 -14.734,-0.418 -17.578,-7.281 -2.84,-6.862 0.418,-14.733 7.279,-17.572 6.862,-2.845 14.734,0.417 17.573,7.279 z"
+ id="path21"
+ style="fill:none;stroke:#ffffff;stroke-width:11.69439983" />
+ </g>
+</svg>
diff --git a/plugins/turtle_blocks_extras/icons/extrason.svg b/plugins/turtle_blocks_extras/icons/extrason.svg
new file mode 100644
index 0000000..7ee08bf
--- /dev/null
+++ b/plugins/turtle_blocks_extras/icons/extrason.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"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <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="defs5">
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2431"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2428"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ 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="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2557"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,9.2560985,9.9123239)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2561"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,8.962951,22.783517)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2461"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2463"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
+ </defs>
+ <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="g3799">
+ <polygon
+ points="20.555,2.531 26.891,12.363 17.238,16.369 14.659,4.975 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon5"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="52.477,20.55 42.646,26.88 38.649,17.228 50.04,14.654 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon7"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="34.453,52.471 28.117,42.645 37.775,38.645 40.349,50.029 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon9"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="40.295,4.882 37.824,16.315 28.171,12.309 34.394,2.439 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon11"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="50.055,40.258 38.628,37.791 42.623,28.139 52.493,34.365 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon13"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="4.953,40.234 16.385,37.76 12.391,28.105 2.515,34.34 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon15"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="2.493,20.539 12.319,26.875 16.32,17.216 4.936,14.643 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon17"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="20.609,52.461 26.939,42.623 17.287,38.629 14.719,50.018 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon19"
+ style="fill:#ff0000;fill-opacity:1" />
+ <path
+ d="m 35.45648,24.236169 c 1.8208,4.389511 -0.26368,9.419891 -4.65536,11.240166 -4.39488,1.822833 -9.42976,-0.267349 -11.24992,-4.65686 -1.8176,-4.388871 0.26752,-9.423089 4.65856,-11.238887 4.39168,-1.819635 9.42976,0.26671 11.24672,4.655581 z"
+ id="path21"
+ style="fill:none;stroke:#ff0000;stroke-width:7.48202181;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/icons/portfoliooff.svg b/plugins/turtle_blocks_extras/icons/mediaoff.svg
index b2f460a..b2f460a 100644
--- a/icons/portfoliooff.svg
+++ b/plugins/turtle_blocks_extras/icons/mediaoff.svg
diff --git a/plugins/turtle_blocks_extras/icons/mediaon.svg b/plugins/turtle_blocks_extras/icons/mediaon.svg
new file mode 100644
index 0000000..27dd516
--- /dev/null
+++ b/plugins/turtle_blocks_extras/icons/mediaon.svg
@@ -0,0 +1,46 @@
+<?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="55"
+ height="55"
+ id="svg2">
+ <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 40.391135,39.00323 c 0,1.68237 -1.02376,2.90177 -2.90244,2.90177 l -19.73619,0 0,-28.81 19.73686,0 c 1.4405,0 2.90244,1.44318 2.90244,2.9011 l -6.7e-4,23.00713 0,0 z"
+ id="path2458"
+ style="fill:#80ff00;fill-opacity:1;stroke:#000000;stroke-width:2.34500003;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ id="line2460"
+ y2="41.825272"
+ y1="13.17607"
+ x2="23.959385"
+ x1="23.959385"
+ style="fill:none;stroke:#000000;stroke-width:2.34500003;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 14.608195,18.76588 c 0,0 1.39628,0.46565 2.7939,0.46565 1.39762,0 2.79591,-0.46565 2.79591,-0.46565"
+ id="path2462"
+ style="fill:none;stroke:#000000;stroke-width:2.34500003;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 14.608195,27.84907 c 0,0 1.28104,0.46565 2.91115,0.46565 1.63011,0 2.67933,-0.46565 2.67933,-0.46565"
+ id="path2464"
+ style="fill:none;stroke:#000000;stroke-width:2.34500003;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m 14.608195,36.70044 c 0,0 1.16312,0.46565 3.02706,0.46565 1.86327,0 2.56275,-0.46565 2.56275,-0.46565"
+ id="path2466"
+ style="fill:none;stroke:#000000;stroke-width:2.34500003;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+</svg>
diff --git a/plugins/turtle_blocks_extras/icons/portfoliooff.svg b/plugins/turtle_blocks_extras/icons/portfoliooff.svg
new file mode 100644
index 0000000..d404bab
--- /dev/null
+++ b/plugins/turtle_blocks_extras/icons/portfoliooff.svg
@@ -0,0 +1,72 @@
+<?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="defs12" />
+<rect
+ width="55"
+ height="55"
+ x="0"
+ y="0"
+ id="rect3269"
+ style="fill:#282828;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+
+
+` <g
+ transform="matrix(0.8501594,0,0,0.8501594,4.1206165,2.9055499)"
+ id="g3041">
+ <rect
+ width="40"
+ height="30"
+ x="7.5"
+ y="5.8274999"
+ id="rect2996"
+ style="fill:none;stroke:#ffffff;stroke-width:2.34500003;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="translate(-1.0846148,0)"
+ id="g3793">
+ <line
+ id="line2460"
+ y2="52.31316"
+ y1="36.358479"
+ x2="23.419287"
+ x1="27.69433"
+ style="fill:none;stroke:#ffffff;stroke-width:1.78056884;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ id="line2460-2"
+ y2="52.313156"
+ y1="36.358479"
+ x2="33.749943"
+ x1="29.474899"
+ style="fill:none;stroke:#ffffff;stroke-width:1.78056884;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ <line
+ style="fill:none;stroke:#ffffff;stroke-width:2.66399026;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="9.0131855"
+ x2="45.986813"
+ y1="8.168005"
+ y2="8.168005"
+ id="line2460-0" />
+</g>
+</svg>
diff --git a/icons/portfolioon.svg b/plugins/turtle_blocks_extras/icons/portfolioon.svg
index fa4ddf6..e174ee2 100644
--- a/icons/portfolioon.svg
+++ b/plugins/turtle_blocks_extras/icons/portfolioon.svg
@@ -2,6 +2,9 @@
<!-- 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"
@@ -9,6 +12,18 @@
width="55"
height="55"
id="svg2">
+ <metadata
+ id="metadata3983">
+ <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">
<linearGradient
@@ -229,30 +244,41 @@
id="rect2839"
style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
<g
- transform="matrix(0.67,0,0,0.67,9.075,9.075)"
- id="g2856">
- <path
- d="m 46.7405,44.669 c 0,2.511 -1.528,4.331 -4.332,4.331 l -29.457,0 0,-43 29.458,0 c 2.15,0 4.332,2.154 4.332,4.33 l -0.001,34.339 0,0 z"
- id="path2458"
- style="fill:#ffffff;stroke:#0000ff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ transform="matrix(0.8501594,0,0,0.8501594,4.1206165,2.9055499)"
+ id="g3816"
+ style="fill:#f9f9f9;stroke:#0000ff;stroke-opacity:1">
+ <rect
+ width="40"
+ height="30"
+ x="7.5"
+ y="5.8274999"
+ id="rect2996"
+ style="fill:#f9f9f9;stroke:#0000ff;stroke-width:2.34500003;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="translate(-1.0846148,0)"
+ id="g3793"
+ style="fill:#f9f9f9;stroke:#0000ff;stroke-opacity:1">
+ <line
+ id="line2460-5"
+ y2="52.31316"
+ y1="36.358479"
+ x2="23.419287"
+ x1="27.69433"
+ style="fill:#f9f9f9;stroke:#0000ff;stroke-width:1.78056884;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ id="line2460-2"
+ y2="52.313156"
+ y1="36.358479"
+ x2="33.749943"
+ x1="29.474899"
+ style="fill:#f9f9f9;stroke:#0000ff;stroke-width:1.78056884;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
<line
- style="fill:none;stroke:#0000ff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
- x1="22.2155"
- x2="22.2155"
- y1="6.1209998"
- y2="48.881001"
- id="line2460" />
- <path
- d="m 8.2585,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:#0000ff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
- <path
- d="m 8.2585,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:#0000ff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
- <path
- d="m 8.2585,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:#0000ff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ style="fill:#f9f9f9;stroke:#0000ff;stroke-width:2.66399026;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ x1="9.0131855"
+ x2="45.986813"
+ y1="8.168005"
+ y2="8.168005"
+ id="line2460-0" />
</g>
</svg>
diff --git a/icons/sensoroff.svg b/plugins/turtle_blocks_extras/icons/sensoroff.svg
index ec55f03..ec55f03 100644
--- a/icons/sensoroff.svg
+++ b/plugins/turtle_blocks_extras/icons/sensoroff.svg
diff --git a/icons/sensoron.svg b/plugins/turtle_blocks_extras/icons/sensoron.svg
index e902656..e902656 100644
--- a/icons/sensoron.svg
+++ b/plugins/turtle_blocks_extras/icons/sensoron.svg
diff --git a/plugins/turtle_blocks_extras/turtle_blocks_extras.py b/plugins/turtle_blocks_extras/turtle_blocks_extras.py
new file mode 100644
index 0000000..e868ba4
--- /dev/null
+++ b/plugins/turtle_blocks_extras/turtle_blocks_extras.py
@@ -0,0 +1,1246 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2011, Walter Bender
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 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 gtk
+from time import time
+import os.path
+from gettext import gettext as _
+
+try:
+ from sugar.datastore import datastore
+except ImportError:
+ pass
+
+from plugins.plugin import Plugin
+from TurtleArt.tapalette import make_palette, define_logo_function
+from TurtleArt.talogo import primitive_dictionary, logoerror, \
+ media_blocks_dictionary
+from TurtleArt.taconstants import DEFAULT_SCALE, ICON_SIZE, CONSTANTS
+from TurtleArt.tautils import convert, round_int, debug_output
+from TurtleArt.tajail import myfunc, myfunc_import
+
+
+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 _string_to_num(x):
+ """ Try to comvert a string to a number """
+ 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 _millisecond():
+ """ Current time in milliseconds """
+ return time() * 1000
+
+
+class Turtle_blocks_extras(Plugin):
+ """ a class for defining the extra palettes that distinguish Turtle Blocks
+ from Turtle Art """
+
+ def __init__(self, parent):
+ self.tw = parent
+
+ def setup(self):
+ self.heap = self.tw.lc.heap
+ self.keyboard = self.tw.lc.keyboard
+ self.title_height = int((self.tw.canvas.height / 20) * self.tw.scale)
+
+ # set up Turtle Block palettes
+ self._flow_palette()
+
+ self._media_palette()
+
+ self._sensor_palette()
+
+ self._extras_palette()
+
+ self._portfolio_palette()
+
+ # Palette definitions
+
+ def _flow_palette(self):
+ palette = make_palette('flow',
+ colors=["#FFC000", "#A08000"],
+ help_string=_('Palette of flow operators'))
+
+ # macro
+ palette.add_block('while',
+ style='flow-style-boolean',
+ label=_('while'),
+ help_string=_('do-while-True operator that uses \
+boolean operators from Numbers palette'))
+
+ # macro
+ palette.add_block('until',
+ style='flow-style-boolean',
+ label=_('until'),
+ help_string=_('do-until-True operator that uses \
+boolean operators from Numbers palette'))
+
+ def _media_palette(self):
+
+ palette = make_palette('media',
+ colors=["#A0FF00", "#80A000"],
+ help_string=_('Palette of media objects'))
+
+ palette.add_block('journal',
+ style='box-style-media',
+ label=' ',
+ default='None',
+ special_name=_('journal'),
+ help_string=_('Sugar Journal media object'))
+
+ palette.add_block('audio',
+ style='box-style-media',
+ label=' ',
+ special_name=_('audio'),
+ default='None',
+ help_string=_('Sugar Journal audio object'))
+
+ palette.add_block('video',
+ style='box-style-media',
+ label=' ',
+ special_name=_('video'),
+ default='None',
+ help_string=_('Sugar Journal video object'))
+
+ palette.add_block('description',
+ style='box-style-media',
+ label=' ',
+ special_name=_('description'),
+ default='None',
+ help_string=_('Sugar Journal description field'))
+
+ palette.add_block('string',
+ style='box-style',
+ label=_('text'),
+ default=_('text'),
+ special_name=_('text'),
+ help_string=_('string value'))
+
+ primitive_dictionary['show'] = self._prim_show
+ palette.add_block('show',
+ style='basic-style-1arg',
+ label=_('show'),
+ default=_('text'),
+ prim_name='show',
+ logo_command='label',
+ help_string=_('draws text or show media from the \
+Journal'))
+ self.tw.lc.def_prim('show', 1,
+ lambda self, x: primitive_dictionary['show'](x, True))
+
+ palette.add_block('showaligned',
+ hidden=True,
+ colors=["#A0FF00", "#80A000"],
+ style='basic-style-1arg',
+ label=_('show aligned'),
+ default=_('text'),
+ prim_name='showaligned',
+ logo_command='label',
+ help_string=_('draws text or show media from the \
+Journal'))
+ self.tw.lc.def_prim('showaligned', 1,
+ lambda self, x: primitive_dictionary['show'](x, False))
+
+ # deprecated
+ primitive_dictionary['write'] = self._prim_write
+ palette.add_block('write',
+ hidden=True,
+ colors=["#A0FF00", "#80A000"],
+ style='basic-style-1arg',
+ label=_('show'),
+ default=[_('text'), 32],
+ prim_name='write',
+ logo_command='label',
+ help_string=_('draws text or show media from the \
+Journal'))
+ self.tw.lc.def_prim('write', 2,
+ lambda self, x, y: primitive_dictionary['write'](x, y))
+
+ primitive_dictionary['setscale'] = self._prim_setscale
+ palette.add_block('setscale',
+ style='basic-style-1arg',
+ label=_('set scale'),
+ prim_name='setscale',
+ default=33,
+ logo_command='setlabelheight',
+ help_string=_('sets the scale of media'))
+ self.tw.lc.def_prim('setscale', 1,
+ lambda self, x: primitive_dictionary['setscale'](x))
+
+ primitive_dictionary['savepix'] = self._prim_save_picture
+ palette.add_block('savepix',
+ style='basic-style-1arg',
+ label=_('save picture'),
+ prim_name='savepix',
+ default=_('picture name'),
+ help_string=_('saves a picture to the Sugar \
+Journal'))
+ self.tw.lc.def_prim('savepix', 1,
+ lambda self, x: primitive_dictionary['savepix'](x))
+
+ primitive_dictionary['savesvg'] = self._prim_save_svg
+ palette.add_block('savesvg',
+ style='basic-style-1arg',
+ label=_('save SVG'),
+ prim_name='savesvg',
+ default=_('picture name'),
+ help_string=_('saves turtle graphics as an SVG file \
+in the Sugar Journal'))
+ self.tw.lc.def_prim('savesvg', 1,
+ lambda self, x: primitive_dictionary['savesvg'](x))
+
+ palette.add_block('scale',
+ style='box-style',
+ label=_('scale'),
+ prim_name='scale',
+ value_block=True,
+ logo_command='labelsize',
+ help_string=_('holds current scale value'))
+ self.tw.lc.def_prim('scale', 0, lambda self: self.tw.lc.scale)
+
+ palette.add_block('mediawait',
+ style='basic-style-extended-vertical',
+ label=_('media wait'),
+ prim_name='mediawait',
+ help_string=_('wait for current video or audio to \
+complete'))
+ self.tw.lc.def_prim('mediawait', 0, self.tw.lc.media_wait, True)
+
+ def _sensor_palette(self):
+
+ palette = make_palette('sensor',
+ colors=["#FF6060", "#A06060"],
+ help_string=_('Palette of sensor blocks'))
+
+ primitive_dictionary['kbinput'] = self._prim_kbinput
+ palette.add_block('kbinput',
+ style='basic-style-extended-vertical',
+ label=_('query keyboard'),
+ prim_name='kbinput',
+ help_string=_('query for keyboard input (results \
+stored in keyboard block)'))
+ self.tw.lc.def_prim('kbinput', 0,
+ lambda self: primitive_dictionary['kbinput']())
+
+ palette.add_block('keyboard',
+ style='box-style',
+ label=_('keyboard'),
+ prim_name='keyboard',
+ value_block=True,
+ logo_command='make "keyboard readchar',
+ help_string=_('holds results of query-keyboard \
+block'))
+ self.tw.lc.def_prim('keyboard', 0, lambda self: self.tw.lc.keyboard)
+
+ primitive_dictionary['readpixel'] = self._prim_readpixel
+ palette.add_block('readpixel',
+ style='basic-style-extended-vertical',
+ label=_('read pixel'),
+ prim_name='readpixel',
+ logo_command=':keyboard',
+ help_string=_('RGB color under the turtle is pushed \
+to the stack'))
+ self.tw.lc.def_prim('readpixel', 0,
+ lambda self: primitive_dictionary['readpixel']())
+
+ primitive_dictionary['see'] = self._prim_see
+ palette.add_block('see',
+ style='box-style',
+ label=_('turtle sees'),
+ prim_name='see',
+ help_string=_('returns the color that the turtle \
+"sees"'))
+ self.tw.lc.def_prim('see', 0,
+ lambda self: primitive_dictionary['see']())
+
+ primitive_dictionary['time'] = self._prim_time
+ palette.add_block('time',
+ style='box-style',
+ label=_('time'),
+ prim_name='time',
+ value_block=True,
+ help_string=_('elapsed time (in seconds) since \
+program started'))
+ self.tw.lc.def_prim('time', 0,
+ lambda self: primitive_dictionary['time']())
+
+ def _extras_palette(self):
+
+ palette = make_palette('extras',
+ colors=["#FF0000", "#A00000"],
+ help_string=_('Palette of extra options'))
+
+ primitive_dictionary['push'] = self._prim_push
+ palette.add_block('push',
+ style='basic-style-1arg',
+ label=_('push'),
+ prim_name='push',
+ logo_command='tapush',
+ help_string=_('pushes value onto FILO (first-in \
+last-out heap)'))
+ self.tw.lc.def_prim('push', 1,
+ lambda self, x: primitive_dictionary['push'](x))
+ define_logo_function('tapush', 'to tapush :foo\rmake "taheap fput \
+:foo :taheap\rend\rmake "taheap []\r')
+
+ primitive_dictionary['printheap'] = self._prim_printheap
+ palette.add_block('printheap',
+ style='basic-style-extended-vertical',
+ label=_('show heap'),
+ prim_name='printheap',
+ logo_command='taprintheap',
+ help_string=_('shows values in FILO (first-in \
+last-out heap)'))
+ self.tw.lc.def_prim('printheap', 0,
+ lambda self: primitive_dictionary['printheap']())
+ define_logo_function('taprintheap', 'to taprintheap \rprint :taheap\r\
+end\r')
+
+ primitive_dictionary['clearheap'] = self._prim_emptyheap
+ palette.add_block('clearheap',
+ style='basic-style-extended-vertical',
+ label=_('empty heap'),
+ prim_name='clearheap',
+ logo_command='taclearheap',
+ help_string=_('emptys FILO (first-in-last-out \
+heap)'))
+ self.tw.lc.def_prim('clearheap', 0,
+ lambda self: primitive_dictionary['clearheap']())
+ define_logo_function('taclearheap', 'to taclearheap\rmake "taheap []\r\
+end\r')
+
+ primitive_dictionary['pop'] = self._prim_pop
+ palette.add_block('pop',
+ style='box-style',
+ label=_('pop'),
+ prim_name='pop',
+ value_block=True,
+ logo_command='tapop',
+ help_string=_('pops value off FILO (first-in \
+last-out heap)'))
+ self.tw.lc.def_prim('pop', 0,
+ lambda self: primitive_dictionary['pop']())
+ define_logo_function('tapop', 'to tapop\rif emptyp :taheap [stop]\r\
+make "tmp first :taheap\rmake "taheap butfirst :taheap\routput :tmp\rend\r')
+
+ primitive_dictionary['print'] = self._prim_print
+ palette.add_block('comment',
+ style='basic-style-1arg',
+ label=_('comment'),
+ prim_name='comment',
+ default=_('comment'),
+ help_string=_('places a comment in your code'))
+ self.tw.lc.def_prim('comment', 1,
+ lambda self, x: primitive_dictionary['print'](x, True))
+
+ palette.add_block('print',
+ style='basic-style-1arg',
+ label=_('print'),
+ prim_name='print',
+ logo_command='label',
+ help_string=_('prints value in status block at \
+bottom of the screen'))
+ self.tw.lc.def_prim('print', 1,
+ lambda self, x: primitive_dictionary['print'](x, False))
+
+ primitive_dictionary['myfunction'] = self._prim_myfunction
+ palette.add_block('myfunc1arg',
+ style='number-style-var-arg',
+ label=[_('Python'), 'f(x)', 'x'],
+ prim_name='myfunction',
+ default=['x', 100],
+ help_string=_('a programmable block: used to add \
+advanced single-variable math equations, e.g., sin(x)'))
+ self.tw.lc.def_prim('myfunction', 2,
+ lambda self, f, x: primitive_dictionary['myfunction'](f, [x]))
+
+ palette.add_block('myfunc2arg',
+ hidden=True,
+ colors=["#FF0000", "#A00000"],
+ style='number-style-var-arg',
+ label=[_('Python'), 'f(x,y)', 'x'],
+ prim_name='myfunction2',
+ default=['x+y', 100, 100],
+ help_string=_('a programmable block: used to add \
+advanced multi-variable math equations, e.g., sqrt(x*x+y*y)'))
+ self.tw.lc.def_prim('myfunction2', 3,
+ lambda self, f, x, y: primitive_dictionary['myfunction'](
+ f, [x, y]))
+
+ palette.add_block('myfunc3arg',
+ hidden=True,
+ colors=["#FF0000", "#A00000"],
+ style='number-style-var-arg',
+ label=[_('Python'), 'f(x,y,z)', 'x'],
+ prim_name='myfunction3',
+ default=['x+y+z', 100, 100, 100],
+ help_string=_('a programmable block: used to add \
+advanced multi-variable math equations, e.g., sin(x+y+z)'))
+ self.tw.lc.def_prim('myfunction3', 4,
+ lambda self, f, x, y, z: primitive_dictionary['myfunction'](
+ f, [x, y, z]))
+
+ primitive_dictionary['userdefined'] = self._prim_myblock
+ palette.add_block('userdefined',
+ style='basic-style-var-arg',
+ label=' ',
+ prim_name='userdefined',
+ special_name=_('Python block'),
+ default=100,
+ help_string=_('runs code found in the tamyblock.py \
+module found in the Journal'))
+ self.tw.lc.def_prim('userdefined', 1,
+ lambda self, x: primitive_dictionary['userdefined']([x]))
+
+ palette.add_block('userdefined2args',
+ hidden=True,
+ colors=["#FF0000", "#A00000"],
+ style='basic-style-var-arg',
+ label=' ',
+ prim_name='userdefined2',
+ special_name=_('Python block'),
+ default=[100, 100],
+ help_string=_('runs code found in the tamyblock.py \
+module found in the Journal'))
+ self.tw.lc.def_prim('userdefined2', 2,
+ lambda self, x, y: primitive_dictionary['userdefined']([x, y]))
+
+ palette.add_block('userdefined3args',
+ hidden=True,
+ colors=["#FF0000", "#A00000"],
+ style='basic-style-var-arg',
+ label=' ',
+ prim_name='userdefined3',
+ special_name=_('Python block'),
+ default=[100, 100, 100],
+ help_string=_('runs code found in the tamyblock.py \
+module found in the Journal'))
+ self.tw.lc.def_prim('userdefined3', 3,
+ lambda self, x, y, z: primitive_dictionary['userdefined'](
+ [x, y, z]))
+
+ palette.add_block('cartesian',
+ style='basic-style-extended-vertical',
+ label=_('Cartesian'),
+ prim_name='cartesian',
+ help_string=_('displays Cartesian coordinates'))
+ self.tw.lc.def_prim('cartesian', 0,
+ lambda self: self.tw.set_cartesian(True))
+
+ palette.add_block('polar',
+ style='basic-style-extended-vertical',
+ label=_('polar'),
+ prim_name='polar',
+ help_string=_('displays polar coordinates'))
+ self.tw.lc.def_prim('polar', 0,
+ lambda self: self.tw.set_polar(True))
+
+ palette.add_block('addturtle',
+ style='basic-style-1arg',
+ label=_('turtle'),
+ prim_name='turtle',
+ default=1,
+ help_string=_('chooses which turtle to command'))
+ self.tw.lc.def_prim('turtle', 1,
+ lambda self, x: self.tw.canvas.set_turtle(x))
+
+ primitive_dictionary['skin'] = self._prim_reskin
+ palette.add_block('skin',
+ hidden=True,
+ colors=["#FF0000", "#A00000"],
+ style='basic-style-1arg',
+ label=_('turtle shell'),
+ prim_name='skin',
+ help_string=_("put a custom 'shell' on the turtle"))
+ self.tw.lc.def_prim('skin', 1,
+ lambda self, x: primitive_dictionary['skin'](x))
+
+ # macro
+ palette.add_block('reskin',
+ style='basic-style-1arg',
+ label=_('turtle shell'),
+ help_string=_("put a custom 'shell' on the turtle"))
+
+ palette.add_block('sandwichtop_no_label',
+ style='collapsible-top-no-label',
+ label=[' ', ' '],
+ special_name=_('top'),
+ prim_name='nop',
+ help_string=_('top of a collapsed stack'))
+
+ palette.add_block('sandwichbottom',
+ style='collapsible-bottom',
+ label=[' ', ' '],
+ prim_name='nop',
+ special_name=_('bottom'),
+ help_string=_('bottom of a collapsible stack'))
+
+ palette.add_block('sandwichcollapsed',
+ hidden=True,
+ colors=["#FF0000", "#A00000"],
+ style='invisible',
+ label=' ',
+ prim_name='nop',
+ help_string=_('bottom block in a collapsed stack: \
+click to open'))
+
+ # deprecated blocks
+ palette.add_block('sandwichtop',
+ hidden=True,
+ colors=["#FF0000", "#A00000"],
+ style='collapsible-top',
+ label=_('top of stack'),
+ default=_('label'),
+ prim_name='comment',
+ help_string=_('top of stack'))
+
+ palette.add_block('sandwichtop_no_arm',
+ hidden=True,
+ colors=["#FF0000", "#A00000"],
+ style='collapsible-top-no-arm',
+ label=_('top of a collapsible stack'),
+ default=_('label'),
+ prim_name='comment',
+ help_string=_('top of stack'))
+
+ palette.add_block('sandwichtop_no_arm_no_label',
+ hidden=True,
+ colors=["#FF0000", "#A00000"],
+ style='collapsible-top-no-arm-no-label',
+ label=[' ', _('click to open')],
+ prim_name='nop',
+ help_string=_('top of stack'))
+
+ def _portfolio_palette(self):
+
+ palette = make_palette('portfolio',
+ colors=["#0606FF", "#0606A0"],
+ help_string=_('Palette of presentation templates'))
+
+ palette.add_block('hideblocks',
+ style='basic-style-extended-vertical',
+ label=_('hide blocks'),
+ prim_name='hideblocks',
+ help_string=_('declutters canvas by hiding blocks'))
+ self.tw.lc.def_prim('hideblocks', 0, lambda self: self.tw.hideblocks())
+
+ palette.add_block('showblocks',
+ style='basic-style-extended-vertical',
+ label=_('show blocks'),
+ prim_name='showblocks',
+ help_string=_('restores hidden blocks'))
+ self.tw.lc.def_prim('showblocks', 0, lambda self: self.tw.showblocks())
+
+ palette.add_block('fullscreen',
+ style='basic-style-extended-vertical',
+ label=_('full screen'),
+ prim_name='fullscreen',
+ help_string=_('hides the Sugar toolbars'))
+ self.tw.lc.def_prim('fullscreen', 0,
+ lambda self: self.tw.set_fullscreen())
+
+ primitive_dictionary['bulletlist'] = self._prim_list
+ palette.add_block('list',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='bullet-style',
+ label=_('list'),
+ prim_name='bulletlist',
+ default=['∙ ', '∙ '],
+ help_string=_('presentation bulleted list'))
+ self.tw.lc.def_prim('bulletlist', 1,
+ primitive_dictionary['bulletlist'], True)
+
+ # macros
+ palette.add_block('picturelist',
+ style='basic-style-extended',
+ label=' ',
+ help_string=_('presentation template: list of \
+bullets'))
+
+ palette.add_block('picture1x1a',
+ style='basic-style-extended',
+ label=' ',
+ help_string=_('presentation template: select \
+Journal object (no description)'))
+
+ palette.add_block('picture1x1',
+ style='basic-style-extended',
+ label=' ',
+ help_string=_('presentation template: select \
+Journal object (with description)'))
+
+ palette.add_block('picture2x2',
+ style='basic-style-extended',
+ label=' ',
+ help_string=_('presentation template: select four \
+Journal objects'))
+
+ palette.add_block('picture2x1',
+ style='basic-style-extended',
+ label=' ',
+ help_string=_('presentation template: select two \
+Journal objects'))
+
+ palette.add_block('picture1x2',
+ style='basic-style-extended',
+ label=' ',
+ help_string=_('presentation template: select two \
+Journal objects'))
+
+ # Display-dependent constants
+ palette.add_block('leftpos',
+ style='box-style',
+ label=_('left'),
+ prim_name='lpos',
+ logo_command='lpos',
+ help_string=_('xcor of left of screen'))
+ self.tw.lc.def_prim('lpos', 0, lambda self: CONSTANTS['leftpos'])
+
+ palette.add_block('bottompos',
+ style='box-style',
+ label=_('bottom'),
+ prim_name='bpos',
+ logo_command='bpos',
+ help_string=_('ycor of bottom of screen'))
+ self.tw.lc.def_prim('bpos', 0, lambda self: CONSTANTS['bottompos'])
+
+ palette.add_block('width',
+ style='box-style',
+ label=_('width'),
+ prim_name='hres',
+ logo_command='width',
+ help_string=_('the canvas width'))
+ self.tw.lc.def_prim('hres', 0, lambda self: CONSTANTS['width'])
+
+ palette.add_block('rightpos',
+ style='box-style',
+ label=_('right'),
+ prim_name='rpos',
+ logo_command='rpos',
+ help_string=_('xcor of right of screen'))
+ self.tw.lc.def_prim('rpos', 0, lambda self: CONSTANTS['rightpos'])
+
+ palette.add_block('toppos',
+ style='box-style',
+ label=_('top'),
+ prim_name='tpos',
+ logo_command='tpos',
+ help_string=_('ycor of top of screen'))
+ self.tw.lc.def_prim('tpos', 0, lambda self: CONSTANTS['toppos'])
+
+ palette.add_block('height',
+ style='box-style',
+ label=_('height'),
+ prim_name='vres',
+ logo_command='height',
+ help_string=_('the canvas height'))
+ self.tw.lc.def_prim('vres', 0, lambda self: CONSTANTS['height'])
+
+ palette.add_block('titlex',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='box-style',
+ label=_('title x'),
+ logo_command='titlex',
+ prim_name='titlex')
+ self.tw.lc.def_prim('titlex', 0, lambda self: CONSTANTS['titlex'])
+
+ palette.add_block('titley',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='box-style',
+ label=_('title y'),
+ logo_command='titley',
+ prim_name='titley')
+ self.tw.lc.def_prim('titley', 0, lambda self: CONSTANTS['titley'])
+
+ palette.add_block('leftx',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='box-style',
+ label=_('left x'),
+ prim_name='leftx',
+ logo_command='leftx')
+ self.tw.lc.def_prim('leftx', 0, lambda self: CONSTANTS['leftx'])
+
+ palette.add_block('topy',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='box-style',
+ label=_('top y'),
+ prim_name='topy',
+ logo_command='topy')
+ self.tw.lc.def_prim('topy', 0, lambda self: CONSTANTS['topy'])
+
+ palette.add_block('rightx',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='box-style',
+ label=_('right x'),
+ prim_name='rightx',
+ logo_command='rightx')
+ self.tw.lc.def_prim('rightx', 0, lambda self: CONSTANTS['rightx'])
+
+ palette.add_block('bottomy',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='box-style',
+ label=_('bottom y'),
+ prim_name='bottomy',
+ logo_command='bottomy')
+ self.tw.lc.def_prim('bottomy', 0, lambda self: CONSTANTS['bottomy'])
+
+ # deprecated blocks
+
+ primitive_dictionary['t1x1'] = self._prim_t1x1
+ palette.add_block('template1x1',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='portfolio-style-1x1',
+ label=' ',
+ prim_name='t1x1',
+ default=[_('Title'), 'None'],
+ special_name=_('presentation 1x1'),
+ help_string=_('presentation template: select \
+Journal object (with description)'))
+ self.tw.lc.def_prim('t1x1', 2,
+ lambda self, a, b: primitive_dictionary['t1x1'](a, b))
+
+ primitive_dictionary['t1x1a'] = self._prim_t1x1a
+ palette.add_block('template1x1a',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='portfolio-style-1x1',
+ label=' ',
+ prim_name='t1x1a',
+ default=[_('Title'), 'None'],
+ special_name=_('presentation 1x1'),
+ help_string=_('presentation template: select \
+Journal object (no description)'))
+ self.tw.lc.def_prim('t1x1a', 2,
+ lambda self, a, b: primitive_dictionary['t1x1a'](a, b))
+
+ primitive_dictionary['2x1'] = self._prim_t2x1
+ palette.add_block('template2x1',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='portfolio-style-2x1',
+ label=' ',
+ prim_name='t2x1',
+ default=[_('Title'), 'None', 'None'],
+ special_name=_('presentation 2x1'),
+ help_string=_("presentation template: select two \
+Journal objects"))
+ self.tw.lc.def_prim('t2x1', 3,
+ lambda self, a, b, c: primitive_dictionary['t2x1'](a, b, c))
+
+ primitive_dictionary['1x2'] = self._prim_t1x2
+ palette.add_block('template1x2',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='portfolio-style-1x2',
+ label=' ',
+ prim_name='t1x2',
+ default=[_('Title'), 'None', 'None'],
+ special_name=_('presentation 1x2'),
+ help_string=_("presentation template: select two \
+Journal objects"))
+ self.tw.lc.def_prim('t1x2', 3,
+ lambda self, a, b, c: primitive_dictionary['t1x2'](a, b, c))
+
+ primitive_dictionary['t2x2'] = self._prim_t2x2
+ palette.add_block('template2x2',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='portfolio-style-2x2',
+ label=' ',
+ prim_name='t2x2',
+ default=[_('Title'), 'None', 'None', 'None', 'None'],
+ special_name=_('presentation 2x2'),
+ help_string=_("presentation template: select four \
+Journal objects"))
+ self.tw.lc.def_prim('t2x2', 5,
+ lambda self, a, b, c, d, e: primitive_dictionary['t2x2'](
+ a, b, c, d, e))
+
+ palette.add_block('templatelist',
+ hidden=True,
+ colors=["#0606FF", "#0606A0"],
+ style='bullet-style',
+ label=' ',
+ prim_name='bullet',
+ default=[_('Title'), '∙ '],
+ special_name=_('presentation bulleted list'),
+ help_string=_('presentation template: list of \
+bullets'))
+ self.tw.lc.def_prim('bullet', 1, self._prim_list, True)
+
+ # Block primitives
+
+ def _prim_emptyheap(self):
+ """ Empty FILO """
+ self.tw.lc.heap = []
+
+ def _prim_kbinput(self):
+ """ Query keyboard """
+ if len(self.tw.keypress) == 1:
+ self.tw.lc.keyboard = ord(self.tw.keypress[0])
+ else:
+ try:
+ self.tw.lc.keyboard = {'Escape': 27, 'space': 32, ' ': 32,
+ 'Return': 13, 'KP_Up': 2, 'KP_Down': 4, 'KP_Left': 1,
+ 'KP_Right': 3}[self.tw.keypress]
+ except KeyError:
+ self.tw.lc.keyboard = 0
+ self.tw.lc.update_label_value('keyboard', self.tw.lc.keyboard)
+ self.tw.keypress = ''
+
+ def _prim_list(self, blklist):
+ """ Expandable list block """
+ self._prim_showlist(blklist)
+ self.tw.lc.ireturn()
+ yield True
+
+ def _prim_myblock(self, x):
+ """ Run Python code imported from Journal """
+ if self.tw.lc.bindex is not None and \
+ self.tw.lc.bindex in self.tw.myblock:
+ try:
+ if len(x) == 1:
+ myfunc_import(self, self.tw.myblock[self.tw.lc.bindex],
+ x[0])
+ else:
+ myfunc_import(self, self.tw.myblock[self.tw.lc.bindex], x)
+ except:
+ raise logoerror("#syntaxerror")
+
+ def _prim_myfunction(self, f, x):
+ """ Programmable block """
+ try:
+ y = myfunc(f, x)
+ if str(y) == 'nan':
+ debug_output('Python function returned NAN',
+ self.tw.running_sugar)
+ self.tw.lc.stop_logo()
+ raise logoerror("#notanumber")
+ else:
+ return y
+ except ZeroDivisionError:
+ self.tw.lc.stop_logo()
+ raise logoerror("#zerodivide")
+ except ValueError, e:
+ self.tw.lc.stop_logo()
+ raise logoerror('#' + str(e))
+ except SyntaxError, e:
+ self.tw.lc.stop_logo()
+ raise logoerror('#' + str(e))
+ except NameError, e:
+ self.tw.lc.stop_logo()
+ raise logoerror('#' + str(e))
+ except OverflowError:
+ self.tw.lc.stop_logo()
+ raise logoerror("#overflowerror")
+ except TypeError:
+ self.tw.lc.stop_logo()
+ raise logoerror("#notanumber")
+
+ def _prim_pop(self):
+ """ Pop value off of FILO """
+ if len(self.tw.lc.heap) == 0:
+ raise logoerror("#emptyheap")
+ else:
+ if len(self.tw.lc.heap) == 1:
+ self.tw.lc.update_label_value('pop')
+ else:
+ self.tw.lc.update_label_value('pop', self.tw.lc.heap[-2])
+ return self.tw.lc.heap.pop(-1)
+
+ def _prim_print(self, n, flag):
+ """ Print object n """
+ if flag and (self.tw.hide or self.tw.step_time == 0):
+ return
+ if type(n) == str or type(n) == unicode:
+ if n[0:6] == 'media_' and \
+ n[6:].lower not in media_blocks_dictionary:
+ try:
+ if self.tw.running_sugar:
+ try:
+ dsobject = datastore.get(n[6:])
+ except:
+ debug_output("Couldn't open %s" % (n[6:]),
+ self.tw.running_sugar)
+ self.tw.showlabel('status', dsobject.metadata['title'])
+ dsobject.destroy()
+ else:
+ self.tw.showlabel('status', n[6:])
+ except IOError:
+ self.tw.showlabel('status', n)
+ else:
+ self.tw.showlabel('status', n)
+ elif type(n) == int:
+ self.tw.showlabel('status', n)
+ else:
+ self.tw.showlabel('status',
+ str(round_int(n)).replace('.', self.tw.decimal_point))
+
+ def _prim_printheap(self):
+ """ Display contents of heap """
+ heap_as_string = str(self.tw.lc.heap)
+ if len(heap_as_string) > 80:
+ self.tw.showlabel('status', str(self.tw.lc.heap)[0:79] + '…')
+ else:
+ self.tw.showlabel('status', str(self.tw.lc.heap))
+
+ def _prim_push(self, val):
+ """ Push value onto FILO """
+ self.tw.lc.heap.append(val)
+ self.tw.lc.update_label_value('pop', val)
+
+ def _prim_readpixel(self):
+ """ Read r, g, b, a from the canvas and push b, g, r to the stack """
+ r, g, b, a = self.tw.canvas.get_pixel()
+ self.tw.lc.heap.append(b)
+ self.tw.lc.heap.append(g)
+ self.tw.lc.heap.append(r)
+
+ def _prim_reskin(self, media):
+ """ Reskin the turtle with an image from a file """
+ scale = int(ICON_SIZE * float(self.tw.lc.scale) / DEFAULT_SCALE)
+ if scale < 1:
+ return
+ self.tw.lc.filepath = None
+ dsobject = None
+ if os.path.exists(media[6:]): # is it a path?
+ self.tw.lc.filepath = media[6:]
+ elif self.tw.running_sugar: # is it a datastore object?
+ try:
+ dsobject = datastore.get(media[6:])
+ except:
+ debug_output("Couldn't open skin %s" % (media[6:]),
+ self.tw.running_sugar)
+ if dsobject is not None:
+ self.tw.lc.filepath = dsobject.file_path
+ if self.tw.lc.filepath == None:
+ self.tw.showlabel('nojournal', self.tw.lc.filepath)
+ return
+ pixbuf = None
+ try:
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.tw.lc.filepath,
+ scale, scale)
+ except:
+ self.tw.showlabel('nojournal', self.tw.lc.filepath)
+ debug_output("Couldn't open skin %s" % (self.tw.lc.filepath),
+ self.tw.running_sugar)
+ if pixbuf is not None:
+ self.tw.active_turtle.set_shapes([pixbuf])
+ pen_state = self.tw.active_turtle.get_pen_state()
+ if pen_state:
+ self.tw.canvas.setpen(False)
+ self.tw.canvas.forward(0)
+ if pen_state:
+ self.tw.canvas.setpen(True)
+
+ def _prim_save_picture(self, name):
+ """ Save canvas to file as PNG """
+ self.tw.save_as_image(name)
+
+ def _prim_save_svg(self, name):
+ """ Save SVG to file """
+ self.tw.canvas.svg_close()
+ self.tw.save_as_image(name, svg=True)
+
+ def _prim_see(self):
+ """ Read r, g, b from the canvas and return a corresponding palette
+ color """
+ r, g, b, a = self.tw.canvas.get_pixel()
+ color_index = self.tw.canvas.get_color_index(r, g, b)
+ self.tw.lc.update_label_value('see', color_index)
+ return color_index
+
+ def _prim_setscale(self, scale):
+ """ Set the scale used by the show block """
+ self.tw.lc.scale = scale
+ self.tw.lc.update_label_value('scale', scale)
+
+ def _prim_show(self, string, center=False):
+ """ Show is the general-purpose media-rendering block. """
+ if type(string) == str or type(string) == unicode:
+ if string in ['media_', 'descr_', 'audio_', 'video_',
+ 'media_None', 'descr_None', 'audio_None',
+ 'video_None']:
+ pass
+ elif string[0:6] in ['media_', 'descr_', 'audio_', 'video_']:
+ self.tw.lc.filepath = None
+ self.tw.lc.dsobject = None
+ if string[6:].lower() in media_blocks_dictionary:
+ media_blocks_dictionary[string[6:].lower()]()
+ elif os.path.exists(string[6:]): # is it a path?
+ self.tw.lc.filepath = string[6:]
+ elif self.tw.running_sugar: # is it a datastore object?
+ try:
+ self.tw.lc.dsobject = datastore.get(string[6:])
+ except:
+ debug_output("Couldn't find dsobject %s" % (
+ string[6:]), self.tw.running_sugar)
+ if self.tw.lc.dsobject is not None:
+ self.tw.lc.filepath = self.tw.lc.dsobject.file_path
+ if self.tw.lc.filepath == None:
+ if self.tw.lc.dsobject is not None:
+ self.tw.showlabel('nojournal',
+ self.tw.lc.dsobject.metadata['title'])
+ else:
+ self.tw.showlabel('nojournal', string[6:])
+ debug_output("Couldn't open %s" % (string[6:]),
+ self.tw.running_sugar)
+ elif string[0:6] == 'media_':
+ self.tw.lc.insert_image(center)
+ elif string[0:6] == 'descr_':
+ mimetype = None
+ if self.tw.lc.dsobject is not None and \
+ 'mime_type' in self.tw.lc.dsobject.metadata:
+ mimetype = self.tw.lc.dsobject.metadata['mime_type']
+ description = None
+ if self.tw.lc.dsobject is not None and \
+ 'description' in self.tw.lc.dsobject.metadata:
+ description = self.tw.lc.dsobject.metadata[
+ 'description']
+ self.tw.lc.insert_desc(mimetype, description)
+ elif string[0:6] == 'audio_':
+ self.tw.lc.play_sound()
+ elif string[0:6] == 'video_':
+ self.tw.lc.play_video()
+ if self.tw.lc.dsobject is not None:
+ self.tw.lc.dsobject.destroy()
+ else: # assume it is text to display
+ x, y = self.tw.lc.x2tx(), self.tw.lc.y2ty()
+ if center:
+ y -= self.tw.canvas.textsize
+ self.tw.canvas.draw_text(string, x, y,
+ int(self.tw.canvas.textsize * \
+ self.tw.lc.scale / 100.),
+ self.tw.canvas.width - x)
+ elif type(string) == float or type(string) == int:
+ string = round_int(string)
+ x, y = self.tw.lc.x2tx(), self.tw.lc.y2ty()
+ if center:
+ y -= self.tw.canvas.textsize
+ self.tw.canvas.draw_text(string, x, y,
+ int(self.tw.canvas.textsize * \
+ self.tw.lc.scale / 100.),
+ self.tw.canvas.width - x)
+
+ def _prim_showlist(self, sarray):
+ """ Display list of media objects """
+ x = self.tw.canvas.xcor / self.tw.coord_scale
+ y = self.tw.canvas.ycor / self.tw.coord_scale
+ for s in sarray:
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(s)
+ y -= int(self.tw.canvas.textsize * self.tw.lead)
+
+ def _prim_time(self):
+ """ Number of seconds since program execution has started or
+ clean (prim_clear) block encountered """
+ elapsed_time = int(time() - self.tw.lc.start_time)
+ self.tw.lc.update_label_value('time', elapsed_time)
+ return elapsed_time
+
+ # Deprecated blocks
+
+ def _prim_t1x1(self, title, media):
+ """ title, one image, and description """
+ xo = self.tw.calc_position('t1x1')[2]
+ x = -(self.tw.canvas.width / 2) + xo
+ y = self.tw.canvas.height / 2
+ self.tw.canvas.setxy(x, y, pendown=False)
+ # save the text size so we can restore it later
+ save_text_size = self.tw.canvas.textsize
+ # set title text
+ self.tw.canvas.settextsize(self.title_height)
+ self._prim_show(title)
+ # calculate and set scale for media blocks
+ myscale = 45 * (self.tw.canvas.height - self.title_height * 2) \
+ / self.tw.canvas.height
+ self._prim_setscale(myscale)
+ # set body text size
+ self.tw.canvas.settextsize(self.tw.lc.body_height)
+ # render media object
+ # leave some space below the title
+ y -= int(self.title_height * 2 * self.tw.lead)
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media)
+ if self.tw.running_sugar:
+ x = 0
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media.replace('media_', 'descr_'))
+ # restore text size
+ self.tw.canvas.settextsize(save_text_size)
+
+ def _prim_t2x1(self, title, media1, media2):
+ """ title, two images (horizontal), two descriptions """
+ xo = self.tw.calc_position('t2x1')[2]
+ x = -(self.tw.canvas.width / 2) + xo
+ y = self.tw.canvas.height / 2
+ self.tw.canvas.setxy(x, y, pendown=False)
+ # save the text size so we can restore it later
+ save_text_size = self.tw.canvas.textsize
+ # set title text
+ self.tw.canvas.settextsize(self.title_height)
+ self._prim_show(title)
+ # calculate and set scale for media blocks
+ myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \
+ self.tw.canvas.height
+ self._prim_setscale(myscale)
+ # set body text size
+ self.tw.canvas.settextsize(self.tw.lc.body_height)
+ # render four quadrents
+ # leave some space below the title
+ y -= int(self.title_height * 2 * self.tw.lead)
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media1)
+ x = 0
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media2)
+ y = -self.title_height
+ if self.tw.running_sugar:
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media2.replace('media_', 'descr_'))
+ x = -(self.tw.canvas.width / 2) + xo
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media1.replace('media_', 'descr_'))
+ # restore text size
+ self.tw.canvas.settextsize(save_text_size)
+
+ def _prim_t1x2(self, title, media1, media2):
+ """ title, two images (vertical), two desciptions """
+ xo = self.tw.calc_position('t1x2')[2]
+ x = -(self.tw.canvas.width / 2) + xo
+ y = self.tw.canvas.height / 2
+ self.tw.canvas.setxy(x, y, pendown=False)
+ # save the text size so we can restore it later
+ save_text_size = self.tw.canvas.textsize
+ # set title text
+ self.tw.canvas.settextsize(self.title_height)
+ self._prim_show(title)
+ # calculate and set scale for media blocks
+ myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \
+ self.tw.canvas.height
+ self._prim_setscale(myscale)
+ # set body text size
+ self.tw.canvas.settextsize(self.tw.lc.body_height)
+ # render four quadrents
+ # leave some space below the title
+ y -= int(self.title_height * 2 * self.tw.lead)
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media1)
+ if self.tw.running_sugar:
+ x = 0
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media1.replace('media_', 'descr_'))
+ y = -self.title_height
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media2.replace('media_', 'descr_'))
+ x = -(self.tw.canvas.width / 2) + xo
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media2)
+ # restore text size
+ self.tw.canvas.settextsize(save_text_size)
+
+ def _prim_t2x2(self, title, media1, media2, media3, media4):
+ """ title and four images """
+ xo = self.tw.calc_position('t2x2')[2]
+ x = -(self.tw.canvas.width / 2) + xo
+ y = self.tw.canvas.height / 2
+ self.tw.canvas.setxy(x, y, pendown=False)
+ # save the text size so we can restore it later
+ save_text_size = self.tw.canvas.textsize
+ # set title text
+ self.tw.canvas.settextsize(self.title_height)
+ self._prim_show(title)
+ # calculate and set scale for media blocks
+ myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \
+ self.tw.canvas.height
+ self._prim_setscale(myscale)
+ # set body text size
+ self.tw.canvas.settextsize(self.tw.lc.body_height)
+ # render four quadrents
+ # leave some space below the title
+ y -= int(self.title_height * 2 * self.tw.lead)
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media1)
+ x = 0
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media2)
+ y = -self.title_height
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media4)
+ x = -(self.tw.canvas.width / 2) + xo
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media3)
+ # restore text size
+ self.tw.canvas.settextsize(save_text_size)
+
+ def _prim_t1x1a(self, title, media1):
+ """ title, one media object """
+ xo = self.tw.calc_position('t1x1a')[2]
+ x = -(self.tw.canvas.width / 2) + xo
+ y = self.tw.canvas.height / 2
+ self.tw.canvas.setxy(x, y, pendown=False)
+ # save the text size so we can restore it later
+ save_text_size = self.tw.canvas.textsize
+ # set title text
+ self.tw.canvas.settextsize(self.title_height)
+ self._prim_show(title)
+ # calculate and set scale for media blocks
+ myscale = 90 * (self.tw.canvas.height - self.title_height * 2) / \
+ self.tw.canvas.height
+ self._prim_setscale(myscale)
+ # set body text size
+ self.tw.canvas.settextsize(self.tw.lc.body_height)
+ # render media object
+ # leave some space below the title
+ y -= int(self.title_height * 2 * self.tw.lead)
+ self.tw.canvas.setxy(x, y, pendown=False)
+ self._prim_show(media1)
+ # restore text size
+ self.tw.canvas.settextsize(save_text_size)
+
+ def _prim_write(self, string, fsize):
+ """ Write string at size """
+ x = self.tw.canvas.width / 2 + int(self.tw.canvas.xcor)
+ y = self.tw.canvas.height / 2 - int(self.tw.canvas.ycor)
+ self.tw.canvas.draw_text(string, x, y - 15, int(fsize),
+ self.tw.canvas.width)
diff --git a/pysamples/COPYING b/pysamples/COPYING
new file mode 100644
index 0000000..9cdf1e6
--- /dev/null
+++ b/pysamples/COPYING
@@ -0,0 +1,21 @@
+Copyright (c) 2008-11, Walter Bender
+Copyright (c) 2008-11, TOny Forster
+
+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/pysamples/copy_from_heap.py b/pysamples/copy_from_heap.py
index 8483fde..fbca999 100644
--- a/pysamples/copy_from_heap.py
+++ b/pysamples/copy_from_heap.py
@@ -1,35 +1,16 @@
-#Copyright (c) 2010, Walter Bender, Tony Forster
+#Copyright (c) 2010-11, Walter Bender, Tony Forster
-#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:
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected.
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
+# Usage: Import this code into a Python (user-definable) block; when
+# this code is run, the FILO heap will be copied to the clipboard.
-#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 procedure is invoked when the user-definable block on the "extras"
-# palette is selected.
-
-def myblock(lc, x):
-
- ###########################################################################
- #
- # Copy heap to clipboard
- #
- ###########################################################################
+def myblock(tw, x): # second argument is ignored
+ ''' Copy heap to clipboard '''
from gtk import Clipboard
- from tautils import data_to_string
+ from TurtleArt.tautils import data_to_string
- Clipboard().set_text(data_to_string(lc.heap))
+ Clipboard().set_text(data_to_string(tw.lc.heap))
diff --git a/pysamples/dotted_line.py b/pysamples/dotted_line.py
index 9db7f9d..febd409 100644
--- a/pysamples/dotted_line.py
+++ b/pysamples/dotted_line.py
@@ -1,42 +1,23 @@
-#Copyright (c) 2009-10, Walter Bender
+#Copyright (c) 2009-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.
-
-#
# This procedure is invoked when the user-definable block on the "extras"
# palette is selected. Some examples of how to use this block are included
# below. Try uncommenting an example or write your own Python code.
-#
+#
# To uncomment code, remove the '# ' in the Python code. Take care to preserve
# the proper indentations.
#
-#
+#
# NOTES:
-#
+#
# Turtle Art is created in object oriented Python code. This is based
# around the definition of classes and the creation of object(s) which
# are instance(s) of that class. These objects then have properties and
# methods which are defined by their class.
-#
+#
# See http://docs.python.org/tutorial/classes.html for a description of
# classes in Python.
-#
+#
# Class Defined in Instance Created in
# TurtleArtWindow tawindow.py tw TurtleArtActivity.py
# LogoCode talogo.py lc tawindow.py
@@ -44,72 +25,72 @@
# Turtles, Turtle taturtle.py turtles tawindow.py,
# tacanvas.py
# Blocks, Block tablock.py block_list tawindow.py
-#
-#
+#
+#
# Class TurtleArtWindow -- useful properties and methods (from within
-# tamyblock.py, lc.tw is the class instance)
-#
+# tamyblock.py, tw is the class instance)
+#
# Methods and data attributes Example
-# set_fullscreen(self) lc.tw.set_fullscreen()
+# set_fullscreen(self) tw.set_fullscreen()
# Note: Hides the Sugar toolbar
-# set_cartesian(self, flag) lc.tw.set_cartesian(True)
+# set_cartesian(self, flag) tw.set_cartesian(True)
# Note: True will make the overlay visible;
# False will make it invisible
-# set_polar(self, flag) lc.tw.set_polar(True)
+# set_polar(self, flag) tw.set_polar(True)
# Note: True will make the overlay visible;
# False will make it invisible
-# hideshow_button(self, flag) lc.tw.hideshow_button()
+# hideshow_button(self, flag) tw.hideshow_button()
# Note: Toggles visibility of blocks and palettes
-# self.active_turtle lc.tw.active_turtle
+# self.active_turtle tw.active_turtle
# Note: The active turtle instance
-#
-#
+#
+#
# Class TurtleGraphics -- useful properties and methods (from within
-# tamyblock.py, lc.tw.canvas is the class instance)
-#
+# tamyblock.py, tw.canvas is the class instance)
+#
# Methods and data attributes Example
-# clearscreen(self) lc.tw.canvas.clearscreen()
+# clearscreen(self) tw.canvas.clearscreen()
# Note: Clears the screen and resets all turtle and
# pen attributes to default values
-# setpen(self, flag) lc.tw.canvas.setpen(True)
+# setpen(self, flag) tw.canvas.setpen(True)
# Note: True will set the pen "down", enabling drawing;
# False will set the pen "up"
-# forward(self, n) lc.tw.canvas.forward(100)
+# forward(self, n) tw.canvas.forward(100)
# Note: Move the turtle forward 100 units
-# arc(self, a, r) lc.tw.canvas.arc(120, 50)
+# arc(self, a, r) tw.canvas.arc(120, 50)
# Note: Move the turtle along an arc of 120 degrees
# (clockwise) and radius of 50 units
-# setheading(self, a) lc.tw.canvas.setheading(180)
+# setheading(self, a) tw.canvas.setheading(180)
# Note: Set the turtle heading to 180
# (towards the bottom of the screen)
-# self.heading lc.tw.canvas.heading
+# self.heading tw.canvas.heading
# Note: The current heading
-# setpensize(self, n) lc.tw.canvas.setpensize(25)
+# setpensize(self, n) tw.canvas.setpensize(25)
# Note: Set the turtle pensize to 25 units
-# self.pensize lc.tw.canvas.pensize
+# self.pensize tw.canvas.pensize
# Note: The current pensize
-# setcolor(self, c) lc.tw.canvas.color(70)
+# setcolor(self, c) tw.canvas.color(70)
# Note: Set the pen color to 70 (blue)
-# self.color lc.tw.canvas.color
+# self.color tw.canvas.color
# Note: The current pen color
-# setshade(self, s) lc.tw.canvas.shade(50)
+# setshade(self, s) tw.canvas.shade(50)
# Note: Set the pen shade to 50
-# self.shade lc.tw.canvas.shade
+# self.shade tw.canvas.shade
# Note: The current pen shade
-# fillscreen(self, c, s) lc.tw.canvas.fillscreen(70, 90)
+# fillscreen(self, c, s) tw.canvas.fillscreen(70, 90)
# Note: Fill the screen with color 70, shade 90 (light blue)
-# setxy(self, x, y) lc.tw.canvas.setxy(100,100)
+# setxy(self, x, y) tw.canvas.setxy(100,100)
# Note: Move the turtle to position (100, 100)
-# self.xcor lc.tw.canvas.xcor
+# self.xcor tw.canvas.xcor
# Note: The current x coordinate of the turtle
# (scaled to current units)
-# self.ycor lc.tw.canvas.ycor
+# self.ycor tw.canvas.ycor
# Note: The current y coordinate of the turtle
# (scaled to current units)
-# self.set_turtle(name) lc.tw.canvas.set_turtle(1)
+# self.set_turtle(name) tw.canvas.set_turtle(1)
# Note: Set the current turtle to turtle '1'
-#
-#
+#
+#
# Other useful Python functions
# Module Example
# from math import pow pow(2,3) returns 2 to the 3rd power
@@ -118,34 +99,35 @@
# Note: See http://docs.python.org/library/math.html
# from time import localtime localtime().tm_hour returns the current hour
# Note: See http://docs.python.org/library/time.html
-# lc.heap.append(data) adds data to the heap
+# tw.lc.heap.append(data) adds data to the heap
# Note: See http://docs.python.org/tutorial/datastructures.html
-# data = lc.heap.pop(-1) pops data off the heap
+# data = tw.lc.heap.pop(-1) pops data off the heap
# Note: See http://docs.python.org/tutorial/datastructures.html
#
-def myblock(lc, x):
+# Usage: Import this code into a Python (user-definable) block; when
+# this code is run, the turtle will draw a dotted line of the length
+# of the numeric argument block docked to the Python block.
+
- ###########################################################################
- #
- # Draw a dotted line of length x.
- #
- ###########################################################################
+def myblock(tw, line_length):
+ ''' Draw a dotted line of length line_length. '''
- try: # make sure x is a number
- x = float(x)
+ try: # make sure line_length is a number
+ line_length = float(line_length)
except ValueError:
return
- if lc.tw.canvas.pendown:
+ if tw.canvas.pendown:
dist = 0
- while dist+lc.tw.canvas.pensize < x: # repeat drawing dots
- lc.tw.canvas.setpen(True)
- lc.tw.canvas.forward(1)
- lc.tw.canvas.setpen(False)
- lc.tw.canvas.forward((lc.tw.canvas.pensize*2)-1)
- dist += (lc.tw.canvas.pensize*2)
- lc.tw.canvas.forward(x-dist) # make sure we have moved exactly x
- lc.tw.canvas.setpen(True)
+ while dist + tw.canvas.pensize < line_length: # repeat drawing dots
+ tw.canvas.setpen(True)
+ tw.canvas.forward(1)
+ tw.canvas.setpen(False)
+ tw.canvas.forward((tw.canvas.pensize * 2) - 1)
+ dist += (tw.canvas.pensize * 2)
+ # make sure we have moved exactly line_length
+ tw.canvas.forward(line_length - dist)
+ tw.canvas.setpen(True)
else:
- lc.tw.canvas.forward(x)
+ tw.canvas.forward(line_length)
return
diff --git a/pysamples/grecord.py b/pysamples/grecord.py
new file mode 100644
index 0000000..84b1a5c
--- /dev/null
+++ b/pysamples/grecord.py
@@ -0,0 +1,227 @@
+#Copyright (c) 2008, Media Modifications Ltd.
+#Copyright (c) 2011, Walter Bender
+
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected.
+
+# Usage: Import this code into a Python (user-definable) block; Pass
+# it 'start' to start recording; 'stop' to stop recording; 'play' to
+# play back your recording; or 'save' to save your recording to the
+# Sugar Journal.
+
+
+def myblock(tw, arg):
+ ''' Record and playback a sound (Sugar only) '''
+ import os
+ import time
+
+ import gtk
+ import gst
+
+ import gobject
+ gobject.threads_init()
+
+ from TurtleArt.tautils import get_path
+ from TurtleArt.tagplay import play_audio_from_file
+ from sugar.datastore import datastore
+ from sugar import profile
+
+ from gettext import gettext as _
+
+ class Grecord:
+ ''' A class for creating a gstreamer session for recording audio. '''
+
+ def __init__(self, tw):
+ ''' Set up the stream. We save to a raw .wav file and then
+ convert the sound to .ogg for saving. '''
+ datapath = get_path(tw.parent, 'instance')
+ self.capture_file = os.path.join(datapath, 'output.wav')
+ self.save_file = os.path.join(datapath, 'output.ogg')
+ self._eos_cb = None
+
+ self._can_limit_framerate = False
+ self._recording = False
+
+ self._audio_transcode_handler = None
+ self._transcode_id = None
+
+ self._pipeline = gst.Pipeline("Record")
+ self._create_audiobin()
+ self._pipeline.add(self._audiobin)
+
+ bus = self._pipeline.get_bus()
+ bus.add_signal_watch()
+ bus.connect('message', self._bus_message_handler)
+
+ def _create_audiobin(self):
+ ''' Assemble all the pieces we need. '''
+ src = gst.element_factory_make("alsasrc", "absrc")
+
+ # attempt to use direct access to the 0,0 device, solving some A/V
+ # sync issues
+ src.set_property("device", "plughw:0,0")
+ hwdev_available = src.set_state(gst.STATE_PAUSED) != \
+ gst.STATE_CHANGE_FAILURE
+ src.set_state(gst.STATE_NULL)
+ if not hwdev_available:
+ src.set_property("device", "default")
+
+ srccaps = gst.Caps("audio/x-raw-int,rate=16000,channels=1,depth=16")
+
+ # guarantee perfect stream, important for A/V sync
+ rate = gst.element_factory_make("audiorate")
+
+ # without a buffer here, gstreamer struggles at the start of the
+ # recording and then the A/V sync is bad for the whole video
+ # (possibly a gstreamer/ALSA bug -- even if it gets caught up, it
+ # should be able to resync without problem)
+ queue = gst.element_factory_make("queue", "audioqueue")
+ queue.set_property("leaky", True) # prefer fresh data
+ queue.set_property("max-size-time", 5000000000) # 5 seconds
+ queue.set_property("max-size-buffers", 500)
+ queue.connect("overrun", self._log_queue_overrun)
+
+ enc = gst.element_factory_make("wavenc", "abenc")
+
+ sink = gst.element_factory_make("filesink", "absink")
+ sink.set_property("location", self.capture_file)
+
+ self._audiobin = gst.Bin("audiobin")
+ self._audiobin.add(src, rate, queue, enc, sink)
+
+ src.link(rate, srccaps)
+ gst.element_link_many(rate, queue, enc, sink)
+
+ def _log_queue_overrun(self, queue):
+ ''' We use a buffer, which may overflow. '''
+ cbuffers = queue.get_property("current-level-buffers")
+ cbytes = queue.get_property("current-level-bytes")
+ ctime = queue.get_property("current-level-time")
+
+ def is_recording(self):
+ ''' Are we recording? '''
+ return self._recording
+
+ def _get_state(self):
+ ''' What is the state of our gstreamer pipeline? '''
+ return self._pipeline.get_state()[1]
+
+ def start_recording_audio(self):
+ ''' Start the stream in order to start recording. '''
+ if self._get_state() == gst.STATE_PLAYING:
+ return
+ self._pipeline.set_state(gst.STATE_PLAYING)
+ self._recording = True
+
+ def stop_recording_audio(self):
+ ''' Stop recording and then convert the results into a
+ .ogg file using a new stream. '''
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._recording = False
+
+ if not os.path.exists(self.capture_file) or \
+ os.path.getsize(self.capture_file) <= 0:
+ return
+
+ # Remove previous transcoding results.
+ if os.path.exists(self.save_file):
+ os.remove(self.save_file)
+
+ line = 'filesrc location=' + self.capture_file + ' name=audioFilesrc ! wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! filesink name=audioFilesink'
+ audioline = gst.parse_launch(line)
+
+ vorbis_enc = audioline.get_by_name('audioVorbisenc')
+
+ audioFilesink = audioline.get_by_name('audioFilesink')
+ audioFilesink.set_property("location", self.save_file)
+
+ audioBus = audioline.get_bus()
+ audioBus.add_signal_watch()
+ self._audio_transcode_handler = audioBus.connect(
+ 'message', self._onMuxedAudioMessageCb, audioline)
+ self._transcode_id = gobject.timeout_add(
+ 200, self._transcodeUpdateCb, audioline)
+ audioline.set_state(gst.STATE_PLAYING)
+
+ def _transcodeUpdateCb(self, pipe):
+ ''' Where are we in the transcoding process? '''
+ position, duration = self._query_position(pipe)
+ if position != gst.CLOCK_TIME_NONE:
+ value = position * 100.0 / duration
+ value = value/100.0
+ return True
+
+ def _query_position(self, pipe):
+ ''' Where are we in the stream? '''
+ try:
+ position, format = pipe.query_position(gst.FORMAT_TIME)
+ except:
+ position = gst.CLOCK_TIME_NONE
+
+ try:
+ duration, format = pipe.query_duration(gst.FORMAT_TIME)
+ except:
+ duration = gst.CLOCK_TIME_NONE
+
+ return (position, duration)
+
+ def _onMuxedAudioMessageCb(self, bus, message, pipe):
+ ''' Clean up at end of stream.'''
+ if message.type != gst.MESSAGE_EOS:
+ return True
+
+ gobject.source_remove(self._audio_transcode_handler)
+ self._audio_transcode_handler = None
+ gobject.source_remove(self._transcode_id)
+ self._transcode_id = None
+ pipe.set_state(gst.STATE_NULL)
+ pipe.get_bus().remove_signal_watch()
+ pipe.get_bus().disable_sync_message_emission()
+
+ os.remove(self.capture_file)
+ return False
+
+ def _bus_message_handler(self, bus, message):
+ ''' Handle any messages associated with the stream. '''
+ t = message.type
+ if t == gst.MESSAGE_EOS:
+ if self._eos_cb:
+ cb = self._eos_cb
+ self._eos_cb = None
+ cb()
+ elif t == gst.MESSAGE_ERROR:
+ # TODO: if we come out of suspend/resume with errors, then
+ # get us back up and running... TODO: handle "No space
+ # left on the resource.gstfilesink.c" err, debug =
+ # message.parse_error()
+ pass
+
+ # We store the audio-record stream instance as tw.grecord so that
+ # we can use it repeatedly.
+ if not hasattr(tw, 'grecord'):
+ tw.grecord = Grecord(tw)
+
+ # Sometime we need to parse multiple arguments, e.g., save, savename
+ save_name = _('Turtle Art') + ' ' + _('sound')
+ if type(arg) == type([]):
+ cmd = arg[0].lower()
+ if len(arg) > 1:
+ save_name = str(arg[1])
+ else:
+ cmd = arg.lower()
+
+ if cmd == 'start' or cmd == _('start').lower():
+ tw.grecord.start_recording_audio()
+ elif cmd == 'stop' or cmd == _('stop').lower():
+ tw.grecord.stop_recording_audio()
+ elif cmd == 'play' or cmd == _('play').lower():
+ play_audio_from_file(tw.lc, tw.grecord.save_file)
+ elif cmd == 'save' or cmd == _('save').lower():
+ if os.path.exists(tw.grecord.save_file) and tw.running_sugar:
+ dsobject = datastore.create()
+ dsobject.metadata['title'] = save_name
+ dsobject.metadata['icon-color'] = profile.get_color().to_string()
+ dsobject.metadata['mime_type'] = 'audio/ogg'
+ dsobject.set_file_path(tw.grecord.save_file)
+ datastore.write(dsobject)
+ dsobject.destroy()
diff --git a/pysamples/load_block.py b/pysamples/load_block.py
new file mode 100644
index 0000000..a457504
--- /dev/null
+++ b/pysamples/load_block.py
@@ -0,0 +1,78 @@
+#Copyright (c) 2011, Walter Bender
+
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected.
+
+# Usage: Import this code into a Python (user-definable) block; when
+# this code is run, the turtle will create a block at the current
+# location of the turtle. The first argument to the Python block
+# should be a string containing the name of the desired
+# block. Arguments to the block can be passed by expanding the Python
+# block to include up to two additional arguments. Note that the
+# turtle is moved to the bottom of the block after it is loaded in
+# order position another block to the bottom of the stack.
+
+# For example, try the following to place forward 100, right 90 on the canvas:
+# start
+# Python(forward, 100) <-- Python load_block.py expanded to two arguments
+# Python(right, 90) <-- Python load_block.py expanded to two arguments
+
+
+def myblock(tw, blkname):
+ ''' Load a block on to the canvas '''
+
+ from TurtleArt.tapalette import block_names, block_primitives, \
+ special_names, content_blocks
+ from TurtleArt.tautils import find_group
+
+ def make_block(tw, name, x, y, defaults):
+ x_pos = x + 20
+ y_pos = y + 20
+ tw._new_block(name, x_pos, y_pos, defaults)
+
+ # Find the block we just created and attach it to a stack.
+ tw.drag_group = None
+ spr = tw.sprite_list.find_sprite((x_pos, y_pos))
+ if spr is not None:
+ blk = tw.block_list.spr_to_block(spr)
+ if blk is not None:
+ tw.drag_group = find_group(blk)
+ tw._snap_to_dock()
+
+ # Disassociate new block from mouse.
+ tw.drag_group = None
+ return blk.height
+
+ def find_block(tw, blkname, x, y, defaults=None):
+ """ Create a new block. It is a bit more work than just calling
+ _new_block(). We need to:
+ (1) translate the label name into the internal block name;
+ (2) 'dock' the block onto a stack where appropriate; and
+ (3) disassociate the new block from the mouse. """
+
+ print blkname
+ for name in block_names:
+ # Translate label name into block/prim name.
+ if blkname in block_names[name]:
+ if (name in block_primitives and \
+ block_primitives[name] == name) or \
+ name in content_blocks:
+ return make_block(tw, name, x, y, defaults)
+ for name in special_names:
+ # Translate label name into block/prim name.
+ if blkname in special_names[name]:
+ return make_block(tw, name, x, y, defaults)
+ return -1
+
+ # Place the block at the active turtle (x, y) and move the turtle
+ # into position to place the next block in the stack.
+ x, y = tw.active_turtle.get_xy()
+ if type(blkname) == type([]):
+ name = blkname[0]
+ value = blkname[1:]
+ dy = int(find_block(tw, name, x, y, value))
+ else:
+ name = blkname
+ dy = int(find_block(tw, name, x, y))
+
+ tw.active_turtle.move((x, y - dy))
diff --git a/pysamples/load_journal_entry_to_heap.py b/pysamples/load_journal_entry_to_heap.py
index 7416554..3dd3bb5 100644
--- a/pysamples/load_journal_entry_to_heap.py
+++ b/pysamples/load_journal_entry_to_heap.py
@@ -1,35 +1,18 @@
-#Copyright (c) 2010, Walter Bender, Tony Forster
+#Copyright (c) 2010-11, Walter Bender, Tony Forster
-#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:
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected.
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
+# Usage: Import this code into a Python (user-definable) block; when
+# this code is run, the chooser will be opened for selecting a file
+# from the Journal. The contents of that file will be loaded onto the
+# FILO heap.
-#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 procedure is invoked when the user-definable block on the "extras"
-# palette is selected.
+def myblock(tw, x): # ignore second argument
+ ''' Load heap from journal (Sugar only) '''
-def myblock(lc, x):
-
- ###########################################################################
- #
- # Load heap from journal
- #
- ###########################################################################
-
- from tautils import chooser
+ from TurtleArt.tautils import chooser
# Choose a datastore object and push data to heap (Sugar only)
- chooser(lc.tw.parent, '', lc.push_file_data_to_heap)
+ chooser(tw.parent, '', tw.lc.push_file_data_to_heap)
diff --git a/pysamples/paste_to_heap.py b/pysamples/paste_to_heap.py
index 9255a93..0371078 100644
--- a/pysamples/paste_to_heap.py
+++ b/pysamples/paste_to_heap.py
@@ -1,33 +1,15 @@
-#Copyright (c) 2010, Walter Bender, Tony Forster
-
-#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.
+#Copyright (c) 2010-11, Walter Bender, Tony Forster
# This procedure is invoked when the user-definable block on the "extras"
# palette is selected.
-def myblock(lc, x):
+# Usage: Import this code into a Python (user-definable) block; when
+# this code is run, the contents of the clipboard will be appended to
+# the FILO heap.
+
- ###########################################################################
- #
- # Paste from clipboard to heap
- #
- ###########################################################################
+def myblock(tw, x): # ignore second argument
+ ''' Paste from clipboard to heap '''
from gtk import Clipboard
from tautils import data_from_string
@@ -35,5 +17,5 @@ def myblock(lc, x):
text = Clipboard().wait_for_text()
if text is not None:
for val in data_from_string(text):
- lc.heap.append(val)
- lc.update_label_value('pop', val)
+ tw.lc.heap.append(val)
+ tw.lc.update_label_value('pop', val)
diff --git a/pysamples/push_mouse_event.py b/pysamples/push_mouse_event.py
index 485061f..b14315c 100644
--- a/pysamples/push_mouse_event.py
+++ b/pysamples/push_mouse_event.py
@@ -1,40 +1,26 @@
-#Copyright (c) 2009-10, Walter Bender, Tony Forster
+#Copyright (c) 2009-11, Walter Bender, Tony Forster
-#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:
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected.
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
+# Usage: Import this code into a Python (user-definable) block; when
+# this code is run, the current mouse status will be pushed to the
+# FILO heap. If a mouse button event occurs, a y, x, and 1 are pushed
+# to the heap. If no button is pressed, 0 is pushed to the heap.
-#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.
+# To use these data, pop the heap in a compare block to determine if a
+# button has been pushed. If a 1 was popped from the heap, pop the x
+# and y coordinates.
-#
-# This procedure is invoked when the user-definable block on the "extras"
-# palette is selected.
-def myblock(lc, x):
+def myblock(tw, x): # ignore second argument
+ ''' Push mouse event to stack '''
- ###########################################################################
- #
- # Push mouse event to stack
- #
- ###########################################################################
-
- if lc.tw.mouse_flag == 1:
+ if tw.mouse_flag == 1:
# push y first so x will be popped first
- lc.heap.append((lc.tw.canvas.height / 2) - lc.tw.mouse_y)
- lc.heap.append(lc.tw.mouse_x - (lc.tw.canvas.width / 2))
- lc.heap.append(1) # mouse event
- lc.tw.mouse_flag = 0
+ tw.lc.heap.append((tw.canvas.height / 2) - tw.mouse_y)
+ tw.lc.heap.append(tw.mouse_x - (tw.canvas.width / 2))
+ tw.lc.heap.append(1) # mouse event
+ tw.mouse_flag = 0
else:
- lc.heap.append(0) # no mouse event
+ tw.lc.heap.append(0) # no mouse event
diff --git a/pysamples/push_time.py b/pysamples/push_time.py
index ae22684..5f86be4 100644
--- a/pysamples/push_time.py
+++ b/pysamples/push_time.py
@@ -1,37 +1,21 @@
-#Copyright (c) 2009-10, Walter Bender
+#Copyright (c) 2009-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:
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected.
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
+# Usage: Import this code into a Python (user-definable) block; when
+# this code is run, the current hour, minute, and second are pushed to
+# the FILO heap. To use these values, pop second, then minute, then
+# hour from the FILO.
-#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 procedure is invoked when the user-definable block on the "extras"
-# palette is selected.
+def myblock(tw, x): # ignore second argument
+ ''' Push hours, minutes, seconds onto the FILO. '''
-def myblock(lc, x):
-
- ###########################################################################
- #
- # Push hours, minutes, seconds onto the FILO.
# Use three 'pop' blocks to retrieve these values.
# Note: because we use a FILO (first in, last out heap),
# the first value you pop off of the FILO will be seconds.
- #
- ###########################################################################
- lc.heap.append(localtime().tm_hour)
- lc.heap.append(localtime().tm_min)
- lc.heap.append(localtime().tm_sec)
+ tw.lc.heap.append(localtime().tm_hour)
+ tw.lc.heap.append(localtime().tm_min)
+ tw.lc.heap.append(localtime().tm_sec)
diff --git a/pysamples/save_heap_to_journal_entry.py b/pysamples/save_heap_to_journal_entry.py
index c4f2d50..a06d4d0 100644
--- a/pysamples/save_heap_to_journal_entry.py
+++ b/pysamples/save_heap_to_journal_entry.py
@@ -1,33 +1,16 @@
-#Copyright (c) 2010, Walter Bender, Tony Forster
+#Copyright (c) 2010-11, Walter Bender, Tony Forster
-#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:
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected.
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
+# Usage: Import this code into a Python (user-definable) block; when
+# this code is run, the contents of the FILO heap are saved to a
+# Journal entry named by the value of the argument to the Python
+# block.
-#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 procedure is invoked when the user-definable block on the "extras"
-# palette is selected.
-
-def myblock(lc, x):
-
- ###########################################################################
- #
- # Save heap to journal (Sugar only)
- #
- ###########################################################################
+def myblock(tw, title):
+ ''' Save heap to journal (Sugar only) '''
import os.path
from gettext import gettext as _
@@ -36,18 +19,19 @@ def myblock(lc, x):
from sugar.datastore import datastore
from sugar import profile
- from tautils import get_path, data_to_file
+ from TurtleArt.tautils import get_path, data_to_file
# Save JSON-encoded heap to temporary file
- heap_file = os.path.join(get_path(activity, 'instance'), str(x) + '.txt')
- data_to_file(lc.heap, heap_file)
+ heap_file = os.path.join(get_path(activity, 'instance'),
+ str(title) + '.txt')
+ data_to_file(tw.lc.heap, heap_file)
# Create a datastore object
dsobject = datastore.create()
# Write any metadata (specifically set the title of the file
# and specify that this is a plain text file).
- dsobject.metadata['title'] = str(x)
+ dsobject.metadata['title'] = str(title)
dsobject.metadata['icon-color'] = profile.get_color().to_string()
dsobject.metadata['mime_type'] = 'text/plain'
dsobject.set_file_path(heap_file)
diff --git a/pysamples/set_rgb.py b/pysamples/set_rgb.py
index f524ec6..d4baa48 100644
--- a/pysamples/set_rgb.py
+++ b/pysamples/set_rgb.py
@@ -1,50 +1,19 @@
-#Copyright (c) 2009-10, Walter Bender
+#Copyright (c) 2009-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:
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected and expanded to 3 arguments.
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
+# Usage: Import this code into a Python (user-definable) block.
+# First, expand the Python block to reveal three numerics arguments.
+# Set these values to the desired red, green, and blue. When the code
+# is run, the red, green, and blue values are used to set the pen
+# color.
-#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 procedure is invoked when the user-definable block on the "extras"
-# palette is selected and expanded to 3 arguments.
-
-def myblock(lc, x):
-
- ###########################################################################
- #
- # Set rgb color from values
- #
- ###########################################################################
-
- # assuming x is an array [r, g, b]
- b = int(x[2])
- while b < 0:
- b += 256
- while b > 255:
- b -= 256
- g = int(x[1])
- while g < 0:
- g += 256
- while g > 255:
- g -= 256
- r = int(x[0])
- while r < 0:
- r += 256
- while r > 255:
- r -= 256
- rgb = "#%02x%02x%02x" % (r,g,b)
- lc.tw.canvas.fgcolor = lc.tw.canvas.cm.alloc_color(rgb)
+def myblock(tw, rgb_array):
+ ''' Set rgb color from values '''
+
+ rgb = "#%02x%02x%02x" % ((int(rgb_array[0]) % 256),
+ (int(rgb_array[1]) % 256),
+ (int(rgb_array[2]) % 256))
+ tw.canvas.fgcolor = tw.canvas.cm.alloc_color(rgb)
diff --git a/pysamples/sinewave.py b/pysamples/sinewave.py
index eea975d..4f14c4c 100644
--- a/pysamples/sinewave.py
+++ b/pysamples/sinewave.py
@@ -1,33 +1,15 @@
-#Copyright (c) 2010, Tony Forster
+#Copyright (c) 2010-11, Tony Forster
-#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:
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected.
-#The above copyright notice and this permission notice shall be included in
-#all copies or substantial portions of the Software.
+# Usage: Import this code into a Python (user-definable) block and
+# pass a frequency in Hertz to the Python block. A tone will play over
+# the speaker at the specified frequency.
-#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 procedure is invoked when the user-definable block on the "extras"
-# palette is selected.
-
-def myblock(lc, x):
-
- ###########################################################################
- #
- # Plays a sound at frequency x
- #
- ###########################################################################
+def myblock(tw, frequency):
+ ''' Plays a sound at frequency frequency '''
import os
- os.system('speaker-test -t sine -l 1 -f %d' % (int(x)))
+ os.system('speaker-test -t sine -l 1 -f %d' % (int(frequency)))
diff --git a/pysamples/speak.py b/pysamples/speak.py
new file mode 100644
index 0000000..30762a9
--- /dev/null
+++ b/pysamples/speak.py
@@ -0,0 +1,57 @@
+#Copyright (c) 2009-11, Walter Bender, Tony Forster
+
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected.
+
+# Usage: Import this code into a Python (user-definable) block and
+# pass a string to be read by the voice synthesizer. If a second
+# argument is passed, by expanding the Python block, it is used to specify
+# the pitch level of the speaker. Valid range is 0 to 99.
+
+
+def myblock(tw, arg):
+ ''' Text to speech '''
+
+ TABLE = {'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'}
+ import os
+
+ pitch = None
+ if type(arg) == type([]):
+ text = arg[0]
+ if len(arg) > 1:
+ pitch = int(arg[1])
+ if pitch > 99:
+ pitch = 99
+ elif pitch < 0:
+ pitch = 0
+ else:
+ text = arg
+
+ # Turtle Art numbers are passed as float,
+ # but they may be integer values.
+ if type(text) == float and int(text) == text:
+ text = int(text)
+
+ lang = os.environ['LANG'][0:2]
+ if lang in TABLE:
+ language_option = '-v ' + TABLE[lang]
+ else:
+ language_option = ''
+ if pitch is None:
+ os.system('espeak %s "%s" --stdout | aplay' % (language_option,
+ text))
+ else:
+ os.system('espeak %s "%s" -p "%s" --stdout | aplay' % (
+ language_option, text, pitch))
diff --git a/pysamples/svg_end_group.py b/pysamples/svg_end_group.py
new file mode 100644
index 0000000..e4682b3
--- /dev/null
+++ b/pysamples/svg_end_group.py
@@ -0,0 +1,17 @@
+#Copyright (c) 2009-11, Walter Bender
+
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected and expanded to 3 arguments.
+
+# Usage: Import this code into a Python (user-definable) block.
+# First, expand the Python block to reveal three numerics arguments.
+# Set these values to the desired red, green, and blue. When the code
+# is run, the red, green, and blue values are used to set the pen
+# color.
+
+
+def myblock(tw, x):
+ ''' Add end group to SVG output '''
+
+ tw.svg_string += '</g>'
+
diff --git a/pysamples/svg_start_group.py b/pysamples/svg_start_group.py
new file mode 100644
index 0000000..57500bc
--- /dev/null
+++ b/pysamples/svg_start_group.py
@@ -0,0 +1,17 @@
+#Copyright (c) 2009-11, Walter Bender
+
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected and expanded to 3 arguments.
+
+# Usage: Import this code into a Python (user-definable) block.
+# First, expand the Python block to reveal three numerics arguments.
+# Set these values to the desired red, green, and blue. When the code
+# is run, the red, green, and blue values are used to set the pen
+# color.
+
+
+def myblock(tw, x):
+ ''' Add start group to SVG output '''
+
+ tw.svg_string += '<g>'
+
diff --git a/pysamples/uturn.py b/pysamples/uturn.py
new file mode 100644
index 0000000..0b164a0
--- /dev/null
+++ b/pysamples/uturn.py
@@ -0,0 +1,35 @@
+#Copyright (c) 2011, Walter Bender
+
+# This procedure is invoked when the user-definable block on the
+# "extras" palette is selected.
+
+# Usage: Import this code into a Python (user-definable) block; when
+# it is run, a u-turn block will be added to the Turtle Palette. You
+# can use the u-turn block as you would any other block.
+
+
+def myblock(tw, arg):
+ ''' Add a uturn block to the 'turtle' palette '''
+
+ from TurtleArt.tapalette import make_palette, palette_name_to_index
+ from TurtleArt.talogo import primitive_dictionary
+ from gettext import gettext as _
+
+ # Choose a palette for the new block.
+ palette = make_palette('turtle')
+
+ # Create a new block prototype.
+ palette.add_block('uturn',
+ style='basic-style-extended-vertical',
+ label=_('uturn'),
+ prim_name='uturn',
+ help_string=_('make a uturn'))
+
+ # Add its primitive to the LogoCode dictionary.
+ tw.lc.def_prim('uturn', 0,
+ lambda self: primitive_dictionary['set'](
+ 'heading', tw.canvas.seth, tw.canvas.heading + 180))
+
+ # Regenerate the palette, which will now include the new block.
+ tw.show_toolbar_palette(palette_name_to_index('turtle'),
+ regenerate=True)
diff --git a/samples/dice.ta b/samples/dice.ta
new file mode 100644
index 0000000..7d45893
--- /dev/null
+++ b/samples/dice.ta
@@ -0,0 +1,99 @@
+[[0, ["start", 2.0], 743, 65, [null, 24]],
+[1, ["random", 0], 273, 616, [7, 2, 3, null]],
+[2, ["number", 1], 333, 616, [1, null]],
+[3, ["number", 6], 333, 658, [1, null]],
+[4, ["random", 0], 273, 534, [7, 5, 6, null]],
+[5, ["number", 1], 333, 534, [4, null]],
+[6, ["number", 6], 333, 576, [4, null]],
+[7, ["plus2", 20], 219, 534, [11, 4, 1]],
+[8, ["storein", 0], 85, 698, [15, 13, 14, null]],
+[9, ["number", 1], 237, 782, [14, null]],
+[10, "box", 237, 740, [14, 12, null]],
+[11, "storeinbox1", 85, 534, [16, 7, 15]],
+[12, "box1", 295, 740, [10, null]],
+[13, "box1", 183, 698, [8, null]],
+[14, ["plus2", 0], 183, 740, [8, 10, 9]],
+[15, ["vspace", 40], 85, 576, [11, 8]],
+[16, "repeat", 22, 474, [18, 27, 11, 17]],
+[17, ["vspace", 0], 22, 552, [16, null]],
+[18, "hat", 22, 424, [null, 19, 16]],
+[19, ["string", "toss dice"], 83, 432, [18, null]],
+[20, "stack", 743, 233, [31, 21, 22]],
+[21, ["string", "toss dice"], 804, 233, [20, null]],
+[22, "stack", 743, 275, [20, 23, null]],
+[23, ["string", "plot results"], 804, 275, [22, null]],
+[24, ["storein", 0], 743, 107, [0, 25, 26, 31]],
+[25, ["string", "trials"], 841, 107, [24, null]],
+[26, ["number", 1600], 841, 149, [24, null]],
+[27, "box", 71, 474, [16, 28, null]],
+[28, ["string", "trials"], 129, 474, [27, null]],
+[29, "hat", 1086, 452, [null, 30, 36]],
+[30, ["string", "clear bins"], 1147, 460, [29, null]],
+[31, "stack", 743, 191, [24, 32, 20]],
+[32, ["string", "clear bins"], 804, 191, [31, null]],
+[33, ["storein", 0], 1149, 604, [42, 35, 34, 38]],
+[34, ["number", 0], 1247, 646, [33, null]],
+[35, "box1", 1247, 604, [33, null]],
+[36, "storeinbox1", 1086, 502, [29, 37, 42]],
+[37, ["number", 1], 1220, 502, [36, null]],
+[38, "storeinbox1", 1149, 688, [33, 41, null]],
+[39, ["number", 1], 1337, 730, [41, null]],
+[40, "box1", 1337, 688, [41, null]],
+[41, ["plus2", 0], 1283, 688, [38, 40, 39]],
+[42, "repeat", 1086, 544, [36, 43, 33, 44]],
+[43, ["number", 12], 1135, 544, [42, null]],
+[44, ["vspace", 0], 1086, 622, [42, null]],
+[45, "hat", 528, 428, [null, 46, 82]],
+[46, ["string", "plot results"], 589, 436, [45, null]],
+[47, "penup", 591, 664, [58, 49]],
+[48, "pendown", 591, 996, [90, 78]],
+[49, ["setxy2", 40], 591, 706, [47, 54, 50, 86]],
+[50, ["number", 0], 656, 828, [49, null]],
+[51, "storeinbox1", 528, 562, [83, 52, 58]],
+[52, ["number", 2], 662, 562, [51, null]],
+[53, "box1", 764, 748, [55, null]],
+[54, ["product2", 0], 656, 706, [49, 57, 55]],
+[55, ["minus2", 0], 710, 748, [54, 53, 56]],
+[56, ["number", 6], 788, 790, [55, null]],
+[57, ["number", 50], 710, 706, [54, null]],
+[58, "repeat", 528, 604, [51, 59, 47, 97]],
+[59, ["number", 11], 577, 604, [58, null]],
+[60, ["vspace", 40], 591, 1284, [73, 74]],
+[61, "repeat", 591, 1164, [72, 62, 64, 73]],
+[62, ["number", 2], 640, 1164, [61, null]],
+[63, ["vspace", 0], 591, 1080, [78, 72]],
+[64, "forward", 654, 1224, [61, 85, 66]],
+[65, "box1", 791, 1224, [85, null]],
+[66, "right", 654, 1266, [64, 67, 68]],
+[67, ["number", 90], 729, 1266, [66, null]],
+[68, "forward", 654, 1308, [66, 69, 70]],
+[69, ["number", 40], 733, 1308, [68, null]],
+[70, "right", 654, 1350, [68, 71, null]],
+[71, ["number", 90], 729, 1350, [70, null]],
+[72, "startfill", 591, 1122, [63, 61]],
+[73, "stopfill", 591, 1242, [61, 60]],
+[74, "storeinbox1", 591, 1406, [60, 77, null]],
+[75, ["number", 1], 779, 1448, [77, null]],
+[76, "box1", 779, 1406, [77, null]],
+[77, ["plus2", 0], 725, 1406, [74, 76, 75]],
+[78, "setcolor", 591, 1038, [48, 80, 63]],
+[79, ["number", 10], 730, 1080, [80, null]],
+[80, ["product2", 0], 676, 1038, [78, 81, 79]],
+[81, "box1", 730, 1038, [80, null]],
+[82, "clean", 528, 478, [45, 83]],
+[83, "setshade", 528, 520, [82, 84, 51]],
+[84, ["number", 25], 609, 520, [83, null]],
+[85, "box", 733, 1224, [64, 65, null]],
+[86, "back", 591, 870, [49, 87, 88]],
+[87, ["number", 50], 649, 870, [86, null]],
+[88, "show", 591, 912, [86, 89, 90]],
+[89, "box1", 664, 912, [88, null]],
+[90, "forward", 591, 954, [88, 91, 48]],
+[91, ["number", 50], 670, 954, [90, null]],
+[92, "penup", 528, 1444, [97, 94]],
+[93, "pendown", 528, 1570, [94, null]],
+[94, ["setxy2", 0], 528, 1486, [92, 95, 96, 93]],
+[95, ["number", 0], 593, 1486, [94, null]],
+[96, ["number", -50], 593, 1528, [94, null]],
+[97, ["vspace", 360], 528, 682, [58, 92]],
+[-1, ["turtle", "Yertle"], 0.0, -50.0, 0.0, 120.0, 25.0, 5]] \ No newline at end of file
diff --git a/samples/hoops.ta b/samples/hoops.ta
new file mode 100644
index 0000000..2596cb4
--- /dev/null
+++ b/samples/hoops.ta
@@ -0,0 +1,101 @@
+[[0, ["start", 2.0], 639, 62, [null, 100]],
+[1, "skin", 639, 398, [24, 2, 49]],
+[2, ["journal", "./samples/images/basketball.png"], 821, 398, [1, null]],
+[3, ["setxy2", 60], 19, 243, [16, 5, 33, null]],
+[4, "xcor", 138, 243, [5, null]],
+[5, ["plus2", 0], 84, 243, [3, 4, 63]],
+[6, ["setxy2", 0], 564, 780, [14, 26, 7, 29]],
+[7, ["number", 0], 629, 822, [6, null]],
+[8, "seth", 220, 827, [12, 11, 36]],
+[9, ["number", 90], 372, 869, [11, null]],
+[10, "heading", 372, 827, [11, null]],
+[11, ["plus2", 0], 318, 827, [8, 10, 9]],
+[12, "wait", 220, 785, [18, 13, 8]],
+[13, ["number", 0.25], 291, 785, [12, null]],
+[14, "penup", 564, 738, [53, 6]],
+[15, "pendown", 564, 948, [47, null]],
+[16, "hat", 19, 193, [null, 17, 3]],
+[17, ["string", "parabola"], 80, 201, [16, null]],
+[18, "stack", 220, 743, [40, 19, 12]],
+[19, ["string", "parabola"], 281, 743, [18, null]],
+[20, "show", 639, 314, [21, 23, 24]],
+[21, "setscale", 639, 272, [68, 22, 20]],
+[22, ["number", 100], 735, 272, [21, null]],
+[23, ["journal", "./samples/images/basketball-court1-a.png"], 712, 314, [20, null]],
+[24, "setscale", 639, 356, [20, 25, 1]],
+[25, ["number", 33], 735, 356, [24, null]],
+[26, "leftpos", 629, 780, [6, null]],
+[27, "storeinbox2", 220, 911, [36, 35, 38]],
+[28, ["number", 1], 432, 953, [35, null]],
+[29, "storeinbox2", 564, 864, [6, 30, 47]],
+[30, ["number", 40.0], 698, 864, [29, null]],
+[31, "box2", 408, 911, [35, null]],
+[32, "box2", 192, 447, [64, null]],
+[33, ["plus2", 0], 84, 405, [3, 34, 64]],
+[34, "ycor", 138, 405, [33, null]],
+[35, ["minus2", 0], 354, 911, [27, 31, 28]],
+[36, ["vspace", 0], 220, 869, [8, 27]],
+[37, "forever", 15, 615, [45, 39, null]],
+[38, ["vspace", 0], 220, 953, [27, 85]],
+[39, "ifelse", 116, 633, [37, 42, 40, 41, null]],
+[40, ["vspace", 0], 220, 701, [39, 18]],
+[41, "stopstack", 272, 701, [39, null]],
+[42, ["greater2", 0], 206, 599, [39, 44, 43, null]],
+[43, ["number", -1], 276, 641, [42, null]],
+[44, "ycor", 252, 599, [42, null]],
+[45, "hat", 15, 565, [null, 46, 37]],
+[46, ["string", "shoot"], 76, 573, [45, null]],
+[47, "stack", 564, 906, [29, 48, 15]],
+[48, ["string", "shoot"], 625, 906, [47, null]],
+[49, "setpensize", 639, 440, [1, 50, 54]],
+[50, ["number", 1], 746, 440, [49, null]],
+[51, "storeinbox1", 488, 586, [81, 52, 55]],
+[52, "volume", 622, 586, [51, null]],
+[53, "if", 488, 670, [55, 56, 14, null]],
+[54, "forever", 639, 482, [49, 83, null]],
+[55, ["vspace", 0], 488, 628, [51, 53]],
+[56, ["greater2", 0], 550, 636, [53, 57, 58, null]],
+[57, "box1", 596, 636, [56, null]],
+[58, ["number", 300.0], 620, 678, [56, null]],
+[59, ["division2", 0], 192, 327, [63, 61, 62]],
+[60, ["number", 30.0], 192, 285, [63, null]],
+[61, "box1", 246, 327, [59, null]],
+[62, "width", 270, 369, [59, null]],
+[63, ["product2", 0], 138, 285, [5, 60, 59]],
+[64, ["product2", 0], 138, 447, [33, 32, 65]],
+[65, ["division2", 0], 192, 489, [64, 66, 67]],
+[66, "box1", 246, 489, [65, null]],
+[67, "height", 270, 531, [65, null]],
+[68, ["fillscreen", 0], 639, 188, [98, 69, 70, 21]],
+[69, ["number", 60], 779, 188, [68, null]],
+[70, ["number", 80], 779, 230, [68, null]],
+[71, "readpixel", 921, 139, [87, 80]],
+[72, "pop", 1029, 189, [89, null]],
+[73, "pop", 1105, 299, [90, null]],
+[74, "pop", 1181, 409, [91, null]],
+[75, "if", 921, 223, [80, 89, 76, null]],
+[76, ["vspace", 0], 997, 291, [75, 77]],
+[77, "if", 997, 333, [76, 90, 78, null]],
+[78, ["vspace", 0], 1073, 401, [77, 79]],
+[79, "if", 1073, 443, [78, 91, 95, null]],
+[80, ["vspace", 0], 921, 181, [71, 75]],
+[81, "hat", 488, 536, [null, 82, 51]],
+[82, ["string", "trigger"], 549, 544, [81, null]],
+[83, "stack", 740, 500, [54, 84, null]],
+[84, ["string", "trigger"], 801, 500, [83, null]],
+[85, "stack", 220, 995, [38, 86, 99]],
+[86, ["string", "test"], 281, 995, [85, null]],
+[87, "hat", 921, 89, [null, 88, 71]],
+[88, ["string", "test"], 982, 97, [87, null]],
+[89, ["equal2", 0], 983, 189, [75, 72, 92, null]],
+[90, ["equal2", 0], 1059, 299, [77, 73, 93, null]],
+[91, ["equal2", 0], 1135, 409, [79, 74, 94, null]],
+[92, ["number", 255.0], 1029, 231, [89, null]],
+[93, ["number", 255.0], 1105, 341, [90, null]],
+[94, ["number", 255.0], 1181, 451, [91, null]],
+[95, "print", 1149, 511, [79, 96, 97]],
+[96, ["string", "BASKET"], 1226, 511, [95, null]],
+[97, "stopstack", 1149, 553, [95, null]],
+[98, "clean", 639, 146, [100, 68]],
+[99, "clearheap", 220, 1037, [85, null]],
+[100, "hideblocks", 639, 104, [0, 98]]]
diff --git a/samples/images/basketball-court1-a.png b/samples/images/basketball-court1-a.png
new file mode 100644
index 0000000..dd3d6bb
--- /dev/null
+++ b/samples/images/basketball-court1-a.png
Binary files differ
diff --git a/samples/images/basketball.png b/samples/images/basketball.png
new file mode 100644
index 0000000..e8af3fd
--- /dev/null
+++ b/samples/images/basketball.png
Binary files differ
diff --git a/samples/images/turtle-a.png b/samples/images/turtle-a.png
new file mode 100644
index 0000000..e2c897a
--- /dev/null
+++ b/samples/images/turtle-a.png
Binary files differ
diff --git a/samples/images/turtle-b.png b/samples/images/turtle-b.png
new file mode 100644
index 0000000..3b45dd7
--- /dev/null
+++ b/samples/images/turtle-b.png
Binary files differ
diff --git a/samples/loudness-monitor.ta b/samples/loudness-monitor.ta
new file mode 100644
index 0000000..7bb0b5f
--- /dev/null
+++ b/samples/loudness-monitor.ta
@@ -0,0 +1,74 @@
+[[0, ["start", 2.0], 1217, 22, [null, 11]],
+[1, ["setxy2", 20], 1434, 84, [8, 3, 6, 46]],
+[2, "bottompos", 1546, 166, [6, null]],
+[3, "random", 1492, 84, [1, 4, 5, null]],
+[4, "leftpos", 1578, 84, [3, null]],
+[5, "rightpos", 1578, 126, [3, null]],
+[6, ["plus2", 0], 1492, 166, [1, 2, 7]],
+[7, ["number", 100], 1546, 208, [6, null]],
+[8, "penup", 1434, 42, [10, 1]],
+[9, "pendown", 1434, 250, [46, 52]],
+[10, "hat1", 1434, 0, [null, 8]],
+[11, "stack1", 1217, 64, [0, 12]],
+[12, "stack2", 1217, 106, [11, null]],
+[13, "hat2", 833, 0, [null, 14]],
+[14, "forever", 833, 42, [13, 38, 15]],
+[15, ["vspace", 0], 833, 78, [14, null]],
+[16, "storeinbox1", 894, 312, [71, 17, 40]],
+[17, "volume", 1012, 312, [16, null]],
+[18, "storeinbox1", 1434, 460, [47, 19, 42]],
+[19, ["number", 0.0], 1552, 460, [18, null]],
+[20, "forward", 1224, 433, [28, 27, 21]],
+[21, "right", 1224, 475, [20, 22, 23]],
+[22, ["number", 90], 1282, 475, [21, null]],
+[23, "forward", 1224, 517, [21, 24, 25]],
+[24, ["number", 25.0], 1294, 517, [23, null]],
+[25, "right", 1224, 559, [23, 26, null]],
+[26, ["number", 90], 1282, 559, [25, null]],
+[27, "box1", 1294, 433, [20, null]],
+[28, "repeat", 1160, 373, [50, 29, 20, 51]],
+[29, ["number", 2.0], 1210, 373, [28, null]],
+[30, "hat", 1160, 281, [null, 31, 50]],
+[31, ["string", "bar"], 1219, 289, [30, null]],
+[32, "stack", 894, 102, [38, 33, 64]],
+[33, ["string", "bar"], 953, 102, [32, null]],
+[34, "stack", 894, 396, [40, 35, 36]],
+[35, ["string", "bar"], 953, 396, [34, null]],
+[36, "wait", 894, 438, [34, 37, 62]],
+[37, ["number", 0.5], 952, 438, [36, null]],
+[38, "setcolor", 894, 60, [14, 39, 32]],
+[39, "white", 972, 60, [38, null]],
+[40, "setcolor", 894, 354, [16, 41, 34]],
+[41, "box2", 972, 354, [40, null]],
+[42, "storeinbox2", 1434, 502, [18, 43, null]],
+[43, "random", 1552, 502, [42, 44, 45, null]],
+[44, ["number", 0], 1638, 502, [43, null]],
+[45, ["number", 100], 1638, 544, [43, null]],
+[46, ["vspace", 0], 1434, 208, [1, 9]],
+[47, ["fillscreen", 0], 1434, 376, [52, 48, 49, 18]],
+[48, ["number", 60], 1520, 376, [47, null]],
+[49, "white", 1520, 418, [47, null]],
+[50, "startfill", 1160, 331, [30, 28]],
+[51, "stopfill", 1160, 451, [28, null]],
+[52, ["storein", 0], 1434, 292, [9, 53, 54, 47]],
+[53, ["string", "max"], 1503, 292, [52, null]],
+[54, ["number", 0.0], 1503, 334, [52, null]],
+[55, "box", 1002, 530, [63, 56, null]],
+[56, ["string", "max"], 1056, 530, [55, null]],
+[57, ["storein", 0], 946, 590, [61, 58, 59, null]],
+[58, ["string", "max"], 1015, 590, [57, null]],
+[59, "box1", 1015, 632, [57, null]],
+[60, "box1", 978, 488, [63, null]],
+[61, "if", 894, 522, [62, 63, 57, null]],
+[62, ["vspace", 0], 894, 480, [36, 61]],
+[63, ["greater2", 0], 932, 488, [61, 60, 55, null]],
+[64, "setcolor", 894, 144, [32, 67, 73]],
+[65, ["number", 50.0], 1026, 186, [67, null]],
+[66, "color", 1026, 144, [67, null]],
+[67, ["plus2", 0], 972, 144, [64, 66, 65]],
+[68, "storeinbox1", 894, 228, [73, 69, 71]],
+[69, "box", 1012, 228, [68, 70, null]],
+[70, ["string", "max"], 1066, 228, [69, null]],
+[71, "stack", 894, 270, [68, 72, 16]],
+[72, ["string", "bar"], 953, 270, [71, null]],
+[73, ["vspace", 0], 894, 186, [64, 68]]]
diff --git a/samples/love-speaks-volumes.ta b/samples/love-speaks-volumes.ta
new file mode 100644
index 0000000..30e0cb3
--- /dev/null
+++ b/samples/love-speaks-volumes.ta
@@ -0,0 +1,109 @@
+[[0, ["start", 2.0], 760, 100, [null, 55]],
+[1, "hat1", 780, 260, [null, 106]],
+[2, "hat2", 120, 200, [null, 80]],
+[3, "setcolor", 1164, 992, [51, 4, 16]],
+[4, ["number", 0], 1242, 992, [3, null]],
+[5, "stack1", 1164, 698, [17, 62]],
+[6, "volume", 1233, 572, [23, null]],
+[7, "forever", 1100, 344, [102, 29, 103]],
+[8, "penup", 780, 378, [67, 101]],
+[9, "pendown", 780, 462, [101, 70]],
+[10, "penup", 780, 546, [70, 75]],
+[11, "pendown", 780, 714, [78, 107]],
+[12, "clean", 900, 344, [104, 13]],
+[13, ["fillscreen", 0], 900, 386, [12, 15, 14, 35]],
+[14, ["number", 80], 986, 428, [13, null]],
+[15, "white", 986, 386, [13, null]],
+[16, "stack1", 1164, 1034, [3, 42]],
+[17, "setcolor", 1164, 656, [64, 18, 5]],
+[18, "white", 1242, 656, [17, null]],
+[19, ["storein", 0], 1164, 446, [29, 20, 27, 23]],
+[20, ["string", "b"], 1233, 446, [19, null]],
+[21, ["storein", 0], 900, 554, [35, 22, 41, 38]],
+[22, ["string", "b"], 969, 554, [21, null]],
+[23, ["storein", 0], 1164, 530, [19, 24, 6, 64]],
+[24, ["string", "a"], 1233, 530, [23, null]],
+[25, "box", 1283, 908, [63, 26, null]],
+[26, ["string", "a"], 1338, 908, [25, null]],
+[27, "box", 1233, 488, [19, 28, null]],
+[28, ["string", "a"], 1288, 488, [27, null]],
+[29, ["storein", 0], 1164, 362, [7, 30, 31, 19]],
+[30, ["string", "c"], 1233, 362, [29, null]],
+[31, "box", 1233, 404, [29, 32, null]],
+[32, ["string", "b"], 1288, 404, [31, null]],
+[33, "box", 1283, 614, [64, 34, null]],
+[34, ["string", "c"], 1338, 614, [33, null]],
+[35, ["storein", 0], 900, 470, [13, 36, 37, 21]],
+[36, ["string", "a"], 969, 470, [35, null]],
+[37, ["number", 0], 969, 512, [35, null]],
+[38, ["storein", 0], 900, 638, [21, 39, 40, 105]],
+[39, ["string", "c"], 969, 638, [38, null]],
+[40, ["number", 0], 969, 680, [38, null]],
+[41, ["number", 0], 969, 596, [21, null]],
+[42, "wait", 1164, 1076, [16, 43, null]],
+[43, ["number", 0.1], 1222, 1076, [42, null]],
+[44, "setshade", 1164, 782, [62, 45, 46]],
+[45, ["number", 75], 1250, 782, [44, null]],
+[46, "setcolor", 1164, 824, [44, 47, 50]],
+[47, ["number", 0], 1242, 824, [46, null]],
+[48, "box", 1283, 740, [62, 49, null]],
+[49, ["string", "b"], 1338, 740, [48, null]],
+[50, "stack1", 1164, 866, [46, 63]],
+[51, "setshade", 1164, 950, [63, 52, 3]],
+[52, ["number", 50], 1250, 950, [51, null]],
+[53, "hat", 900, 260, [null, 54, 104]],
+[54, ["string", "setup"], 958, 268, [53, null]],
+[55, "stack", 760, 142, [0, 56, 59]],
+[56, ["string", "setup"], 818, 142, [55, null]],
+[57, "hat", 1100, 260, [null, 58, 102]],
+[58, ["string", "loop"], 1158, 268, [57, null]],
+[59, "stack", 760, 184, [55, 60, null]],
+[60, ["string", "loop"], 818, 184, [59, null]],
+[61, "box1", 953, 336, [71, null]],
+[62, "storeinbox1", 1164, 740, [5, 48, 44]],
+[63, "storeinbox1", 1164, 908, [50, 25, 51]],
+[64, "storeinbox1", 1164, 614, [23, 33, 17]],
+[65, "forward", 120, 326, [80, 82, 99]],
+[66, "box2", 178, 284, [80, null]],
+[67, "storeinbox2", 780, 336, [106, 71, 8]],
+[68, "box2", 246, 326, [82, null]],
+[69, "box2", 906, 420, [73, null]],
+[70, "stack2", 780, 504, [9, 10]],
+[71, ["division2", 0], 899, 336, [67, 61, 72]],
+[72, ["number", 2], 977, 378, [71, null]],
+[73, ["division2", 0], 852, 420, [101, 69, 74]],
+[74, ["number", 2], 930, 462, [73, null]],
+[75, ["setxy2", 0], 780, 588, [10, 76, 77, 78]],
+[76, ["number", 0], 838, 588, [75, null]],
+[77, ["number", 0], 838, 630, [75, null]],
+[78, "seth", 780, 672, [75, 79, 11]],
+[79, ["number", 0], 882, 672, [78, null]],
+[80, ["arc", 0], 120, 242, [2, 81, 66, 65]],
+[81, ["number", 225], 178, 242, [80, null]],
+[82, ["product2", 0], 192, 326, [65, 68, 85]],
+[83, "sqrt", 300, 410, [85, 84]],
+[84, ["number", 2], 354, 410, [83, null]],
+[85, ["product2", 0], 246, 368, [82, 86, 83]],
+[86, ["number", 1.7], 300, 368, [85, null]],
+[87, ["arc", 0], 120, 616, [100, 88, 89, null]],
+[88, ["number", 225], 178, 616, [87, null]],
+[89, "box2", 178, 658, [87, null]],
+[90, "right", 120, 450, [99, 91, 92]],
+[91, ["number", 90], 178, 450, [90, null]],
+[92, "forward", 120, 492, [90, 93, 100]],
+[93, ["product2", 0], 192, 492, [92, 94, 95]],
+[94, "box2", 246, 492, [93, null]],
+[95, ["product2", 0], 246, 534, [93, 96, 97]],
+[96, ["number", 1.7], 300, 534, [95, null]],
+[97, "sqrt", 300, 576, [95, 98]],
+[98, ["number", 2], 354, 576, [97, null]],
+[99, ["vspace", 20], 120, 368, [65, 90]],
+[100, ["vspace", 20], 120, 534, [92, 87]],
+[101, "forward", 780, 420, [8, 73, 9]],
+[102, "sandwichtop_no_arm_no_label", 1082, 310, [57, 7]],
+[103, ["sandwichcollapsed", 1], 1100, 344, [7, null]],
+[104, "sandwichtop_no_arm_no_label", 882, 310, [53, 12]],
+[105, ["sandwichcollapsed", 1], 900, 344, [38, null]],
+[106, "sandwichtop_no_arm_no_label", 762, 302, [1, 67]],
+[107, ["sandwichcollapsed", 1], 780, 336, [11, null]],
+[-1, ["turtle", "Yertle"], 0, 0, 0.0, 0.0, 50.0, 5]] \ No newline at end of file
diff --git a/samples/scratch.ta b/samples/scratch.ta
new file mode 100644
index 0000000..ed80e40
--- /dev/null
+++ b/samples/scratch.ta
@@ -0,0 +1,88 @@
+[[0, ["start", 2.0], 0, 180, [null, 14]],
+[1, "skin", 720, 474, [20, 2, 7]],
+[2, ["journal", "./samples/images/turtle-a.png"], 902, 474, [1, null]],
+[3, "skin", 720, 600, [44, 4, 35]],
+[4, ["journal", "./samples/images/turtle-b.png"], 902, 600, [3, null]],
+[5, "addturtle", 720, 390, [74, 6, 20]],
+[6, ["number", 1], 788, 390, [5, null]],
+[7, "addturtle", 720, 516, [1, 8, 44]],
+[8, ["number", 2], 788, 516, [7, null]],
+[9, "show", 720, 306, [11, 10, 74]],
+[10, ["journal", "./samples/images/Boston.png"], 793, 306, [9, null]],
+[11, "setscale", 720, 264, [45, 12, 9]],
+[12, ["number", 100], 816, 264, [11, null]],
+[13, "hat1", 720, 180, [null, 45]],
+[14, "stack1", 0, 222, [0, 69]],
+[15, "stack2", 101, 534, [46, 53]],
+[16, "hat2", 360, 180, [null, 75]],
+[17, ["setxy2", 20], 461, 282, [21, 19, 42, 28]],
+[18, "xcor", 580, 324, [19, null]],
+[19, ["plus2", 0], 526, 282, [17, 47, 18]],
+[20, "penup", 720, 432, [5, 1]],
+[21, "addturtle", 461, 240, [75, 22, 17]],
+[22, ["number", 1], 529, 240, [21, null]],
+[23, "addturtle", 461, 448, [28, 24, 25]],
+[24, ["number", 2], 529, 448, [23, null]],
+[25, ["setxy2", 20], 461, 490, [23, 26, 43, 30]],
+[26, ["plus2", 0], 526, 490, [25, 48, 27]],
+[27, "xcor", 580, 532, [26, null]],
+[28, "wait", 461, 406, [17, 29, 23]],
+[29, ["number", 0.5], 532, 406, [28, null]],
+[30, "wait", 461, 614, [25, 31, 80]],
+[31, ["number", 0.25], 532, 614, [30, null]],
+[32, ["setxy2", 0], 720, 684, [35, 34, 33, 37]],
+[33, ["number", -100], 785, 726, [32, null]],
+[34, "leftpos", 785, 684, [32, null]],
+[35, "addturtle", 720, 642, [3, 36, 32]],
+[36, ["number", 1], 788, 642, [35, null]],
+[37, "addturtle", 720, 768, [32, 38, 39]],
+[38, ["number", 2], 788, 768, [37, null]],
+[39, ["setxy2", 0], 720, 810, [37, 40, 41, null]],
+[40, "leftpos", 785, 810, [39, null]],
+[41, ["number", -100], 785, 852, [39, null]],
+[42, "ycor", 526, 364, [17, null]],
+[43, "ycor", 526, 572, [25, null]],
+[44, "penup", 720, 558, [7, 3]],
+[45, "clean", 720, 222, [13, 11]],
+[46, "storeinbox1", 101, 492, [66, 71, 15]],
+[47, "box1", 580, 282, [19, null]],
+[48, "box1", 580, 490, [26, null]],
+[49, "seth", 101, 618, [53, 50, 55]],
+[50, ["number", 180], 199, 618, [49, null]],
+[51, "seth", 101, 702, [55, 52, 58]],
+[52, ["number", 180], 199, 702, [51, null]],
+[53, "addturtle", 101, 576, [15, 54, 49]],
+[54, ["number", 1], 169, 576, [53, null]],
+[55, "addturtle", 101, 660, [49, 56, 51]],
+[56, ["number", 2], 169, 660, [55, null]],
+[57, "forever", 0, 306, [69, 60, null]],
+[58, "storeinbox1", 101, 744, [51, 73, 68]],
+[59, ["number", 0], 289, 744, [73, null]],
+[60, "addturtle", 101, 324, [57, 61, 62]],
+[61, ["number", 1], 169, 324, [60, null]],
+[62, "seth", 101, 366, [60, 63, 64]],
+[63, ["number", 0], 199, 366, [62, null]],
+[64, "addturtle", 101, 408, [62, 65, 66]],
+[65, ["number", 2], 169, 408, [64, null]],
+[66, "seth", 101, 450, [64, 67, 46]],
+[67, ["number", 0], 199, 450, [66, null]],
+[68, "stack2", 101, 786, [58, null]],
+[69, "storeinbox2", 0, 264, [14, 70, 57]],
+[70, ["number", 20], 134, 264, [69, null]],
+[71, "box2", 235, 492, [46, null]],
+[72, "box2", 313, 786, [73, null]],
+[73, ["minus2", 0], 235, 744, [58, 59, 72]],
+[74, ["vspace", 0], 720, 348, [9, 5]],
+[75, "forever", 360, 222, [16, 21, null]],
+[76, ["vspace", 0], 461, 784, [77, 79]],
+[77, "if", 461, 698, [80, 82, 78, 76]],
+[78, "stopstack", 537, 766, [77, null]],
+[79, "if", 461, 826, [76, 85, 81, null]],
+[80, ["vspace", 0], 461, 656, [30, 77]],
+[81, "stopstack", 537, 894, [79, null]],
+[82, ["greater2", 0], 523, 664, [77, 83, 84, null]],
+[83, "xcor", 569, 664, [82, null]],
+[84, "rightpos", 593, 706, [82, null]],
+[85, ["less2", 0], 523, 792, [79, 86, 87, null]],
+[86, "xcor", 569, 792, [85, null]],
+[87, "leftpos", 593, 834, [85, null]]]
diff --git a/samples/spectrum_analyzer.ta b/samples/spectrum_analyzer.ta
new file mode 100644
index 0000000..bb850d3
--- /dev/null
+++ b/samples/spectrum_analyzer.ta
@@ -0,0 +1,53 @@
+[[0, ["start", 2.0], 114, 36, [null, 31]],
+[1, ["myfunc1arg", 0], 297, 138, [35, 2, 4, null]],
+[2, ["string", "int(x)"], 378, 138, [1, null]],
+[3, ["number", 100.0], 456, 222, [4, null]],
+[4, ["division2", 0], 378, 180, [1, 5, 3]],
+[5, "pitch", 432, 180, [4, null]],
+[6, "forever", 114, 120, [31, 35, null]],
+[7, "repeat", 644, 157, [10, 8, 16, 9]],
+[8, ["number", 30.0], 692, 157, [7, null]],
+[9, ["vspace", 0], 644, 235, [7, null]],
+[10, "storeinbox1", 644, 115, [30, 11, 7]],
+[11, ["number", 2.0], 763, 115, [10, null]],
+[12, "storeinbox1", 706, 631, [19, 15, null]],
+[13, ["number", 1.0], 879, 673, [15, null]],
+[14, "box1", 879, 631, [15, null]],
+[15, ["plus2", 0], 825, 631, [12, 14, 13]],
+[16, "addturtle", 706, 217, [7, 17, 45]],
+[17, "box1", 764, 217, [16, null]],
+[18, "penup", 706, 343, [44, 20]],
+[19, "pendown", 706, 589, [20, 12]],
+[20, ["setxy2", 60], 706, 385, [18, 27, 22, 19]],
+[21, ["number", 30.0], 950, 427, [26, null]],
+[22, ["number", -200.0], 764, 547, [20, null]],
+[23, "leftpos", 818, 507, [27, null]],
+[24, "width", 926, 385, [26, null]],
+[25, "box1", 980, 467, [42, null]],
+[26, ["division2", 0], 872, 385, [28, 24, 21]],
+[27, ["plus2", 40], 764, 385, [20, 28, 23]],
+[28, ["product2", 20], 818, 385, [27, 26, 52]],
+[29, "hat1", 644, 31, [null, 30]],
+[30, "clean", 644, 73, [29, 10]],
+[31, "stack1", 114, 78, [0, 6]],
+[32, "addturtle", 230, 450, [38, 36, 33]],
+[33, "forward", 230, 492, [32, 34, null]],
+[34, ["number", 1.0], 302, 492, [33, null]],
+[35, "storeinbox2", 178, 138, [6, 1, 39]],
+[36, "box2", 288, 450, [32, null]],
+[37, "box2", 308, 340, [41, null]],
+[38, "if", 178, 382, [39, 48, 32, null]],
+[39, ["vspace", 80], 178, 180, [35, 38]],
+[40, ["number", 1.0], 332, 382, [41, null]],
+[41, ["greater2", 0], 262, 340, [48, 37, 40, null]],
+[42, ["minus2", 0], 926, 467, [52, 25, 43]],
+[43, ["number", 1.0], 1004, 509, [42, null]],
+[44, "setcolor", 706, 301, [45, 47, 18]],
+[45, "setshade", 706, 259, [16, 46, 44]],
+[46, ["number", 25.0], 792, 259, [45, null]],
+[47, "box1", 784, 301, [44, null]],
+[48, ["and2", 0], 216, 300, [38, 49, 41]],
+[49, ["less2", 0], 262, 258, [48, 50, 51, null]],
+[50, "box2", 308, 258, [49, null]],
+[51, ["number", 21.0], 332, 300, [49, null]],
+[52, ["identity2", 0], 872, 467, [28, 42]]]
diff --git a/samples/spiralaterals.ta b/samples/spiralaterals.ta
new file mode 100644
index 0000000..9099145
--- /dev/null
+++ b/samples/spiralaterals.ta
@@ -0,0 +1,55 @@
+[[0, ["start", 2.0], 180, 9, [null, 6]],
+[1, "hat1", 541, 3, [null, 18]],
+[2, "stack1", 306, 673, [28, null]],
+[3, "forward", 761, 49, [9, 11, 12]],
+[4, "right", 761, 133, [12, 5, null]],
+[5, ["number", 90], 836, 133, [4, null]],
+[6, "storeinbox1", 180, 51, [0, 7, 51]],
+[7, ["number", 20], 314, 51, [6, null]],
+[8, "box1", 894, 49, [11, null]],
+[9, "hat2", 761, 7, [null, 3]],
+[10, "pop", 894, 91, [11, null]],
+[11, ["product2", 0], 840, 49, [3, 8, 10]],
+[12, ["vspace", 0], 761, 91, [3, 4]],
+[13, "stack2", 541, 171, [19, 22]],
+[14, "stack2", 541, 87, [18, 19]],
+[15, "stack2", 541, 255, [22, 21]],
+[16, "stack2", 541, 339, [21, 20]],
+[17, "stack2", 541, 423, [20, null]],
+[18, "push", 541, 45, [1, 23, 14]],
+[19, "push", 541, 129, [14, 24, 13]],
+[20, "push", 541, 381, [16, 25, 17]],
+[21, "push", 541, 297, [15, 26, 16]],
+[22, "push", 541, 213, [13, 27, 15]],
+[23, ["number", 1], 617, 45, [18, null]],
+[24, ["number", 1], 617, 129, [19, null]],
+[25, ["number", 2.0], 617, 381, [20, null]],
+[26, ["number", 3.0], 617, 297, [21, null]],
+[27, ["number", 1], 617, 213, [22, null]],
+[28, "repeat", 243, 613, [30, 29, 2, null]],
+[29, ["number", 4], 292, 613, [28, null]],
+[30, ["vspace", 0], 243, 571, [41, 28]],
+[31, "repeat", 180, 177, [51, 32, 46, null]],
+[32, ["number", 400], 229, 177, [31, null]],
+[33, ["vspace", 0], 243, 445, [34, 50]],
+[34, ["setxy2", 20], 243, 321, [45, 35, 36, 33]],
+[35, ["random", 0], 308, 321, [34, 37, 39, null]],
+[36, ["random", 0], 308, 403, [34, 38, 40, null]],
+[37, "leftpos", 368, 321, [35, null]],
+[38, "bottompos", 368, 403, [36, null]],
+[39, "rightpos", 368, 363, [35, null]],
+[40, "toppos", 368, 445, [36, null]],
+[41, "setcolor", 243, 529, [50, 42, 30]],
+[42, ["random", 0], 328, 529, [41, 43, 44, null]],
+[43, ["number", 0], 388, 529, [42, null]],
+[44, ["number", 100], 388, 571, [42, null]],
+[45, "penup", 243, 279, [46, 34]],
+[46, "seth", 243, 237, [31, 47, 45]],
+[47, ["random", 0], 341, 237, [46, 48, 49, null]],
+[48, ["number", 0], 401, 237, [47, null]],
+[49, ["number", 90], 401, 279, [47, null]],
+[50, "pendown", 243, 487, [33, 41]],
+[51, ["fillscreen", 0], 180, 93, [6, 52, 53, 31]],
+[52, "black", 320, 93, [51, null]],
+[53, ["number", 0], 320, 135, [51, null]],
+[-1, ["turtle", "Yertle"], 0, -38, 0, 0, 50, 5], [-1, ["turtle", "Walter Bender"], 241, 346, 177.0, 36, 50, 5]] \ No newline at end of file
diff --git a/samples/timer.ta b/samples/timer.ta
new file mode 100644
index 0000000..cb691ec
--- /dev/null
+++ b/samples/timer.ta
@@ -0,0 +1,82 @@
+[[0, ["fillscreen", 0], 753, 146, [12, 2, 1, 4]],
+[1, ["number", 80], 839, 188, [0, null]],
+[2, "red", 839, 146, [0, null]],
+[3, "white", 831, 230, [4, null]],
+[4, "setcolor", 753, 230, [0, 3, 71]],
+[5, "white", 843, 407, [7, null]],
+[6, "blue", 835, 491, [9, null]],
+[7, ["fillscreen", 0], 757, 407, [11, 5, 8, 9]],
+[8, ["number", 80], 843, 449, [7, null]],
+[9, "setcolor", 757, 491, [7, 6, 74]],
+[10, ["start", 2.0], 194, 74, [null, 80]],
+[11, "hat1", 757, 365, [null, 7]],
+[12, "hat2", 753, 104, [null, 0]],
+[13, "show", 258, 760, [68, 77, 79]],
+[14, "setscale", 194, 158, [80, 15, 44]],
+[15, ["number", 330], 273, 158, [14, null]],
+[16, ["userdefined", "pysamples/push_time.py"], 892, 673, [41, 17, 18]],
+[17, ["number", 100], 950, 673, [16, null]],
+[18, ["storein", 0], 892, 715, [16, 24, 20, 21]],
+[19, ["string", "h"], 961, 883, [23, null]],
+[20, "pop", 961, 757, [18, null]],
+[21, ["storein", 0], 892, 799, [18, 22, 34, 23]],
+[22, ["string", "m"], 961, 799, [21, null]],
+[23, ["storein", 0], 892, 883, [21, 19, 35, 25]],
+[24, ["string", "s"], 961, 715, [18, null]],
+[25, "storeinbox1", 892, 967, [23, 30, null]],
+[26, "box", 1119, 967, [28, 27, null]],
+[27, ["string", "h"], 1174, 967, [26, null]],
+[28, ["product2", 0], 1065, 967, [30, 26, 29]],
+[29, ["number", 3600], 1119, 1009, [28, null]],
+[30, ["plus2", 20], 1011, 967, [25, 28, 32]],
+[31, ["product2", 0], 1119, 1049, [32, 36, 33]],
+[32, ["plus2", 20], 1065, 1049, [30, 31, 38]],
+[33, ["number", 60], 1173, 1091, [31, null]],
+[34, "pop", 961, 841, [21, null]],
+[35, "pop", 961, 925, [23, null]],
+[36, "box", 1173, 1049, [31, 37, null]],
+[37, ["string", "m"], 1228, 1049, [36, null]],
+[38, "box", 1119, 1131, [32, 39, null]],
+[39, ["string", "s"], 1174, 1131, [38, null]],
+[40, "box1", 313, 242, [43, null]],
+[41, "hat", 892, 623, [null, 42, 16]],
+[42, ["string", "time"], 950, 631, [41, null]],
+[43, "storeinbox2", 194, 242, [44, 40, 46]],
+[44, "stack", 194, 200, [14, 45, 43]],
+[45, ["string", "time"], 252, 200, [44, null]],
+[46, "stack1", 194, 284, [43, 47]],
+[47, "forever", 194, 326, [46, 53, null]],
+[48, ["vspace", 40], 258, 470, [58, 66]],
+[49, "wait", 258, 844, [79, 50, null]],
+[50, ["number", 10], 316, 844, [49, null]],
+[51, "box2", 459, 470, [55, null]],
+[52, "box1", 435, 428, [55, null]],
+[53, "stack", 258, 344, [47, 54, 58]],
+[54, ["string", "time"], 316, 344, [53, null]],
+[55, ["minus2", 0], 381, 428, [56, 52, 51]],
+[56, ["division2", 20], 327, 428, [58, 55, 57]],
+[57, ["number", 60], 405, 510, [56, null]],
+[58, ["storein", 0], 258, 386, [53, 59, 56, 48]],
+[59, ["string", "elapsed"], 327, 386, [58, null]],
+[60, ["greater2", 0], 320, 558, [66, 62, 61, null]],
+[61, ["number", 20], 390, 600, [60, null]],
+[62, "box", 366, 558, [60, 63, null]],
+[63, ["string", "elapsed"], 421, 558, [62, null]],
+[64, "stack2", 334, 702, [67, null]],
+[65, "stack1", 386, 660, [66, null]],
+[66, "ifelse", 258, 592, [48, 60, 67, 65, 68]],
+[67, ["vspace", 0], 334, 660, [66, 64]],
+[68, ["vspace", 20], 258, 678, [66, 13]],
+[69, "box", 397, 802, [77, 70, null]],
+[70, ["string", "elapsed"], 452, 802, [69, null]],
+[71, ["setxy2", 0], 753, 272, [4, 72, 73, null]],
+[72, ["number", -100], 811, 272, [71, null]],
+[73, ["number", 200], 811, 314, [71, null]],
+[74, ["setxy2", 0], 757, 533, [9, 75, 76, null]],
+[75, ["number", -100], 815, 533, [74, null]],
+[76, ["number", 200], 815, 575, [74, null]],
+[77, ["myfunc1arg", 0], 316, 760, [13, 78, 69, null]],
+[78, ["string", "int(x)"], 397, 760, [77, null]],
+[79, ["vspace", 0], 258, 802, [13, 49]],
+[80, "hideblocks", 194, 116, [10, 14]],
+[-1, ["turtle", "Yertle"], -100.0, 200.0, 0, 70, 50, 5]] \ No newline at end of file
diff --git a/samples/vumeter.ta b/samples/vumeter.ta
new file mode 100644
index 0000000..22491c7
--- /dev/null
+++ b/samples/vumeter.ta
@@ -0,0 +1,68 @@
+[[0, ["start", 2.0], 34, 198, [null, 18]],
+[1, "forever", 34, 450, [31, 10, 2]],
+[2, ["vspace", 0], 34, 486, [1, null]],
+[3, ["setxy2", 0], 160, 528, [10, 4, 8, null]],
+[4, ["number", 0], 218, 528, [3, null]],
+[5, "volume", 326, 570, [6, null]],
+[6, ["division2", 0], 272, 570, [8, 5, 7]],
+[7, ["number", 10], 350, 612, [6, null]],
+[8, ["minus2", 20], 218, 570, [3, 6, 9]],
+[9, ["number", 200], 296, 652, [8, null]],
+[10, "repeat", 98, 468, [1, 11, 3, 12]],
+[11, ["number", 20], 146, 468, [10, null]],
+[12, ["vspace", 20], 98, 546, [10, 20]],
+[13, ["setxy2", 0], 652, 319, [17, 14, 15, 29]],
+[14, ["number", 0], 710, 319, [13, null]],
+[15, ["number", -200], 710, 361, [13, null]],
+[16, "penup", 652, 109, [19, 21]],
+[17, "pendown", 652, 277, [24, 13]],
+[18, "hideblocks", 34, 240, [0, 26]],
+[19, "hat1", 652, 67, [null, 16]],
+[20, "stack1", 98, 628, [12, null]],
+[21, ["setxy2", 0], 652, 151, [16, 22, 23, 24]],
+[22, ["number", 0], 710, 151, [21, null]],
+[23, "toppos", 710, 193, [21, null]],
+[24, "setcolor", 652, 235, [21, 28, 17]],
+[25, "white", 120, 282, [26, null]],
+[26, ["fillscreen", 0], 34, 282, [18, 25, 27, 37]],
+[27, ["number", 80], 120, 324, [26, null]],
+[28, "white", 730, 235, [24, null]],
+[29, "setcolor", 652, 403, [13, 30, null]],
+[30, ["number", 0], 730, 403, [29, null]],
+[31, "setpensize", 34, 408, [37, 32, 1]],
+[32, ["number", 30], 137, 408, [31, null]],
+[33, "hat2", 883, 65, [null, 62]],
+[34, "repeat", 883, 359, [42, 35, 43, 36]],
+[35, ["number", 6], 931, 359, [34, null]],
+[36, ["vspace", 80], 883, 437, [34, 53]],
+[37, "stack2", 34, 366, [26, 31]],
+[38, ["setxy2", 0], 883, 233, [41, 39, 40, 42]],
+[39, ["number", -100], 941, 233, [38, null]],
+[40, ["number", -200], 941, 275, [38, null]],
+[41, "penup", 883, 191, [60, 38]],
+[42, "pendown", 883, 317, [38, 34]],
+[43, "right", 945, 419, [34, 44, 45]],
+[44, ["number", 90], 1003, 419, [43, null]],
+[45, "forward", 945, 461, [43, 46, 47]],
+[46, ["number", 20], 1017, 461, [45, null]],
+[47, "back", 945, 503, [45, 48, 49]],
+[48, ["number", 20], 1003, 503, [47, null]],
+[49, "left", 945, 545, [47, 50, 54]],
+[50, ["number", 90], 1003, 545, [49, null]],
+[51, "forward", 945, 711, [64, 52, null]],
+[52, ["number", 100], 1017, 711, [51, null]],
+[53, "penup", 883, 639, [36, null]],
+[54, "show", 945, 587, [49, 66, 64]],
+[55, "ycor", 1165, 629, [57, null]],
+[56, ["number", 200], 1165, 671, [57, null]],
+[57, ["plus2", 0], 1111, 629, [58, 55, 56]],
+[58, ["product2", 20], 1057, 629, [66, 57, 59]],
+[59, ["number", 10], 1111, 711, [58, null]],
+[60, "setpensize", 883, 149, [62, 61, 41]],
+[61, ["number", 5], 986, 149, [60, null]],
+[62, "setscale", 883, 107, [33, 63, 60]],
+[63, ["number", 25.0], 962, 107, [62, null]],
+[64, ["vspace", 20], 945, 629, [54, 51]],
+[65, ["string", " "], 1057, 587, [66, null]],
+[66, ["plus2", 0], 1003, 587, [54, 65, 58]],
+[-1, ["turtle", "Yertle"], 0.0, -179.3354, 0.0, 0.0, 50, 30.0]] \ No newline at end of file
diff --git a/turtleart.py b/turtleart.py
index b8ffa08..ca9153a 100755
--- a/turtleart.py
+++ b/turtleart.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python
#Copyright (c) 2007-8, Playful Invention Company
-#Copyright (c) 2008-10, Walter Bender
+#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
@@ -32,7 +33,7 @@ import cStringIO
import errno
try:
- # Try to use XDG Base Directory standard for config files
+ # 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:
@@ -42,33 +43,44 @@ except ImportError, e:
argv = sys.argv[:] # Workaround for import behavior of gst in tagplay
sys.argv[1:] = [] # Execution of import gst cannot see '--help' or '-h'
-from gettext import gettext as _
+import gettext
-from TurtleArt.taconstants import OVERLAY_LAYER
+# 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')
+
+gettext.textdomain('org.laptop.TurtleArtActivity')
+_ = gettext.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 extra.upload import Uploader
-from extra.collaborationplugin import CollaborationPlugin
+
from util.menubuilder import MenuBuilder
+
class TurtleMain():
- """ Launch Turtle Art from outside of Sugar """
+ ''' Launch Turtle Art from outside of Sugar. '''
- _HELP_MSG = 'turtleart.py: ' + _('usage is') + """
+ _HELP_MSG = 'turtleart.py: ' + _('usage is') + '''
\tturtleart.py
\tturtleart.py project.ta
\tturtleart.py --output_png project.ta
- \tturtleart.py -o project"""
- _INSTALL_PATH = '/usr/share/turtleart'
- _ALTERNATE_INSTALL_PATH = '/usr/local/share/turtleart'
+ \tturtleart.py -o project'''
+ _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._init_vars()
self._parse_command_line()
self._ensure_sugar_paths()
+ self._plugins = []
if self.output_png:
pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8,
@@ -85,18 +97,47 @@ class TurtleMain():
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._dirname,
+ self._GNOME_PLUGIN_SUBPATH)):
+ return os.path.join(self._dirname, 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):
- config_file_path = os.path.join(CONFIG_HOME, 'turtleartrc.collab')
- self._collab_plugin = CollaborationPlugin(self, config_file_path)
- self._uploader = Uploader()
+ ''' 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:
+ print 'failed to import %s' % (P)
def _run_plugins(self):
- self._uploader.set_tw(self.tw)
- self._collab_plugin.set_tw(self.tw)
- self._collab_plugin.setup()
+ ''' Tell the plugin about the TurtleWindow instance. '''
+ for p in self._plugins:
+ p.set_tw(self.tw)
- def _mkdir_p(path):
- '''Create a directory in a fashion similar to `mkdir -p`'''
+ def _mkdir_p(self, path):
+ '''Create a directory in a fashion similar to `mkdir -p`.'''
try:
os.makedirs(path)
except OSError as exc:
@@ -106,7 +147,7 @@ class TurtleMain():
raise
def _makepath(self, path):
- """ Make a path if it doesn't previously exist """
+ ''' Make a path if it doesn't previously exist '''
from os import makedirs
from os.path import normpath, dirname, exists
@@ -115,8 +156,9 @@ class TurtleMain():
makedirs(dpath)
def _start_gtk(self):
+ ''' Get a main window set up. '''
self.win.connect('configure_event', self.tw.update_overlay_position)
- self.tw.win = self.win
+ self.tw.parent = self.win
if self.ta_file is None:
self.tw.load_start()
else:
@@ -127,26 +169,24 @@ class TurtleMain():
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, self.canvas)
def _build_window(self):
- if os.path.exists(self._INSTALL_PATH):
- self.tw = TurtleArtWindow(self.canvas, self._INSTALL_PATH)
- elif os.path.exists(self._ALTERNATE_INSTALL_PATH):
- self.tw = TurtleArtWindow(self.canvas, self._ALTERNATE_INSTALL_PATH)
- else:
- self.tw = TurtleArtWindow(self.canvas, os.path.abspath('.'))
-
+ ''' Initialize the TurtleWindow instance. '''
+ self.tw = TurtleArtWindow(self.canvas, self._dirname)
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.
- os.chdir(os.path.dirname(__file__))
-
+ ''' If we are invoked to start a project from Gnome, we should make
+ sure our current directory is TA's source dir. '''
+ self._dirname = self._get_execution_dir()
+ if self._dirname is not None:
+ os.chdir(self._dirname)
self.ta_file = None
self.output_png = False
self.i = 0 # FIXME: use a better name for this variable
@@ -154,6 +194,7 @@ class TurtleMain():
self.tw = None
def _parse_command_line(self):
+ ''' Try to make sense of the command-line arguments. '''
try:
opts, args = getopt.getopt(argv[1:], 'ho',
['help', 'output_png'])
@@ -182,16 +223,15 @@ class TurtleMain():
if not os.path.exists(self.ta_file):
assert False, ('%s: %s' % (self.ta_file, _('File not found')))
- """
- make sure Sugar paths are present
- """
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:
@@ -212,25 +252,27 @@ class TurtleMain():
data_file.write(str(800) + '\n')
data_file.write(str(550) + '\n')
data_file.seek(0)
- self.x = int(data_file.readline())
- self.y = int(data_file.readline())
- self.width = int(data_file.readline())
- self.height = int(data_file.readline())
+ 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._INSTALL_PATH, self._ICON_SUBPATH)):
- win.set_icon_from_file(os.path.join(self._INSTALL_PATH,
+ if os.path.exists(os.path.join(self._dirname, self._ICON_SUBPATH)):
+ win.set_icon_from_file(os.path.join(self._dirname,
self._ICON_SUBPATH))
- else:
- try:
- win.set_icon_from_file(self._ICON_SUBPATH)
- except IOError:
- pass
win.connect('delete_event', self._quit_ta)
vbox = gtk.VBox(False, 0)
@@ -257,27 +299,30 @@ class TurtleMain():
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)
- if self._uploader.enabled():
- MenuBuilder.make_menu_item(menu, _('Upload to Web'),
- self._uploader.do_upload_to_web)
+ 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, _('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, _('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'),
@@ -290,9 +335,12 @@ class TurtleMain():
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)
+ 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()
@@ -303,19 +351,20 @@ class TurtleMain():
MenuBuilder.make_menu_item(menu, _('Stop'), self._do_stop_cb)
turtle_menu = MenuBuilder.make_sub_menu(menu, _('Turtle'))
- collaboration_menu = self._collab_plugin.get_menu()
-
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)
- menu_bar.append(collaboration_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 """
+ ''' Save changes on exit '''
project_empty = self.tw.is_project_empty()
if not project_empty:
if self.tw.is_new_project():
@@ -326,11 +375,11 @@ class TurtleMain():
gtk.main_quit()
def _show_save_dialog(self, new_project=True):
- """ Dialog for save project """
+ ''' 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?'))
+ 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)
@@ -343,38 +392,38 @@ class TurtleMain():
self._save_changes()
def _do_new_cb(self, widget):
- """ Callback for new project. """
+ ''' Callback for new project. '''
self.tw.new_project()
self.tw.load_start()
def _do_open_cb(self, widget):
- """ Callback for open project. """
+ ''' Callback for open project. '''
self.tw.load_file(True)
def _do_save_cb(self, widget):
- """ Callback for save project. """
+ ''' Callback for save project. '''
self.tw.save_file()
def _do_save_as_cb(self, widget):
- """ Callback for save-as project. """
+ ''' Callback for save-as project. '''
self._save_as()
def _save_as(self):
- """ Save as is called from callback and quit """
+ ''' 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 """
+ ''' 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. """
+ ''' Callback for save canvas. '''
self.tw.save_as_image()
def _do_save_html_cb(self, widget):
- """ Callback for save project to HTML. """
+ ''' Callback for save project to HTML. '''
html = save_html(self, self.tw, False)
if len(html) == 0:
return
@@ -390,7 +439,7 @@ class TurtleMain():
self.tw.saved_pictures = []
def _do_save_logo_cb(self, widget):
- """ Callback for save project to Logo. """
+ ''' Callback for save project to Logo. '''
logocode = save_logo(self.tw)
if len(logocode) == 0:
return
@@ -402,7 +451,7 @@ class TurtleMain():
f.close()
def _do_resize_cb(self, widget, factor):
- """ Callback to resize blocks. """
+ ''' Callback to resize blocks. '''
if factor == -1:
self.tw.block_scale = 2.0
else:
@@ -410,7 +459,7 @@ class TurtleMain():
self.tw.resize_blocks()
def _do_cartesian_cb(self, button):
- """ Callback to display/hide Cartesian coordinate overlay. """
+ ''' Callback to display/hide Cartesian coordinate overlay. '''
if self.tw.cartesian is True:
if self.tw.coord_scale == 1:
self.tw.overlay_shapes['Cartesian_labeled'].hide()
@@ -426,7 +475,7 @@ class TurtleMain():
self.tw.cartesian = True
def _do_polar_cb(self, button):
- """ Callback to display/hide Polar coordinate overlay. """
+ ''' Callback to display/hide Polar coordinate overlay. '''
if self.tw.polar is True:
self.tw.overlay_shapes['polar'].hide()
self.tw.polar = False
@@ -435,7 +484,7 @@ class TurtleMain():
self.tw.polar = True
def _do_rescale_cb(self, button):
- """ Callback to rescale coordinate space. """
+ ''' Callback to rescale coordinate space. '''
if self.tw.coord_scale == 1:
self.tw.coord_scale = self.tw.height / 200
self.tw.eraser_button()
@@ -451,51 +500,51 @@ class TurtleMain():
OVERLAY_LAYER)
def _do_palette_cb(self, widget):
- """ Callback to show/hide palette of blocks. """
+ ''' 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. """
+ ''' Hide the palette of blocks. '''
self.tw.hide_palette()
def _do_hideshow_cb(self, widget):
- """ Hide/show the blocks. """
+ ''' Hide/show the blocks. '''
self.tw.hideshow_button()
def _do_eraser_cb(self, widget):
- """ Callback for eraser button. """
+ ''' Callback for eraser button. '''
self.tw.eraser_button()
return
def _do_run_cb(self, widget):
- """ Callback for run button (rabbit). """
+ ''' Callback for run button (rabbit). '''
self.tw.lc.trace = 0
self.tw.run_button(0)
return
def _do_step_cb(self, widget):
- """ Callback for step button (turtle). """
+ ''' Callback for step button (turtle). '''
self.tw.lc.trace = 0
self.tw.run_button(3)
return
def _do_trace_cb(self, widget):
- """ Callback for debug button (bug). """
+ ''' Callback for debug button (bug). '''
self.tw.lc.trace = 1
self.tw.run_button(6)
return
def _do_stop_cb(self, widget):
- """ Callback for stop button. """
+ ''' Callback for stop button. '''
self.tw.lc.trace = 0
self.tw.stop_button()
return
def _do_copy_cb(self, button):
- """ Callback for copy button. """
+ ''' Callback for copy button. '''
clipBoard = gtk.Clipboard()
data = self.tw.assemble_data_to_save(False, False)
if data is not []:
@@ -503,7 +552,7 @@ class TurtleMain():
clipBoard.set_text(text)
def _do_paste_cb(self, button):
- """ Callback for paste button. """
+ ''' Callback for paste button. '''
clipBoard = gtk.Clipboard()
text = clipBoard.wait_for_text()
if text is not None:
@@ -518,7 +567,7 @@ class TurtleMain():
self.tw.paste_offset += 20
def _window_event(self, event, data):
- """ Callback for resize event. """
+ ''' Callback for resize event. '''
data_file = open('.turtleartrc', 'w')
data_file.write(str(data.x) + '\n')
data_file.write(str(data.y) + '\n')
@@ -526,8 +575,40 @@ class TurtleMain():
data_file.write(str(data.height) + '\n')
def destroy(self, event, data=None):
- """ Callback for destroy event. """
+ ''' Callback for destroy event. '''
gtk.main_quit()
-if __name__ == "__main__":
+ 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/TurtleArt/RtfParser.py b/util/RtfParser.py
index 9a141a4..9a141a4 100644
--- a/TurtleArt/RtfParser.py
+++ b/util/RtfParser.py
diff --git a/util/configfile.py b/util/configfile.py
index 5df8f59..adabb34 100644
--- a/util/configfile.py
+++ b/util/configfile.py
@@ -1,10 +1,24 @@
#!/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"""
@@ -15,7 +29,7 @@ class ConfigFile(gobject.GObject):
()),
}
- def __init__(self, config_file_path, valid_keys = {}):
+ def __init__(self, config_file_path, valid_keys={}):
gobject.GObject.__init__(self)
self._config_file_path = config_file_path
@@ -29,24 +43,24 @@ class ConfigFile(gobject.GObject):
def is_loaded(self):
return self._is_loaded
- def get(self, key, empty_if_not_loaded = False):
- if not self._valid_keys.has_key(key):
+ def get(self, key, empty_if_not_loaded=False):
+ if not key in self._valid_keys:
raise RuntimeError("Unknown config value %s" % key)
- if self._config_hash.has_key(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":
+ elif self._valid_keys[key]["type"] == "boolean":
value = False
- elif self._valid_keys[key]["type"] == "integer":
- value = 0
+ elif self._valid_keys[key]["type"] == "integer":
+ value = 0
return value
def set(self, key, value):
- if not self._valid_keys.has_key(key):
+ if not key in self._valid_keys:
raise RuntimeError("Unknown config value %s" % key)
self._config_hash[key] = value
@@ -58,10 +72,10 @@ class ConfigFile(gobject.GObject):
config_file.close()
for line in lines:
line = line.strip()
- k,v = line.split('=')
+ k, v = line.split('=')
k = k.strip(' ')
v = v.strip(' ')
- if not self._valid_keys.has_key(k):
+ 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":
@@ -73,11 +87,11 @@ class ConfigFile(gobject.GObject):
self._config_hash[k] = value
self._is_loaded = True
self.emit('configuration-loaded')
- except Exception,e:
+ 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():
@@ -94,14 +108,15 @@ class ConfigFile(gobject.GObject):
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" }
+ 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)
@@ -113,28 +128,31 @@ def test_save_load(test_config_file):
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" }
+ 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)
@@ -147,12 +165,13 @@ def test_signals(test_config_file):
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
index 7716362..6c66bd1 100644
--- a/util/configwizard.py
+++ b/util/configwizard.py
@@ -1,25 +1,41 @@
#!/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
+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
+ self._config_file_obj = None
"""
- [ { item_label, item_type, item_name, item_with_value } , ... ]
+ [ {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"] }
+ keys[i["item_name"]] = {"type": i["item_type"]}
self._valid_keys = keys
def set_config_file_obj(self, obj):
@@ -28,7 +44,7 @@ class ConfigWizard():
def get_config_file_obj(self, obj):
return self._config_file_obj
- def show(self, read_from_disc = False):
+ def show(self, read_from_disc=False):
if read_from_disc:
self._config_file_obj = ConfigFile(self._config_file_path)
@@ -47,8 +63,8 @@ class ConfigWizard():
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
+ table.attach(hbox, 0, 1, row, row + 1, xpadding=5, ypadding=2)
+ row = row + 1
hbox = gtk.HBox()
save_button = gtk.Button('Save')
@@ -59,7 +75,7 @@ class ConfigWizard():
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)
+ table.attach(hbox, 0, 1, row, row + 1, xpadding=5, ypadding=2)
self._config_popup.show_all()
@@ -89,11 +105,12 @@ class ConfigWizard():
self._config_file_obj.save()
"""
- { item_label, item_type, item_name, item_with_value }
+ {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 opts.has_key("item_with_value") else True
+ 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()
@@ -117,14 +134,15 @@ class ConfigWizard():
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" }
+ 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)
@@ -136,32 +154,36 @@ def test_wizard_from_config_file_obj(test_config_file):
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" }
+ {"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" }
+ 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)
@@ -173,19 +195,23 @@ def test_wizard_from_config_file_path(test_config_file):
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" }
+ {"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")
diff --git a/util/menubuilder.py b/util/menubuilder.py
index 302d510..4ee4550 100644
--- a/util/menubuilder.py
+++ b/util/menubuilder.py
@@ -1,7 +1,23 @@
#!/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):