Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TurtleArt/tabasics.py1053
-rw-r--r--TurtleArt/tablock.py93
-rw-r--r--TurtleArt/tacanvas.py20
-rw-r--r--TurtleArt/taconstants.py219
-rw-r--r--TurtleArt/taexportpython.py282
-rw-r--r--TurtleArt/tajail.py28
-rw-r--r--TurtleArt/talogo.py629
-rw-r--r--TurtleArt/tapalette.py3
-rw-r--r--TurtleArt/taprimitive.py1162
-rw-r--r--TurtleArt/taturtle.py119
-rw-r--r--TurtleArt/tatype.py443
-rw-r--r--TurtleArt/tautils.py41
-rw-r--r--TurtleArt/tawindow.py340
-rw-r--r--TurtleArtActivity.py136
-rw-r--r--plugins/accelerometer/accelerometer.py7
-rw-r--r--plugins/audio_sensors/audio_sensors.py432
-rw-r--r--plugins/camera_sensor/camera_sensor.py48
-rw-r--r--plugins/light_sensor/light_sensor.py22
-rw-r--r--plugins/rfid/rfid.py14
-rw-r--r--plugins/turtle_blocks_extras/turtle_blocks_extras.py1150
-rw-r--r--pyexported/__init__.py0
-rw-r--r--pyexported/window_setup.py138
-rw-r--r--pysamples/brain.py3
-rw-r--r--pysamples/csound.py25
-rw-r--r--pysamples/dotted_line.py77
-rw-r--r--pysamples/forward_push.py31
-rw-r--r--pysamples/grecord.py11
-rw-r--r--pysamples/serial.py4
-rw-r--r--pysamples/sinewave.py3
-rw-r--r--pysamples/speak.py4
-rw-r--r--pysamples/uturn.py23
-rw-r--r--samples/card-18.tb38
-rw-r--r--samples/game-snake.tb60
-rw-r--r--samples/media-music-keyboard.tb7
-rw-r--r--samples/thumbnails/card-18.pngbin0 -> 15893 bytes
-rw-r--r--samples/thumbnails/game-snake.pngbin0 -> 9694 bytes
-rw-r--r--samples/thumbnails/sensors-follow-path.pngbin0 -> 15267 bytes
-rwxr-xr-xturtleblocks.py40
-rw-r--r--util/ast_extensions.py69
-rw-r--r--util/codegen.py597
40 files changed, 5261 insertions, 2110 deletions
diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py
index 3d1eb10..3d88ce4 100644
--- a/TurtleArt/tabasics.py
+++ b/TurtleArt/tabasics.py
@@ -20,72 +20,94 @@
#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!!)
+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
+../plugins/turtle_blocks_extras/turtle_blocks_extras.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.
+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, we define the _prim_uturn function to set heading += 180.
- self.tw.lc.def_prim('uturn', 0, lambda self: self._prim_uturn)
- def _prim_uturn(self):
- value = self.tw.turtles.get_active_turtle().get_heading() + 180
- self.tw.turtles.get_active_turtle().set_heading(value)
+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 a Primitive object. A Primitive object
+represents the statement to be executed when the block is
+executed in Turtle Art. For the 'uturn' block, we would like the
+statement to look roughly like this:
+
+ Turtle.set_heading(plus(Turtle.get_heading(), 180))
+
+Formally, a Primitive object consists of a function, its return
+type, and descriptions of its arguments and keyword arguments.
+The return type is not a Python type, but a type from Turtle
+Art's internal type system. All available types are defined as
+constants in tatype.py .
+
+In this case, we know in advance which arguments each function
+gets, so we can use ConstantArg objects as argument descrip-
+tions. (For examples where the arguments come from other blocks,
+please refer to ../doc/primitives-with-arguments.md .) Note that
+Primitive objects can be arguments to other Primitive objects.
+This leads to the following tree-like structure for our 'uturn'
+block:
+
+ prim_uturn = Primitive(Turtle.set_heading,
+ arg_descs=[ConstantArg(Primitive(
+ Primitive.plus, return_type=TYPE_NUMBER,
+ arg_descs=[ConstantArg(Primitive(
+ Turtle.get_heading, return_type=TYPE_NUMBER)),
+ ConstantArg(180)]))],
+ call_afterwards=self.after_uturn)
+
+ self.tw.lc.def_prim('uturn', 0, prim_uturn)
+
+ # somewhere else in the same class:
+ def after_uturn(self, value):
if self.tw.lc.update_values:
self.tw.lc.update_label_value('heading', value)
-That's it. When you next run Turtle Art, you will have a 'uturn' block
-on the 'mypalette' palette.
+The `call_afterwards` attribute is a simple function that is
+called just after executing the block. It is often used for
+updating GUI labels.
-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.)
-'''
+That's it. When you next run Turtle Art, you will have a 'uturn'
+block on the 'mypalette' palette.
-from time import time, sleep
-from math import sqrt
-from random import uniform
+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
+object. 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 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, debug_output)
-from taconstants import (COLORDICT, CONSTANTS)
-
-
-def _color_to_num(c):
- if COLORDICT[c][0] is None:
- return(COLORDICT[c][1])
- else:
- return(COLORDICT[c][0])
-
-
-def _num_type(x):
- ''' Is x a number type? '''
- if isinstance(x, (int, float)):
- return True
- return False
+from talogo import primitive_dictionary
+from taconstants import (Color, CONSTANTS)
+from taprimitive import (ArgSlot, ConstantArg, or_, Primitive)
+from tatype import (TYPE_BOOL, TYPE_BOX, TYPE_CHAR, TYPE_COLOR, TYPE_FLOAT,
+ TYPE_INT, TYPE_NUMBER, TYPE_NUMERIC_STRING, TYPE_OBJECT,
+ TYPE_STRING)
+from taturtle import Turtle
def _millisecond():
@@ -99,6 +121,15 @@ class Palettes():
def __init__(self, turtle_window):
self.tw = turtle_window
+ self.prim_cache = {
+ "minus": Primitive(Primitive.minus,
+ return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER)]),
+ "ord": Primitive(ord,
+ return_type=TYPE_INT,
+ arg_descs=[ArgSlot(TYPE_CHAR)])
+ } # avoid several Primitives of the same function
+
self._turtle_palette()
self._pen_palette()
@@ -119,13 +150,11 @@ class Palettes():
def _turtle_palette(self):
''' The basic Turtle Art turtle palette '''
- debug_output('creating %s palette' % _('turtle'),
- self.tw.running_sugar)
palette = make_palette('turtle',
colors=["#00FF00", "#00A000"],
- help_string=_('Palette of turtle commands'))
+ help_string=_('Palette of turtle commands'),
+ translation=_('turtle'))
- primitive_dictionary['move'] = self._prim_move
palette.add_block('forward',
style='basic-style-1arg',
label=_('forward'),
@@ -134,10 +163,10 @@ class Palettes():
logo_command='forward',
help_string=_('moves turtle forward'))
self.tw.lc.def_prim(
- 'forward',
- 1,
- lambda self, x: primitive_dictionary['move'](
- self.tw.turtles.get_active_turtle().forward, x))
+ 'forward', 1,
+ Primitive(Turtle.forward,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=self.after_move))
palette.add_block('back',
style='basic-style-1arg',
@@ -146,13 +175,12 @@ class Palettes():
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.turtles.get_active_turtle().forward, x,
- reverse=True))
+ self.tw.lc.def_prim(
+ 'back', 1,
+ Primitive(Turtle.backward,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=self.after_move))
- primitive_dictionary['clean'] = self._prim_clear
palette.add_block('clean',
style='basic-style-extended-vertical',
label=_('clean'),
@@ -160,12 +188,19 @@ class Palettes():
logo_command='clean',
help_string=_('clears the screen and reset the \
turtle'))
- self.tw.lc.def_prim(
- 'clean',
- 0,
- lambda self: primitive_dictionary['clean']())
+ self.tw.lc.def_prim('clean', 0,
+ Primitive(Primitive.group, arg_descs=[
+ ConstantArg([
+ Primitive(self.tw.clear_plugins),
+ Primitive(self.tw.lc.stop_playing_media),
+ Primitive(self.tw.lc.reset_scale),
+ Primitive(self.tw.lc.reset_timer),
+ Primitive(self.tw.lc.clear_value_blocks),
+ Primitive(self.tw.lc.reset_internals),
+ Primitive(self.tw.canvas.clearscreen),
+ Primitive(self.tw.turtles.reset_turtles)
+ ])]))
- primitive_dictionary['right'] = self._prim_right
palette.add_block('left',
style='basic-style-1arg',
label=_('left'),
@@ -175,8 +210,10 @@ turtle'))
help_string=_('turns turtle counterclockwise (angle \
in degrees)'))
self.tw.lc.def_prim(
- 'left', 1, lambda self,
- x: primitive_dictionary['right'](x, reverse=True))
+ 'left', 1,
+ Primitive(Turtle.left,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=self.after_right))
palette.add_block('right',
style='basic-style-1arg',
@@ -187,11 +224,11 @@ in degrees)'))
help_string=_('turns turtle clockwise (angle in \
degrees)'))
self.tw.lc.def_prim(
- 'right',
- 1,
- lambda self, x: primitive_dictionary['right'](x))
+ 'right', 1,
+ Primitive(Turtle.right,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=self.after_right))
- primitive_dictionary['arc'] = self._prim_arc
palette.add_block('arc',
style='basic-style-2arg',
label=[_('arc'), _('angle'), _('radius')],
@@ -200,10 +237,11 @@ degrees)'))
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.turtles.get_active_turtle().arc, x, y))
+ 'arc', 2,
+ Primitive(Turtle.arc,
+ arg_descs=[ArgSlot(TYPE_NUMBER),
+ ArgSlot(TYPE_NUMBER)],
+ call_afterwards=self.after_arc))
define_logo_function('taarc', 'to taarc :a :r\nrepeat round :a \
[right 1 forward (0.0175 * :r)]\nend\n')
@@ -216,13 +254,12 @@ degrees)'))
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.turtles.get_active_turtle().set_xy, x, y))
+ 'setxy2', 2,
+ Primitive(Turtle.set_xy,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)],
+ call_afterwards=self.after_move))
define_logo_function('tasetxy', 'to tasetxy :x :y\nsetxy :x :y\nend\n')
- primitive_dictionary['set'] = self._prim_set
palette.add_block('seth',
style='basic-style-1arg',
label=_('set heading'),
@@ -232,10 +269,11 @@ degrees)'))
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.turtles.get_active_turtle().set_heading, x))
+ 'seth', 1,
+ Primitive(Turtle.set_heading,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=lambda value: self.after_set(
+ 'heading', value)))
palette.add_block('xcor',
style='box-style',
@@ -246,10 +284,11 @@ the turtle (can be used in place of a number block)'),
prim_name='xcor',
logo_command='xcor')
self.tw.lc.def_prim(
- 'xcor',
- 0,
- lambda self: self.tw.turtles.get_active_turtle().get_xy()[0] /
- self.tw.coord_scale)
+ 'xcor', 0,
+ Primitive(Primitive.divide, return_type=TYPE_FLOAT,
+ arg_descs=[ConstantArg(Primitive(Turtle.get_x)),
+ ConstantArg(Primitive(
+ self.tw.get_coord_scale))]))
palette.add_block('ycor',
style='box-style',
@@ -260,10 +299,11 @@ the turtle (can be used in place of a number block)'),
prim_name='ycor',
logo_command='ycor')
self.tw.lc.def_prim(
- 'ycor',
- 0,
- lambda self: self.tw.turtles.get_active_turtle().get_xy()[1] /
- self.tw.coord_scale)
+ 'ycor', 0,
+ Primitive(Primitive.divide, return_type=TYPE_FLOAT,
+ arg_descs=[ConstantArg(Primitive(Turtle.get_y)),
+ ConstantArg(Primitive(
+ self.tw.get_coord_scale))]))
palette.add_block('heading',
style='box-style',
@@ -273,16 +313,14 @@ 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.turtles.get_active_turtle().get_heading())
+ self.tw.lc.def_prim('heading', 0,
+ Primitive(
+ Turtle.get_heading, return_type=TYPE_NUMBER))
- # This block is used for holding the remote turtle name
palette.add_block('turtle-label',
hidden=True,
style='blank-style',
- label=['remote turtle name'])
+ label=['turtle'])
# Deprecated
palette.add_block('setxy',
@@ -295,22 +333,21 @@ turtle (can be used in place of a number block)'),
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.turtles.get_active_turtle().set_xy, x, y,
- pendown=False))
+ 'setxy', 2,
+ Primitive(Turtle.set_xy,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)],
+ kwarg_descs={'pendown': ConstantArg(False)},
+ call_afterwards=self.after_move))
define_logo_function('tasetxypenup', 'to tasetxypenup :x :y\npenup\n\
setxy :x :y\npendown\nend\n')
def _pen_palette(self):
''' The basic Turtle Art pen palette '''
- debug_output('creating %s palette' % _('pen'),
- self.tw.running_sugar)
palette = make_palette('pen',
colors=["#00FFFF", "#00A0A0"],
- help_string=_('Palette of pen commands'))
+ help_string=_('Palette of pen commands'),
+ translation=_('pen'))
palette.add_block('fillscreen',
hidden=True,
@@ -322,9 +359,9 @@ setxy :x :y\npendown\nend\n')
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))
+ 'fillscreen', 2,
+ Primitive(self.tw.canvas.fillscreen,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]))
palette.add_block('fillscreen2',
style='basic-style-3arg',
@@ -336,9 +373,10 @@ shade)'))
help_string=_('fills the background with (color, \
shade)'))
self.tw.lc.def_prim(
- 'fillscreen2',
- 3,
- lambda self, x, y, z: self.tw.canvas.fillscreen_with_gray(x, y, z))
+ 'fillscreen2', 3,
+ Primitive(self.tw.canvas.fillscreen_with_gray,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER),
+ ArgSlot(TYPE_NUMBER)]))
define_logo_function('tasetbackground', 'to tasetbackground :color \
:shade\ntasetshade :shade\nsetbackground :color\nend\n')
@@ -352,10 +390,12 @@ shade)'))
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.turtles.get_active_turtle().set_color, x))
+ 'setcolor', 1,
+ Primitive(Turtle.set_color,
+ arg_descs=[or_(ArgSlot(TYPE_COLOR),
+ ArgSlot(TYPE_NUMBER))],
+ call_afterwards=lambda value: self.after_set(
+ 'color', value)))
palette.add_block('setshade',
style='basic-style-1arg',
@@ -366,10 +406,11 @@ turtle'))
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.turtles.get_active_turtle().set_shade, x))
+ 'setshade', 1,
+ Primitive(Turtle.set_shade,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=lambda value: self.after_set(
+ 'shade', value)))
palette.add_block('setgray',
style='basic-style-1arg',
@@ -379,10 +420,11 @@ turtle'))
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.turtles.get_active_turtle().set_gray, x))
+ 'setgray', 1,
+ Primitive(Turtle.set_gray,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=lambda value: self.after_set(
+ 'gray', value)))
palette.add_block('color',
style='box-style',
@@ -392,10 +434,9 @@ 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.turtles.get_active_turtle().get_color())
+ self.tw.lc.def_prim('color', 0,
+ Primitive(
+ Turtle.get_color, return_type=TYPE_NUMBER))
palette.add_block('shade',
style='box-style',
@@ -404,10 +445,7 @@ in place of a number block)'),
value_block=True,
prim_name='shade',
logo_command=':shade')
- self.tw.lc.def_prim(
- 'shade',
- 0,
- lambda self: self.tw.turtles.get_active_turtle().get_shade())
+ self.tw.lc.def_prim('shade', 0, Primitive(Turtle.get_shade))
palette.add_block('gray',
style='box-style',
@@ -416,8 +454,9 @@ in place of a number block)'),
used in place of a number block)'),
value_block=True,
prim_name='gray')
- self.tw.lc.def_prim('gray', 0, lambda self:
- self.tw.turtles.get_active_turtle().get_gray())
+ self.tw.lc.def_prim('gray', 0,
+ Primitive(
+ Turtle.get_gray, return_type=TYPE_NUMBER))
palette.add_block('penup',
style='basic-style-extended-vertical',
@@ -426,10 +465,8 @@ used in place of a number block)'),
logo_command='penup',
help_string=_('Turtle will not draw when moved.'))
self.tw.lc.def_prim(
- 'penup',
- 0,
- lambda self:
- self.tw.turtles.get_active_turtle().set_pen_state(False))
+ 'penup', 0,
+ Primitive(Turtle.set_pen_state, arg_descs=[ConstantArg(False)]))
palette.add_block('pendown',
style='basic-style-extended-vertical',
@@ -438,10 +475,8 @@ used in place of a number block)'),
logo_command='pendown',
help_string=_('Turtle will draw when moved.'))
self.tw.lc.def_prim(
- 'pendown',
- 0,
- lambda self:
- self.tw.turtles.get_active_turtle().set_pen_state(True))
+ 'pendown', 0,
+ Primitive(Turtle.set_pen_state, arg_descs=[ConstantArg(True)]))
palette.add_block('penstate',
style='boolean-block-style',
@@ -449,9 +484,8 @@ used in place of a number block)'),
prim_name='penstate',
help_string=_('returns True if pen is down'))
self.tw.lc.def_prim(
- 'penstate',
- 0,
- lambda self: self.tw.turtles.get_active_turtle().get_pen_state())
+ 'penstate', 0,
+ Primitive(Turtle.get_pen_state, return_type=TYPE_BOOL))
palette.add_block('setpensize',
style='basic-style-1arg',
@@ -463,8 +497,10 @@ used in place of a number block)'),
turtle'))
self.tw.lc.def_prim(
'setpensize', 1,
- lambda self, x: primitive_dictionary['set']
- ('pensize', self.tw.turtles.get_active_turtle().set_pen_size, x))
+ Primitive(Turtle.set_pen_size,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=lambda val: self.after_set(
+ 'pensize', val)))
define_logo_function('tasetpensize',
'to tasetpensize :a\nsetpensize round :a\nend\n')
@@ -474,10 +510,7 @@ turtle'))
prim_name='startfill',
help_string=_('starts filled polygon (used with end \
fill block)'))
- self.tw.lc.def_prim(
- 'startfill',
- 0,
- lambda self: self.tw.turtles.get_active_turtle().start_fill())
+ self.tw.lc.def_prim('startfill', 0, Primitive(Turtle.start_fill))
palette.add_block('stopfill',
style='basic-style-extended-vertical',
@@ -485,10 +518,7 @@ fill block)'))
prim_name='stopfill',
help_string=_('completes filled polygon (used with \
start fill block)'))
- self.tw.lc.def_prim(
- 'stopfill',
- 0,
- lambda self: self.tw.turtles.get_active_turtle().stop_fill())
+ self.tw.lc.def_prim('stopfill', 0, Primitive(Turtle.stop_fill))
palette.add_block('pensize',
style='box-style',
@@ -499,33 +529,27 @@ in place of a number block)'),
prim_name='pensize',
logo_command='pensize')
self.tw.lc.def_prim(
- 'pensize',
- 0,
- lambda self: self.tw.turtles.get_active_turtle().get_pen_size())
+ 'pensize', 0,
+ Primitive(Turtle.get_pen_size, return_type=TYPE_NUMBER))
define_logo_function('tapensize', 'to tapensize\noutput first round \
pensize\nend\n')
def _color_palette(self):
''' The basic Turtle Art color palette '''
- debug_output('creating %s palette' % _('colors'),
- self.tw.running_sugar)
palette = make_palette('colors',
colors=["#00FFFF", "#00A0A0"],
- help_string=_('Palette of pen colors'))
-
- self._make_constant(palette, 'red', _('red'), CONSTANTS['red'])
- self._make_constant(palette, 'orange', _('orange'),
- CONSTANTS['orange'])
- self._make_constant(palette, 'yellow', _('yellow'),
- CONSTANTS['yellow'])
- self._make_constant(palette, 'green', _('green'), CONSTANTS['green'])
- self._make_constant(palette, 'cyan', _('cyan'), CONSTANTS['cyan'])
- self._make_constant(palette, 'blue', _('blue'), CONSTANTS['blue'])
- self._make_constant(palette, 'purple', _('purple'),
- CONSTANTS['purple'])
- self._make_constant(palette, 'white', _('white'), CONSTANTS['white'])
- self._make_constant(palette, 'black', _('black'), CONSTANTS['black'])
+ help_string=_('Palette of pen colors'),
+ translation=_('colors'))
+
+ color_names = ('red', 'orange', 'yellow', 'green', 'cyan', 'blue',
+ 'purple', 'white', 'black')
+ # Need to make sure color names are included in the PO files
+ color_names_i18n = (_('red'), _('orange'), _('yellow'), _('green'),
+ _('cyan'), _('blue'), _('purple'), _('white'),
+ _('black'))
+ for name in color_names:
+ self._make_constant(palette, name, _(name), name)
# In order to map Turtle Art colors to the standard UCB Logo palette,
# we need to define a somewhat complex set of functions.
@@ -603,13 +627,11 @@ tasetshade :shade \n')
def _numbers_palette(self):
''' The basic Turtle Art numbers palette '''
- debug_output('creating %s palette' % _('numbers'),
- self.tw.running_sugar)
palette = make_palette('numbers',
colors=["#FF00FF", "#A000A0"],
- help_string=_('Palette of numeric operators'))
+ help_string=_('Palette of numeric operators'),
+ translation=_('numbers'))
- primitive_dictionary['plus'] = self._prim_plus
palette.add_block('plus2',
style='number-style',
label='+',
@@ -619,9 +641,16 @@ tasetshade :shade \n')
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))
+ 'plus', 2,
+ # add up two numbers ...
+ or_(Primitive(Primitive.plus, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER),
+ ArgSlot(TYPE_NUMBER)]),
+ # ... or concatenate two strings
+ Primitive(Primitive.plus, return_type=TYPE_STRING,
+ arg_descs=[ArgSlot(TYPE_STRING),
+ ArgSlot(TYPE_STRING)])))
- primitive_dictionary['minus'] = self._prim_minus
palette.add_block('minus2',
style='number-style-porch',
label=' –',
@@ -631,11 +660,12 @@ tasetshade :shade \n')
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))
+ 'minus', 2,
+ Primitive(Primitive.minus, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]))
define_logo_function('taminus', 'to taminus :y :x\noutput sum :x \
minus :y\nend\n')
- primitive_dictionary['product'] = self._prim_product
palette.add_block('product2',
style='number-style',
label='×',
@@ -645,9 +675,9 @@ minus :y\nend\n')
help_string=_('multiplies two numeric inputs'))
self.tw.lc.def_prim(
'product', 2,
- lambda self, x, y: primitive_dictionary['product'](x, y))
+ Primitive(Primitive.multiply, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]))
- primitive_dictionary['division'] = self._prim_careful_divide
palette.add_block('division2',
style='number-style-porch',
label=' /',
@@ -658,9 +688,9 @@ minus :y\nend\n')
(numerator) by bottom numeric input (denominator)'))
self.tw.lc.def_prim(
'division', 2,
- lambda self, x, y: primitive_dictionary['division'](x, y))
+ Primitive(Primitive.divide, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]))
- primitive_dictionary['id'] = self._prim_identity
palette.add_block('identity2',
style='number-style-1arg',
label='←',
@@ -668,10 +698,26 @@ minus :y\nend\n')
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))
+ self.tw.lc.def_prim(
+ 'id', 1,
+ # preserve the Type of the argument: try less general types first
+ or_(Primitive(Primitive.identity, return_type=TYPE_NUMERIC_STRING,
+ arg_descs=[ArgSlot(TYPE_NUMERIC_STRING)]),
+ Primitive(Primitive.identity, return_type=TYPE_CHAR,
+ arg_descs=[ArgSlot(TYPE_CHAR)]),
+ Primitive(Primitive.identity, return_type=TYPE_COLOR,
+ arg_descs=[ArgSlot(TYPE_COLOR)]),
+ Primitive(Primitive.identity, return_type=TYPE_FLOAT,
+ arg_descs=[ArgSlot(TYPE_FLOAT)]),
+ Primitive(Primitive.identity, return_type=TYPE_INT,
+ arg_descs=[ArgSlot(TYPE_INT)]),
+ Primitive(Primitive.identity, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER)]),
+ Primitive(Primitive.identity, return_type=TYPE_STRING,
+ arg_descs=[ArgSlot(TYPE_STRING)]),
+ Primitive(Primitive.identity, return_type=TYPE_OBJECT,
+ arg_descs=[ArgSlot(TYPE_OBJECT)])))
- primitive_dictionary['remainder'] = self._prim_mod
palette.add_block('remainder2',
style='number-style-porch',
label=_('mod'),
@@ -679,11 +725,11 @@ blocks'))
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))
+ self.tw.lc.def_prim(
+ 'remainder', 2,
+ Primitive(Primitive.modulo, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]))
- primitive_dictionary['sqrt'] = self._prim_sqrt
palette.add_block('sqrt',
style='number-style-1arg',
label=_('√'),
@@ -691,10 +737,11 @@ blocks'))
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))
+ self.tw.lc.def_prim(
+ 'sqrt', 1,
+ Primitive(Primitive.square_root, return_type=TYPE_FLOAT,
+ arg_descs=[ArgSlot(TYPE_NUMBER)]))
- primitive_dictionary['random'] = self._prim_random
palette.add_block('random',
style='number-style-block',
label=[_('random'), _('min'), _('max')],
@@ -703,9 +750,19 @@ blocks'))
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))
+ 'random', 2,
+ or_( # random character ...
+ Primitive(Primitive.random_char, return_type=TYPE_CHAR,
+ arg_descs=[
+ ArgSlot(TYPE_INT,
+ wrapper=self.prim_cache["ord"]),
+ ArgSlot(TYPE_INT,
+ wrapper=self.prim_cache["ord"])]),
+ # ... or random number
+ Primitive(Primitive.random_int, return_type=TYPE_INT,
+ arg_descs=[ArgSlot(TYPE_INT), ArgSlot(TYPE_INT)])))
define_logo_function('tarandom', 'to tarandom :min :max\n \
output (random (:max - :min)) + :min\nend\n')
@@ -717,7 +774,6 @@ output (random (:max - :min)) + :min\nend\n')
help_string=_('used as numeric input in mathematic \
operators'))
- primitive_dictionary['more'] = self._prim_more
palette.add_block('greater2',
style='compare-porch-style',
label=' >',
@@ -728,9 +784,16 @@ operators'))
help_string=_('logical greater-than operator'))
self.tw.lc.def_prim(
'greater?', 2,
- lambda self, x, y: primitive_dictionary['more'](x, y))
+ Primitive(Primitive.greater, return_type=TYPE_BOOL,
+ arg_descs=or_([ArgSlot(TYPE_COLOR),
+ ArgSlot(TYPE_COLOR)],
+ [ArgSlot(TYPE_NUMBER),
+ ArgSlot(TYPE_NUMBER)],
+ [ArgSlot(TYPE_STRING),
+ ArgSlot(TYPE_STRING)],
+ [ArgSlot(TYPE_OBJECT),
+ ArgSlot(TYPE_OBJECT)])))
- primitive_dictionary['less'] = self._prim_less
palette.add_block('less2',
style='compare-porch-style',
label=' <',
@@ -740,9 +803,17 @@ operators'))
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))
+ 'less?', 2,
+ Primitive(Primitive.less, return_type=TYPE_BOOL,
+ arg_descs=or_([ArgSlot(TYPE_COLOR),
+ ArgSlot(TYPE_COLOR)],
+ [ArgSlot(TYPE_NUMBER),
+ ArgSlot(TYPE_NUMBER)],
+ [ArgSlot(TYPE_STRING),
+ ArgSlot(TYPE_STRING)],
+ [ArgSlot(TYPE_OBJECT),
+ ArgSlot(TYPE_OBJECT)])))
- primitive_dictionary['equal'] = self._prim_equal
palette.add_block('equal2',
style='compare-style',
label='=',
@@ -751,9 +822,17 @@ operators'))
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))
+ self.tw.lc.def_prim(
+ 'equal?', 2,
+ Primitive(Primitive.equals, return_type=TYPE_BOOL,
+ arg_descs=or_([ArgSlot(TYPE_COLOR),
+ ArgSlot(TYPE_COLOR)],
+ [ArgSlot(TYPE_NUMBER),
+ ArgSlot(TYPE_NUMBER)],
+ [ArgSlot(TYPE_STRING),
+ ArgSlot(TYPE_STRING)],
+ [ArgSlot(TYPE_OBJECT),
+ ArgSlot(TYPE_OBJECT)])))
palette.add_block('not',
style='not-style',
@@ -761,9 +840,11 @@ operators'))
prim_name='not',
logo_command='not',
help_string=_('logical NOT operator'))
- self.tw.lc.def_prim('not', 1, lambda self, x: not x)
+ self.tw.lc.def_prim(
+ 'not', 1,
+ Primitive(Primitive.not_, return_type=TYPE_BOOL,
+ arg_descs=[ArgSlot(TYPE_BOOL)]))
- primitive_dictionary['and'] = self._prim_and
palette.add_block('and2',
style='boolean-style',
label=_('and'),
@@ -772,9 +853,10 @@ operators'))
special_name=_('and'),
help_string=_('logical AND operator'))
self.tw.lc.def_prim(
- 'and', 2, lambda self, x, y: primitive_dictionary['and'](x, y))
+ 'and', 2,
+ Primitive(Primitive.and_, return_type=TYPE_BOOL,
+ arg_descs=[ArgSlot(TYPE_BOOL), ArgSlot(TYPE_BOOL)]))
- primitive_dictionary['or'] = self._prim_or
palette.add_block('or2',
style='boolean-style',
label=_('or'),
@@ -783,18 +865,18 @@ operators'))
special_name=_('or'),
help_string=_('logical OR operator'))
self.tw.lc.def_prim(
- 'or', 2, lambda self, x, y: primitive_dictionary['or'](x, y))
+ 'or', 2,
+ Primitive(Primitive.or_, return_type=TYPE_BOOL,
+ arg_descs=[ArgSlot(TYPE_BOOL), ArgSlot(TYPE_BOOL)]))
def _flow_palette(self):
''' The basic Turtle Art flow palette '''
- debug_output('creating %s palette' % _('flow'),
- self.tw.running_sugar)
palette = make_palette('flow',
colors=["#FFC000", "#A08000"],
- help_string=_('Palette of flow operators'))
+ help_string=_('Palette of flow operators'),
+ translation=_('flow'))
- primitive_dictionary['wait'] = self._prim_wait
palette.add_block('wait',
style='basic-style-1arg',
label=_('wait'),
@@ -803,9 +885,11 @@ operators'))
logo_command='wait',
help_string=_('pauses program execution a specified \
number of seconds'))
- self.tw.lc.def_prim('wait', 1, primitive_dictionary['wait'], True)
+ self.tw.lc.def_prim(
+ 'wait', 1,
+ Primitive(self.tw.lc.prim_wait, arg_descs=[ArgSlot(TYPE_NUMBER)]),
+ True)
- primitive_dictionary['forever'] = self._prim_forever
palette.add_block('forever',
style='clamp-style',
label=_('forever'),
@@ -813,10 +897,13 @@ number of seconds'))
default=[None, None],
logo_command='forever',
help_string=_('loops forever'))
- self.tw.lc.def_prim('forever', 1, primitive_dictionary['forever'],
- True)
+ self.tw.lc.def_prim(
+ 'forever', 1,
+ Primitive(self.tw.lc.prim_loop, arg_descs=[ConstantArg(Primitive(
+ Primitive.controller_forever)), ArgSlot(TYPE_OBJECT,
+ call_arg=False)]),
+ True)
- primitive_dictionary['repeat'] = self._prim_repeat
palette.add_block('repeat',
style='clamp-style-1arg',
label=_('repeat'),
@@ -825,9 +912,16 @@ number of seconds'))
logo_command='repeat',
special_name=_('repeat'),
help_string=_('loops specified number of times'))
- self.tw.lc.def_prim('repeat', 2, primitive_dictionary['repeat'], True)
+ self.tw.lc.def_prim(
+ 'repeat', 2,
+ Primitive(self.tw.lc.prim_loop,
+ arg_descs=[ArgSlot(
+ TYPE_OBJECT,
+ wrapper=Primitive(Primitive.controller_repeat,
+ arg_descs=[ArgSlot(TYPE_INT)])),
+ ArgSlot(TYPE_OBJECT, call_arg=False)]),
+ True)
- primitive_dictionary['if'] = self._prim_if
palette.add_block('if',
style='clamp-style-boolean',
label=[_('if'), _('then'), ''],
@@ -837,9 +931,12 @@ number of seconds'))
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)
+ self.tw.lc.def_prim(
+ 'if', 2,
+ Primitive(self.tw.lc.prim_if,
+ arg_descs=[ArgSlot(TYPE_BOOL), ArgSlot(TYPE_OBJECT)]),
+ True)
- primitive_dictionary['ifelse'] = self._prim_ifelse
palette.add_block('ifelse',
hidden=True, # Too big to fit palette
style='clamp-style-else',
@@ -850,7 +947,12 @@ operators from Numbers palette'))
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)
+ self.tw.lc.def_prim(
+ 'ifelse', 3,
+ Primitive(self.tw.lc.prim_ifelse,
+ arg_descs=[ArgSlot(TYPE_BOOL), ArgSlot(TYPE_OBJECT),
+ ArgSlot(TYPE_OBJECT)]),
+ True)
# macro
palette.add_block('ifthenelse',
@@ -867,7 +969,9 @@ boolean operators from Numbers palette'))
prim_name='nop',
special_name=_('horizontal space'),
help_string=_('jogs stack right'))
- self.tw.lc.def_prim('nop', 0, lambda self: None)
+ self.tw.lc.def_prim(
+ 'nop', 0,
+ Primitive(Primitive.do_nothing, export_me=False))
palette.add_block('vspace',
style='basic-style-extended-vertical',
@@ -875,28 +979,28 @@ boolean operators from Numbers palette'))
prim_name='nop',
special_name=_('vertical space'),
help_string=_('jogs stack down'))
- self.tw.lc.def_prim('nop', 0, lambda self: None)
+ self.tw.lc.def_prim(
+ 'nop', 0,
+ Primitive(Primitive.do_nothing, export_me=False))
- 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']())
+ self.tw.lc.def_prim(
+ 'stopstack', 0,
+ Primitive(self.tw.lc.prim_stop_stack))
def _blocks_palette(self):
''' The basic Turtle Art blocks palette '''
- debug_output('creating %s palette' % _('blocks'),
- self.tw.running_sugar)
palette = make_palette('blocks',
colors=["#FFFF00", "#A0A000"],
- help_string=_('Palette of variable blocks'))
+ help_string=_('Palette of variable blocks'),
+ translation=_('blocks'))
- primitive_dictionary['start'] = self._prim_start
palette.add_block('start',
style='basic-style-head',
label=_('start'),
@@ -904,8 +1008,13 @@ boolean operators from Numbers palette'))
logo_command='to start\n',
help_string=_('connects action to toolbar run \
buttons'))
- self.tw.lc.def_prim('start', 0,
- lambda self: primitive_dictionary['start']())
+ self.tw.lc.def_prim(
+ 'start', 0,
+ Primitive(Primitive.group, arg_descs=[ConstantArg([
+ Primitive(self.tw.lc.prim_start,
+ export_me=False),
+ Primitive(self.tw.lc.prim_define_stack,
+ arg_descs=[ConstantArg('start')])])]))
palette.add_block('string',
style='box-style',
@@ -922,9 +1031,14 @@ buttons'))
default=_('action'),
logo_command='to action',
help_string=_('top of nameable action stack'))
- self.tw.lc.def_prim('nop3', 1, lambda self, x: None)
+ self.tw.lc.def_prim(
+ 'nop3', 1,
+ Primitive(self.tw.lc.prim_define_stack,
+ arg_descs=[ArgSlot(TYPE_OBJECT)]))
- primitive_dictionary['stack'] = self._prim_stack
+ primitive_dictionary['stack'] = Primitive(
+ self.tw.lc.prim_invoke_stack,
+ arg_descs=[ArgSlot(TYPE_OBJECT)])
palette.add_block('stack',
style='basic-style-1arg',
label=_('action'),
@@ -933,9 +1047,9 @@ buttons'))
logo_command='action',
default=_('action'),
help_string=_('invokes named action stack'))
- self.tw.lc.def_prim('stack', 1, primitive_dictionary['stack'], True)
+ self.tw.lc.def_prim('stack', 1,
+ primitive_dictionary['stack'], True)
- primitive_dictionary['setbox'] = self._prim_setbox
palette.add_block('storeinbox1',
hidden=True,
style='basic-style-1arg',
@@ -945,10 +1059,10 @@ buttons'))
string_or_number=True,
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))
+ self.tw.lc.def_prim(
+ 'storeinbox1', 1,
+ Primitive(self.tw.lc.prim_set_box,
+ arg_descs=[ConstantArg('box1'), ArgSlot(TYPE_OBJECT)]))
palette.add_block('storeinbox2',
hidden=True,
@@ -959,10 +1073,10 @@ buttons'))
string_or_number=True,
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))
+ self.tw.lc.def_prim(
+ 'storeinbox2', 1,
+ Primitive(self.tw.lc.prim_set_box,
+ arg_descs=[ConstantArg('box2'), ArgSlot(TYPE_OBJECT)]))
palette.add_block('box1',
hidden=True,
@@ -972,7 +1086,10 @@ buttons'))
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'])
+ self.tw.lc.def_prim(
+ 'box1', 0,
+ Primitive(self.tw.lc.prim_get_box, return_type=TYPE_BOX,
+ arg_descs=[ConstantArg('box1')]))
palette.add_block('box2',
hidden=True,
@@ -982,8 +1099,14 @@ buttons'))
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'])
+ self.tw.lc.def_prim(
+ 'box2', 0,
+ Primitive(self.tw.lc.prim_get_box, return_type=TYPE_BOX,
+ arg_descs=[ConstantArg('box2')]))
+ primitive_dictionary['setbox'] = Primitive(
+ self.tw.lc.prim_set_box,
+ arg_descs=[ArgSlot(TYPE_OBJECT), ArgSlot(TYPE_OBJECT)])
palette.add_block('storein',
style='basic-style-2arg',
label=[_('store in'), _('box'), _('value')],
@@ -993,12 +1116,12 @@ buttons'))
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))
+ self.tw.lc.def_prim('storeinbox', 2, primitive_dictionary['setbox'])
- primitive_dictionary['box'] = self._prim_box
+ primitive_dictionary['box'] = Primitive(
+ self.tw.lc.prim_get_box,
+ return_type=TYPE_BOX,
+ arg_descs=[ArgSlot(TYPE_OBJECT)])
palette.add_block('box',
style='number-style-1strarg',
hidden=True,
@@ -1009,8 +1132,7 @@ variable'))
logo_command='box',
value_block=True,
help_string=_('named variable (numeric value)'))
- self.tw.lc.def_prim('box', 1,
- lambda self, x: primitive_dictionary['box'](x))
+ self.tw.lc.def_prim('box', 1, primitive_dictionary['box'])
palette.add_block('hat1',
hidden=True,
@@ -1019,7 +1141,10 @@ variable'))
prim_name='nop1',
logo_command='to stack1\n',
help_string=_('top of Action 1 stack'))
- self.tw.lc.def_prim('nop1', 0, lambda self: None)
+ self.tw.lc.def_prim(
+ 'nop1', 0,
+ Primitive(self.tw.lc.prim_define_stack,
+ arg_descs=[ConstantArg('stack1')]))
palette.add_block('hat2',
hidden=True,
@@ -1028,9 +1153,11 @@ variable'))
prim_name='nop2',
logo_command='to stack2\n',
help_string=_('top of Action 2 stack'))
- self.tw.lc.def_prim('nop2', 0, lambda self: None)
+ self.tw.lc.def_prim(
+ 'nop2', 0,
+ Primitive(self.tw.lc.prim_define_stack,
+ arg_descs=[ConstantArg('stack2')]))
- primitive_dictionary['stack1'] = self._prim_stack1
palette.add_block('stack1',
hidden=True,
style='basic-style-extended-vertical',
@@ -1038,9 +1165,12 @@ variable'))
prim_name='stack1',
logo_command='stack1',
help_string=_('invokes Action 1 stack'))
- self.tw.lc.def_prim('stack1', 0, primitive_dictionary['stack1'], True)
+ self.tw.lc.def_prim(
+ 'stack1', 0,
+ Primitive(self.tw.lc.prim_invoke_stack,
+ arg_descs=[ConstantArg('stack1')]),
+ True)
- primitive_dictionary['stack2'] = self._prim_stack2
palette.add_block('stack2',
hidden=True,
style='basic-style-extended-vertical',
@@ -1048,16 +1178,19 @@ variable'))
prim_name='stack2',
logo_command='stack2',
help_string=_('invokes Action 2 stack'))
- self.tw.lc.def_prim('stack2', 0, primitive_dictionary['stack2'], True)
+ self.tw.lc.def_prim(
+ 'stack2', 0,
+ Primitive(self.tw.lc.prim_invoke_stack,
+ arg_descs=[ConstantArg('stack2')]),
+ True)
def _trash_palette(self):
''' The basic Turtle Art turtle palette '''
- debug_output('creating %s palette' % _('trash'),
- self.tw.running_sugar)
palette = make_palette('trash',
colors=["#FFFF00", "#A0A000"],
- help_string=_('trash'))
+ help_string=_('trash'),
+ translation=_('trash'))
palette.add_block('empty',
style='blank-style',
@@ -1074,19 +1207,9 @@ variable'))
label=_('clear all'),
help_string=_('move all blocks to trash'))
- # Block primitives
+ # Callbacks to update labels after executing a block
- def _prim_clear(self):
- self.tw.lc.prim_clear()
- self.tw.turtles.reset_turtles()
-
- 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))
+ def after_arc(self, *ignored_args):
if self.tw.lc.update_values:
self.tw.lc.update_label_value(
'xcor',
@@ -1100,65 +1223,8 @@ variable'))
'heading',
self.tw.turtles.get_active_turtle().get_heading())
- def _prim_box(self, x):
- ''' Retrieve value from named box '''
- if isinstance(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,
- reverse=False):
- ''' Turtle moves by method specified in value1 '''
- pos = None
- if isinstance(value1, (tuple, list)):
- pos = value1
- value1 = pos[0]
- value2 = pos[1]
- if not _num_type(value1):
- raise logoerror("#notanumber")
- if value2 is None:
- if reverse:
- cmd(float(-value1))
- else:
- cmd(float(value1))
- else:
- if not _num_type(value2):
- raise logoerror("#notanumber")
- if pos is not None:
- cmd((float(value1), float(value2)), pendown=pendown)
- else:
- cmd(float(value1), float(value2), pendown=pendown)
+ def after_move(self, *ignored_args, **ignored_kwargs):
+ ''' Update labels after moving the turtle '''
if self.tw.lc.update_values:
self.tw.lc.update_label_value(
'xcor',
@@ -1169,323 +1235,38 @@ variable'))
self.tw.turtles.get_active_turtle().get_xy()[1] /
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. '''
- if not _num_type(num):
- raise logoerror("#notanumber")
- 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, reverse=False):
- ''' Turtle rotates clockwise '''
- if not _num_type(value):
- raise logoerror("#notanumber")
- if reverse:
- self.tw.turtles.get_active_turtle().right(float(-value))
- else:
- self.tw.turtles.get_active_turtle().right(float(value))
+ def after_right(self, *ignored_args):
if self.tw.lc.update_values:
self.tw.lc.update_label_value(
'heading',
self.tw.turtles.get_active_turtle().get_heading())
- def _prim_set(self, name, cmd, value=None):
- ''' Set a value and update the associated value blocks '''
+ def after_set(self, name, value=None):
+ ''' Update the associated value blocks '''
if value is not None:
- cmd(value)
if self.tw.lc.update_values:
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 isinstance(convert(x, float, False), float):
- if int(float(x)) == x:
- x = int(x)
- self.tw.lc.boxes[name + str(x)] = val
- if self.tw.lc.update_values:
- self.tw.lc.update_label_value('box', val, label=x)
- else:
- self.tw.lc.boxes[name] = val
- if self.tw.lc.update_values:
- self.tw.lc.update_label_value(name, val)
-
- def _prim_stack(self, x):
- ''' Process a named stack '''
- if isinstance(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.turtles.get_active_turtle().show()
- endtime = _millisecond() + wait_time * 1000.
- while _millisecond() < endtime:
- sleep(wait_time / 10.)
- yield True
- self.tw.turtles.get_active_turtle().hide()
- self.tw.lc.ireturn()
- yield True
-
- # Math primitivies
-
- def _prim_careful_divide(self, x, y):
- ''' Raise error on divide by zero '''
- if isinstance(x, list) and _num_type(y):
- z = []
- for i in range(len(x)):
- try:
- z.append(x[i] / y)
- except ZeroDivisionError:
- raise logoerror("#zerodivide")
- return z
- try:
- return x / y
- except ZeroDivisionError:
- raise logoerror("#zerodivide")
- except TypeError:
- try:
- return self._string_to_num(x) / self._string_to_num(y)
- except ZeroDivisionError:
- raise logoerror("#zerodivide")
- except ValueError:
- raise logoerror("#syntaxerror")
- except TypeError:
- raise logoerror("#notanumber")
-
- def _prim_equal(self, x, y):
- ''' Numeric and logical equal '''
- if isinstance(x, list) and isinstance(y, list):
- for i in range(len(x)):
- if x[i] != y[i]:
- return False
- return True
- try:
- return float(x) == float(y)
- except ValueError:
- typex, typey = False, False
- if strtype(x):
- typex = True
- if strtype(y):
- typey = True
- if typex and typey:
- return x == y
- try:
- return self._string_to_num(x) == self._string_to_num(y)
- except TypeError:
- raise logoerror("#syntaxerror")
-
- def _prim_less(self, x, y):
- ''' Compare numbers and strings '''
- if isinstance(x, list) or isinstance(y, list):
- raise logoerror("#syntaxerror")
- 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 x in COLORDICT:
- x = _color_to_num(x)
- if y in COLORDICT:
- y = _color_to_num(y)
- if _num_type(x) and _num_type(y):
- return(x + y)
- elif isinstance(x, list) and isinstance(y, list):
- z = []
- for i in range(len(x)):
- z.append(x[i] + y[i])
- return(z)
- else:
- if _num_type(x):
- xx = str(round_int(x))
- else:
- xx = str(x)
- if _num_type(y):
- yy = str(round_int(y))
- else:
- yy = str(y)
- return(xx + yy)
-
- def _prim_minus(self, x, y):
- ''' Numerical subtraction '''
- if _num_type(x) and _num_type(y):
- return(x - y)
- elif isinstance(x, list) and isinstance(y, list):
- z = []
- for i in range(len(x)):
- z.append(x[i] - y[i])
- return(z)
- try:
- return self._string_to_num(x) - self._string_to_num(y)
- except TypeError:
- raise logoerror("#notanumber")
-
- def _prim_product(self, x, y):
- ''' Numerical multiplication '''
- if _num_type(x) and _num_type(y):
- return(x * y)
- elif isinstance(x, list) and _num_type(y):
- z = []
- for i in range(len(x)):
- z.append(x[i] * y)
- return(z)
- elif isinstance(y, list) and _num_type(x):
- z = []
- for i in range(len(y)):
- z.append(y[i] * x)
- return(z)
- try:
- return self._string_to_num(x) * self._string_to_num(y)
- except TypeError:
- raise logoerror("#notanumber")
-
- def _prim_mod(self, x, y):
- ''' Numerical mod '''
- if _num_type(x) and _num_type(y):
- return(x % y)
- try:
- return self._string_to_num(x) % self._string_to_num(y)
- except TypeError:
- raise logoerror("#notanumber")
- except ValueError:
- raise logoerror("#syntaxerror")
-
- def _prim_sqrt(self, x):
- ''' Square root '''
- if _num_type(x):
- if x < 0:
- raise logoerror("#negroot")
- return sqrt(x)
- try:
- return sqrt(self._string_to_num(x))
- except ValueError:
- raise logoerror("#negroot")
- except TypeError:
- raise logoerror("#notanumber")
-
- def _prim_random(self, x, y):
- ''' Random integer '''
- if _num_type(x) and _num_type(y):
- return(int(round(uniform(x, y), 0)))
- xx, xflag = chr_to_ord(x)
- yy, yflag = chr_to_ord(y)
- if xflag and yflag:
- return chr(int(round(uniform(xx, yy), 0)))
- if not xflag:
- xx = self._string_to_num(x)
- if not yflag:
- yy = self._string_to_num(y)
- try:
- return(int(round(uniform(xx, yy), 0)))
- except TypeError:
- raise logoerror("#notanumber")
-
- def _prim_identity(self, x):
- ''' Identity function '''
- return(x)
-
# Utilities
- def _string_to_num(self, x):
- ''' Try to comvert a string to a number '''
- if isinstance(x, (int, float)):
- return(x)
- try:
- return int(ord(x))
- except TypeError:
- pass
- if isinstance(x, list):
- raise logoerror("#syntaxerror")
- if x in COLORDICT:
- return _color_to_num(x)
- xx = convert(x.replace(self.tw.decimal_point, '.'), float)
- if isinstance(xx, float):
- return xx
- else:
- xx, xflag = chr_to_ord(x)
- if xflag:
- return xx
- else:
- raise logoerror("#syntaxerror")
-
- def _make_constant(self, palette, block_name, label, constant):
+ def _make_constant(self, palette, block_name, label, constant_key):
''' Factory for constant blocks '''
- if constant in COLORDICT:
- if COLORDICT[constant][0] is not None:
- value = str(COLORDICT[constant][0])
+ constant = CONSTANTS[constant_key]
+ if isinstance(constant, Color):
+ if constant.color is not None:
+ logo_command = str(constant.color)
else:
# Black or White
- value = '0 tasetshade %d' % (COLORDICT[constant][1])
+ logo_command = '0 tasetshade %d' % (constant.shade)
+ return_type = TYPE_COLOR
else:
- value = constant
+ logo_command = constant
+ return_type = TYPE_NUMBER
palette.add_block(block_name,
style='box-style',
label=label,
prim_name=block_name,
- logo_command=value)
- self.tw.lc.def_prim(block_name, 0, lambda self: constant)
+ logo_command=logo_command)
+ self.tw.lc.def_prim(block_name, 0,
+ Primitive(CONSTANTS.get, return_type=return_type,
+ arg_descs=[ConstantArg(constant_key)]))
diff --git a/TurtleArt/tablock.py b/TurtleArt/tablock.py
index ff392e0..cd5515e 100644
--- a/TurtleArt/tablock.py
+++ b/TurtleArt/tablock.py
@@ -24,7 +24,8 @@ import cairo
from taconstants import (EXPANDABLE, EXPANDABLE_ARGS, OLD_NAMES, CONSTANTS,
STANDARD_STROKE_WIDTH, BLOCK_SCALE, BOX_COLORS,
- GRADIENT_COLOR, EXPANDABLE_FLOW, COLORDICT)
+ GRADIENT_COLOR, EXPANDABLE_FLOW, Color,
+ MEDIA_BLOCK2TYPE, BLOCKS_WITH_SKIN)
from tapalette import (palette_blocks, block_colors, expandable_blocks,
content_blocks, block_names, block_primitives,
block_styles, special_block_colors)
@@ -34,6 +35,36 @@ import sprites
from tautils import (debug_output, error_output)
+media_blocks_dictionary = {} # new media blocks get added here
+
+class Media(object):
+ """ Media objects can be images, audio files, videos, Journal
+ descriptions, or camera snapshots. """
+
+ ALL_TYPES = ('media', 'audio', 'video', 'descr', 'camera', 'camera1')
+
+ def __init__(self, type_, value=None):
+ """
+ type_ --- a string that indicates the kind of media:
+ media --- image
+ audio --- audio file
+ video --- video
+ descr --- Journal description
+ camera, camera1 --- camera snapshot
+ value --- a file path or a reference to a Sugar datastore object """
+ if type_ not in Media.ALL_TYPES:
+ raise ValueError("Media.type must be one of " +
+ repr(Media.ALL_TYPES))
+ self.type = type_
+ self.value = value
+
+ def __str__(self):
+ return '%s_%s' % (self.type, str(self.value))
+
+ def __repr__(self):
+ return 'Media(type=%s, value=%s)' % (repr(self.type), repr(self.value))
+
+
class Blocks:
""" A class for the list of blocks and everything they share in common """
@@ -241,6 +272,13 @@ class Block:
self.block_list.append_to_list(self)
+ def __repr__(self):
+ if self.is_value_block():
+ name = self.get_value()
+ else:
+ name = self.name
+ return 'Block(%s)' % (repr(name))
+
def get_visibility(self):
''' Should block be visible on the palette? '''
return self._visible
@@ -277,6 +315,44 @@ class Block:
return False
return True
+ def is_value_block(self):
+ """ Return True iff this block is a value block (numeric, string,
+ media, etc.) """
+ return self.primitive is None and self.values
+
+ def get_value(self, add_type_prefix=True):
+ """ Return the value stored in this value block or None if this is
+ not a value block
+ add_type_prefix -- prepend a prefix to indicate the type of the
+ 'raw' value """
+ if not self.is_value_block():
+ return None
+
+ if self.name == 'number':
+ try:
+ return float(self.values[0])
+ except ValueError:
+ return float(ord(self.values[0][0]))
+ elif (self.name == 'string' or
+ self.name == 'title'): # deprecated block
+ if add_type_prefix:
+ result = '#s'
+ else:
+ result = ''
+ if isinstance(self.values[0], (float, int)):
+ if int(self.values[0]) == self.values[0]:
+ self.values[0] = int(self.values[0])
+ result += str(self.values[0])
+ else:
+ result += self.values[0]
+ return result
+ elif self.name in MEDIA_BLOCK2TYPE:
+ return Media(MEDIA_BLOCK2TYPE[self.name], self.values[0])
+ elif self.name in media_blocks_dictionary:
+ return Media('media', self.name.upper())
+ else:
+ return None
+
def highlight(self):
""" We may want to highlight a block... """
if self.spr is not None and self.status is not 'collapsed':
@@ -529,13 +605,12 @@ class Block:
else:
self._set_labels(i, str(v))
elif self.type == 'block' and self.name in CONSTANTS:
- if CONSTANTS[self.name] in COLORDICT:
- v = COLORDICT[CONSTANTS[self.name]][0]
- if v is None:
- v = COLORDICT[CONSTANTS[self.name]][1]
+ if isinstance(CONSTANTS[self.name], Color):
+ v = int(CONSTANTS[self.name])
else:
v = CONSTANTS[self.name]
- self._set_labels(0, block_names[self.name][0] + ' = ' + str(v))
+ if self.name not in BLOCKS_WITH_SKIN:
+ self._set_labels(0, block_names[self.name][0] + ' = ' + str(v))
elif self.name in block_names:
for i, n in enumerate(block_names[self.name]):
@@ -561,11 +636,11 @@ class Block:
if n == 0:
n = 1 # Force a scale to be set, even if there is no value.
else:
+ n = 0
if self.name in block_names:
n = len(block_names[self.name])
- else:
+ elif self.name not in BLOCKS_WITH_SKIN:
debug_output('WARNING: unknown block name %s' % (self.name))
- n = 0
for i in range(n):
if i > 0:
size = int(self.font_size[1] + 0.5)
@@ -973,7 +1048,7 @@ class Block:
self._make_block_graphics(svg, self.svg.basic_block)
self.docks = [['flow', True, self.svg.docks[0][0],
self.svg.docks[0][1]],
- ['unavailable', True, 0, self.svg.docks[0][1] + 10, '['],
+ ['flow', True, 0, self.svg.docks[0][1] + 10, '['],
['flow', False, self.svg.docks[1][0],
self.svg.docks[1][1], ']']]
diff --git a/TurtleArt/tacanvas.py b/TurtleArt/tacanvas.py
index 89b8ed1..4bac442 100644
--- a/TurtleArt/tacanvas.py
+++ b/TurtleArt/tacanvas.py
@@ -1,4 +1,4 @@
-31#Copyright (c) 2007-8, Playful Invention Company.
+#Copyright (c) 2007-8, Playful Invention Company.
#Copyright (c) 2008-11, Walter Bender
#Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/>
@@ -28,7 +28,7 @@ import cairo
import pangocairo
from tautils import get_path
-from taconstants import COLORDICT, TMP_SVG_PATH
+from taconstants import Color, TMP_SVG_PATH
def wrap100(n):
@@ -208,19 +208,19 @@ class TurtleGraphics:
save_rgb = self._fgrgb[:]
# Special case for color blocks
- if color in COLORDICT:
- if COLORDICT[color][0] is None:
- self._shade = COLORDICT[color][1]
+ if isinstance(color, Color):
+ if color.color is None:
+ self._shade = color.shade
else:
- self._color = COLORDICT[color][0]
+ self._color = color.color
else:
self._color = color
- if shade in COLORDICT:
- self._shade = COLORDICT[shade][1]
+ if isinstance(shade, Color):
+ self._shade = shade.shade
else:
self._shade = shade
- if gray in COLORDICT:
- self._gray = COLORDICT[gray][2]
+ if isinstance(gray, Color):
+ self._gray = gray.gray
else:
self._gray = gray
diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py
index 835209e..4cedb34 100644
--- a/TurtleArt/taconstants.py
+++ b/TurtleArt/taconstants.py
@@ -79,20 +79,173 @@ XO4 = 'xo4'
UNKNOWN = 'unknown'
TMP_SVG_PATH = '/tmp/turtle_output.svg'
+ARG_MUST_BE_NUMBER = ['product2', 'minus2', 'random', 'remainder2', 'forward',
+ 'back', 'left', 'right', 'arc', 'setxy2', 'setxy',
+ 'fillscreen', 'setscale', 'setpensize', 'wait',
+ 'setcolor', 'seth', 'setgray', 'setshade', 'string',
+ 'fillscreen2']
+
+KEY_DICT = {
+ 'Left': 1,
+ 'KP_Left': 1,
+ 'Up': 2,
+ 'KP_Up': 2,
+ 'Right': 3,
+ 'KP_Right': 3,
+ 'Down': 4,
+ 'KP_Down': 4,
+ 'BackSpace': 8,
+ 'Tab': 9,
+ 'Return': 13,
+ 'Escape': 27,
+ 'space': 32,
+ ' ': 32,
+ 'exclam': 33,
+ 'quotedbl': 34,
+ 'numbersign': 35,
+ 'dollar': 36,
+ 'percent': 37,
+ 'ampersand': 38,
+ 'apostrophe': 39,
+ 'parenleft': 40,
+ 'parenright': 41,
+ 'asterisk': 42,
+ 'plus': 43,
+ 'comma': 44,
+ 'minus': 45,
+ 'period': 46,
+ 'slash': 47,
+ 'colon': 58,
+ 'semicolon': 59,
+ 'less': 60,
+ 'equal': 61,
+ 'greater': 62,
+ 'question': 63,
+ 'at': 64,
+ 'underscore': 95,
+ 'bracketleft': 91,
+ 'backslash': 92,
+ 'bracketright': 93,
+ 'asciicircum': 94,
+ 'grave': 96,
+ 'braceleft': 123,
+ 'bar': 124,
+ 'braceright': 125,
+ 'asciitilde': 126,
+ 'Delete': 127,
+ }
+REVERSE_KEY_DICT = {
+ 1: _('left'),
+ 2: _('up'),
+ 3: _('right'),
+ 4: _('down'),
+ 8: _('backspace'),
+ 9: _('tab'),
+ # TRANS: enter is the name of the enter (or return) key
+ 13: _('enter'),
+ 27: 'esc',
+ # TRANS: space is the name of the space key
+ 32: _('space'),
+ 127: _('delete')
+ }
+
+
+class Color(object):
+ """ A color used in block programs (e.g., as pen color). """
+
+ def __init__(self, name, color=0, shade=50, gray=100):
+ """ name -- a string with the name of the color, e.g., 'red'
+ color -- the hue (0-100, or None for white, gray, and black)
+ shade -- the lightness (0 is black, 100 is white)
+ gray -- the saturation (0 is gray, 100 is fully saturated) """
+ self.name = name
+ self.color = color
+ self.shade = shade
+ self.gray = gray
+
+ def __int__(self):
+ if self.color is None:
+ return int(self.shade)
+ else:
+ return int(self.color)
+
+ def __float__(self):
+ return float(int(self))
+
+ def get_number_string(self):
+ return str(int(self))
+
+ def __str__(self):
+ return str(self.name)
+
+ def __repr__(self):
+ return '%s (%s/%d/%d)' % (str(self.name), str(self.color),
+ self.shade, self.gray)
+
+ def __eq__(self, other):
+ """ A Color is equivalent to
+ * another Color with the same color, shade, and gray values
+ * an integer, float, or long that equals int(self) """
+ if isinstance(other, Color):
+ return (self.color == other.color and self.shade == other.shade
+ and self.gray == other.gray)
+ elif isinstance(other, (int, float, long)):
+ return int(self) == other
+ ## * a basestring that equals str(self)
+ #elif isinstance(other, basestring):
+ # return str(self) == other
+ else:
+ return False
+
+ def __lt__(self, other):
+ """ A Color is less than
+ * another Color whose name appears earlier in the alphabet
+ * a number that is less than int(self)
+ * a string that appears before the underscore in the ASCII table """
+ if isinstance(other, Color):
+ return str(self) < str(other)
+ elif isinstance(other, (int, float, long)):
+ return int(self) < other
+ elif isinstance(other, basestring):
+ return '_' + str(self) < other
+ else:
+ return False
+
+ def __gt__(self, other):
+ """ A Color is greater than
+ * another Color whose name appears later in the alphabet
+ * a number that is greater than int(self)
+ * a string that appears after the underscore in the ASCII table """
+ if isinstance(other, Color):
+ return str(self) > str(other)
+ elif isinstance(other, (int, float, long)):
+ return int(self) > other
+ elif isinstance(other, basestring):
+ return '_' + str(self) > other
+ else:
+ return False
+
+ def is_gray(self):
+ """ Return True iff this color is white, gray, or black, i.e. if its
+ hue is not set or its saturation is zero. """
+ return self.color is None or not self.gray
+
+
+
CONSTANTS = {'leftpos': None, 'toppos': None, 'rightpos': None,
'bottompos': None, 'width': None, 'height': None,
- 'black': '_black', 'white': '_white', 'red': '_red',
- 'orange': '_orange', 'yellow': '_yellow', 'green': '_green',
- 'cyan': '_cyan', 'blue': '_blue', 'purple': '_purple',
+ 'black': Color('black', None, 0, 0),
+ 'white': Color('white', None, 100, 0),
+ 'red': Color('red', 0, 50, 100),
+ 'orange': Color('orange', 10, 50, 100),
+ 'yellow': Color('yellow', 20, 50, 100),
+ 'green': Color('green', 40, 50, 100),
+ 'cyan': Color('cyan', 50, 50, 100),
+ 'blue': Color('blue', 70, 50, 100),
+ 'purple': Color('purple', 90, 50, 100),
'titlex': None, 'titley': None, 'leftx': None,
'topy': None, 'rightx': None, 'bottomy': None}
-COLORDICT = {'_black': [None, 0, 0], '_white': [None, 100, 0],
- '_red': [0, 50, 100], '_orange': [10, 50, 100],
- '_yellow': [20, 50, 100], '_green': [40, 50, 100],
- '_cyan': [50, 50, 100], '_blue': [70, 50, 100],
- '_purple': [90, 50, 100]}
-
# Blocks that are expandable
EXPANDABLE_STYLE = ['boolean-style', 'compare-porch-style', 'compare-style',
'number-style-porch', 'number-style', 'basic-style-2arg',
@@ -113,10 +266,10 @@ OLD_DOCK = ['and', 'or', 'plus', 'minus', 'division', 'product', 'remainder']
CONTENT_ARGS = ['show', 'showaligned', 'push', 'storein', 'storeinbox1',
'storeinbox2']
-PREFIX_DICTIONARY = {}
+MEDIA_BLOCK2TYPE = {} # map media blocks to media types
-# These blocks get a special skin
-BLOCKS_WITH_SKIN = []
+
+BLOCKS_WITH_SKIN = [] # These blocks get a special skin
PYTHON_SKIN = []
@@ -197,7 +350,7 @@ MACROS = {
[5, ['number', '0.1'], 0, 0, [4, None]],
[6, 'kbinput', 0, 0, [4, None]]],
'picturelist':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 252], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -214,12 +367,11 @@ MACROS = {
[14, 'pendown', 0, 0, [11, 15]],
[15, 'setscale', 0, 0, [14, 16, 17]],
[16, ['number', '67'], 0, 0, [15, None]],
- [17, 'list', 0, 0, [15, 18, 19, 20]],
+ [17, 'list', 0, 0, [15, 18, 19, None]],
[18, ['string', '∙ '], 0, 0, [17, None]],
- [19, ['string', '∙ '], 0, 0, [17, None]],
- [20, 'sandwichbottom', 0, 0, [17, None]]],
+ [19, ['string', '∙ '], 0, 0, [17, None]]],
'picture1x1a':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 231], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -236,11 +388,10 @@ MACROS = {
[14, 'pendown', 0, 0, [11, 15]],
[15, 'setscale', 0, 0, [14, 16, 17]],
[16, ['number', '90'], 0, 0, [15, None]],
- [17, 'showaligned', 0, 0, [15, 18, 19]],
- [18, 'journal', 0, 0, [17, None]],
- [19, 'sandwichbottom', 0, 0, [17, None]]],
+ [17, 'showaligned', 0, 0, [15, 18, None]],
+ [18, 'journal', 0, 0, [17, None]]],
'picture2x2':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 546], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -278,11 +429,10 @@ MACROS = {
[35, 'rightx', 0, 0, [34, None]],
[36, 'bottomy', 0, 0, [34, None]],
[37, 'pendown', 0, 0, [34, 38]],
- [38, 'showaligned', 0, 0, [37, 39, 40]],
- [39, 'journal', 0, 0, [38, None]],
- [40, 'sandwichbottom', 0, 0, [38, None]]],
+ [38, 'showaligned', 0, 0, [37, 39, None]],
+ [39, 'journal', 0, 0, [38, None]]],
'picture1x2':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 546], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -320,11 +470,10 @@ MACROS = {
[35, 'rightx', 0, 0, [34, None]],
[36, 'bottomy', 0, 0, [34, None]],
[37, 'pendown', 0, 0, [34, 38]],
- [38, 'showaligned', 0, 0, [37, 39, 40]],
- [39, 'description', 0, 0, [38, None]],
- [40, 'sandwichbottom', 0, 0, [38, None]]],
+ [38, 'showaligned', 0, 0, [37, 39, None]],
+ [39, 'description', 0, 0, [38, None]]],
'picture2x1':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 546], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -362,11 +511,10 @@ MACROS = {
[35, 'rightx', 0, 0, [34, None]],
[36, 'bottomy', 0, 0, [34, None]],
[37, 'pendown', 0, 0, [34, 38]],
- [38, 'showaligned', 0, 0, [37, 39, 40]],
- [39, 'description', 0, 0, [38, None]],
- [40, 'sandwichbottom', 0, 0, [38, None]]],
+ [38, 'showaligned', 0, 0, [37, 39, None]],
+ [39, 'description', 0, 0, [38, None]]],
'picture1x1':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 336], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -390,9 +538,8 @@ MACROS = {
[21, 'rightx', 0, 0, [20, None]],
[22, 'topy', 0, 0, [20, None]],
[23, 'pendown', 0, 0, [20, 24]],
- [24, 'showaligned', 0, 0, [23, 25, 26]],
- [25, 'description', 0, 0, [24, None]],
- [26, 'sandwichbottom', 0, 0, [24, None]]],
+ [24, 'showaligned', 0, 0, [23, 25, None]],
+ [25, 'description', 0, 0, [24, None]]],
'reskin':
[[0, 'skin', 0, 0, [None, 1, None]],
[1, 'journal', 0, 0, [0, None]]]}
diff --git a/TurtleArt/taexportpython.py b/TurtleArt/taexportpython.py
new file mode 100644
index 0000000..435bd06
--- /dev/null
+++ b/TurtleArt/taexportpython.py
@@ -0,0 +1,282 @@
+#Copyright (c) 2013 Marion Zepf
+
+#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.
+
+""" Python export tool """
+
+import ast
+from gettext import gettext as _
+from os import linesep
+import re
+import traceback
+import util.codegen as codegen
+
+#from ast_pprint import * # only used for debugging, safe to comment out
+
+from talogo import LogoCode
+from taprimitive import (ast_yield_true, Primitive, PyExportError,
+ value_to_ast)
+from tautils import (find_group, find_top_block, get_stack_name)
+from tawindow import plugins_in_use
+
+
+_SETUP_CODE_START = """\
+#!/usr/bin/env python
+
+_INSTALL_PATH = '/usr/share/sugar/activities/TurtleArt.activity'
+_ALTERNATIVE_INSTALL_PATH = \
+ '/usr/local/share/sugar/activities/TurtleArt.activity'
+
+import os, sys
+if os.path.exists('../TurtleBlocks.activity'):
+ sys.path.insert(0, '../TurtleBlocks.activity')
+elif os.path.exists(os.path.join(os.path.expanduser('~'), 'Activities',
+ 'TurtleBlocks.activity')):
+ sys.path.insert(0, os.path.join(os.path.expanduser('~'), 'Activities',
+ 'TurtleBlocks.activity'))
+elif os.path.exists(_INSTALL_PATH):
+ sys.path.insert(0, _INSTALL_PATH)
+elif os.path.exists(_ALTERNATIVE_INSTALL_PATH):
+ sys.path.insert(0, _ALTERNATIVE_INSTALL_PATH)
+else:
+ print 'This code require the TurtleBlocks activity to be installed.'
+ exit(1)
+
+from time import *
+from random import uniform
+from math import *
+
+from pyexported.window_setup import *
+
+
+tw = get_tw()
+
+BOX = {}
+ACTION = {}
+
+
+"""
+_SETUP_CODE_END = """\
+
+if __name__ == '__main__':
+ tw.lc.start_time = time()
+ tw.lc.icall(start)
+ gobject.idle_add(tw.lc.doevalstep)
+ gtk.main()
+"""
+_ACTION_STACK_START = """\
+def %s():
+"""
+_START_STACK_START_ADD = """\
+ tw.start_plugins()
+ global_objects = tw.get_global_objects()
+"""
+_ACTION_STACK_PREAMBLE = """\
+ turtles = tw.turtles
+ turtle = turtles.get_active_turtle()
+ canvas = tw.canvas
+ logo = tw.lc
+
+"""
+_ACTION_STACK_END = """\
+ACTION["%s"] = %s
+"""
+# character that is illegal in a Python identifier
+PAT_IDENTIFIER_ILLEGAL_CHAR = re.compile("[^A-Za-z0-9_]")
+
+
+def save_python(tw):
+ """ Find all the action stacks and turn each into Python code """
+ all_blocks = tw.just_blocks()
+ blocks_covered = set()
+ tops_of_stacks = []
+ for block in all_blocks:
+ if block not in blocks_covered:
+ top = find_top_block(block)
+ tops_of_stacks.append(top)
+ block_stack = find_group(top)
+ blocks_covered.update(set(block_stack))
+
+ snippets = [_SETUP_CODE_START]
+ for block in tops_of_stacks:
+ stack_name = get_stack_name(block)
+ if stack_name:
+ pythoncode = _action_stack_to_python(block, tw, name=stack_name)
+ snippets.append(pythoncode)
+ snippets.append(linesep)
+ snippets.append(_SETUP_CODE_END)
+ return "".join(snippets)
+
+
+def _action_stack_to_python(block, tw, name="start"):
+ """ Turn a stack of blocks into Python code
+ name -- the name of the action stack (defaults to "start") """
+
+ if isinstance(name, int):
+ name = float(name)
+ if not isinstance(name, basestring):
+ name = str(name)
+
+ # traverse the block stack and get the AST for every block
+ ast_list = _walk_action_stack(block, tw.lc)
+ if not ast_list or not isinstance(ast_list[-1], ast.Yield):
+ ast_list.append(ast_yield_true())
+ action_stack_ast = ast.Module(body=ast_list)
+
+ # serialize the ASTs into python code
+ generated_code = codegen.to_source(action_stack_ast)
+
+ # wrap the action stack setup code around everything
+ name_id = _make_identifier(name)
+ if name == 'start':
+ pre_preamble = _START_STACK_START_ADD
+ for k in plugins_in_use:
+ pre_preamble += " %s = global_objects['%s']\n" % (k.lower(), k)
+ else:
+ pre_preamble = ''
+ generated_code = _indent(generated_code, 1)
+ if generated_code.endswith(linesep):
+ newline = ""
+ else:
+ newline = linesep
+ snippets = [_ACTION_STACK_START % (name_id),
+ pre_preamble,
+ _ACTION_STACK_PREAMBLE,
+ generated_code,
+ newline,
+ _ACTION_STACK_END % (name, name_id)]
+ return "".join(snippets)
+
+
+def _walk_action_stack(top_block, lc, convert_me=True):
+ """ Turn a stack of blocks into a list of ASTs
+ convert_me -- convert values and Primitives to ASTs or return them
+ unconverted? """
+ block = top_block
+
+ # value blocks don't have a primitive
+ # (but constant blocks (colors, screen dimensions, etc.) do)
+ if block.is_value_block():
+ raw_value = block.get_value(add_type_prefix=False)
+ if convert_me:
+ value_ast = value_to_ast(raw_value)
+ if value_ast is not None:
+ return [value_ast]
+ else:
+ return []
+ else:
+ if raw_value is not None:
+ return [raw_value]
+ else:
+ return []
+
+ def _get_prim(block):
+ prim = lc.get_prim_callable(block.primitive)
+ # fail gracefully if primitive is not a Primitive object
+ if not isinstance(prim, Primitive):
+ raise PyExportError(_("block is not exportable"), block=block)
+ return prim
+
+ prim = _get_prim(block)
+
+ ast_list = []
+ arg_asts = []
+
+ def _finish_off(block, prim=None):
+ """ Convert block to an AST and add it to the ast_list. Raise a
+ PyExportError on failure. """
+ if prim is None:
+ prim = _get_prim(block)
+ if convert_me:
+ if prim.export_me:
+ try:
+ new_ast = prim.get_ast(*arg_asts)
+ except ValueError:
+ traceback.print_exc()
+ raise PyExportError(_("error while exporting block"),
+ block=block)
+ if isinstance(new_ast, (list, tuple)):
+ ast_list.extend(new_ast)
+ elif new_ast is not None:
+ ast_list.append(new_ast)
+ elif arg_asts: # TODO do we ever get here?
+ new_ast = ast.List(elts=arg_asts, ctx=ast.Load)
+ ast_list.append(new_ast)
+ else:
+ ast_list.append((prim, ) + tuple(arg_asts))
+
+ # skip the very first dock/ connection - it's either the previous block or
+ # the return value of this block
+ dock_queue = block.docks[1:]
+ conn_queue = block.connections[1:]
+ while dock_queue and conn_queue:
+ dock = dock_queue.pop(0)
+ conn = conn_queue.pop(0)
+ if conn is None or dock[0] == 'unavailable':
+ continue
+ elif not dock_queue and dock[0] == 'flow':
+ # finish off this block
+ _finish_off(block, prim)
+ arg_asts = []
+ # next block
+ block = conn
+ prim = _get_prim(block)
+ dock_queue = block.docks[1:]
+ conn_queue = block.connections[1:]
+ else:
+ # embedded stack of blocks (body of conditional or loop) or
+ # argument block
+ if dock[0] == 'flow':
+ # body of conditional or loop
+ new_arg_asts = _walk_action_stack(conn, lc,
+ convert_me=convert_me)
+ if (prim == LogoCode.prim_loop and
+ not isinstance(new_arg_asts[-1], ast.Yield)):
+ new_arg_asts.append(ast_yield_true())
+ arg_asts.append(new_arg_asts)
+ else:
+ # argument block
+ new_arg_asts = _walk_action_stack(conn, lc, convert_me=False)
+ arg_asts.append(*new_arg_asts)
+
+ # finish off last block
+ _finish_off(block, prim)
+
+ return ast_list
+
+
+def _make_identifier(name):
+ """ Turn name into a Python identifier name by replacing illegal
+ characters """
+ replaced = re.sub(PAT_IDENTIFIER_ILLEGAL_CHAR, "_", name)
+ # TODO find better strategy to avoid number at beginning
+ if re.match("[0-9]", replaced):
+ replaced = "_" + replaced
+ return replaced
+
+
+def _indent(code, num_levels=1):
+ """ Indent each line of code with num_levels * 4 spaces
+ code -- some python code as a (multi-line) string """
+ indentation = " " * (4 * num_levels)
+ line_list = code.split(linesep)
+ new_line_list = []
+ for line in line_list:
+ new_line_list.append(indentation + line)
+ return linesep.join(new_line_list)
diff --git a/TurtleArt/tajail.py b/TurtleArt/tajail.py
index 40517cd..1a89f1d 100644
--- a/TurtleArt/tajail.py
+++ b/TurtleArt/tajail.py
@@ -27,24 +27,14 @@ from math import *
def myfunc(f, args):
''' Run inline Python code '''
# check to make sure no import calls are made
- if len(args) == 1:
- myf = 'def f(x): return ' + f.replace('import', '')
- userdefined = {}
- exec myf in globals(), userdefined
- return userdefined.values()[0](args[0])
- elif len(args) == 2:
- myf = 'def f(x, y): return ' + f.replace('import', '')
- userdefined = {}
- exec myf in globals(), userdefined
- return userdefined.values()[0](args[0], args[1])
- elif len(args) == 3:
- myf = 'def f(x, y, z): return ' + f.replace('import', '')
- userdefined = {}
- exec myf in globals(), userdefined
- return userdefined.values()[0](args[0], args[1], args[2])
-
-
-def myfunc_import(parent, f, x):
+ params = ", ".join(['x', 'y', 'z'][:len(args)])
+ myf = ''.join(['def f(', params, '): return ', f.replace('import', '')])
+ userdefined = {}
+ exec myf in globals(), userdefined
+ return userdefined.values()[0](*args)
+
+
+def myfunc_import(parent, f, args):
''' Run Python code imported from Journal '''
if 'def myblock(lc,' in f:
base_class = parent.tw.lc # pre-v107, we passed lc
@@ -53,7 +43,7 @@ def myfunc_import(parent, f, x):
userdefined = {}
try:
exec f in globals(), userdefined
- return userdefined['myblock'](base_class, x)
+ return userdefined['myblock'](base_class, args)
except:
traceback.print_exc()
return None
diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py
index 0b178c6..775af0f 100644
--- a/TurtleArt/talogo.py
+++ b/TurtleArt/talogo.py
@@ -22,9 +22,12 @@
#THE SOFTWARE.
import gtk
+import gobject
from time import time, sleep
from operator import isNumberType
+import os
+from os.path import exists as os_path_exists
from UserDict import UserDict
try:
@@ -33,10 +36,17 @@ try:
except ImportError:
GRID_CELL_SIZE = 55
-from taconstants import (TAB_LAYER, DEFAULT_SCALE, PREFIX_DICTIONARY)
+import traceback
+
+from tablock import (Block, Media, media_blocks_dictionary)
+from taconstants import (TAB_LAYER, DEFAULT_SCALE, ICON_SIZE)
+from tajail import (myfunc, myfunc_import)
from tapalette import (block_names, value_blocks)
-from tautils import (get_pixbuf_from_journal, convert, data_from_file,
- text_media_type, round_int, debug_output, find_group)
+from tatype import (TATypeError, TYPES_NUMERIC)
+from tautils import (get_pixbuf_from_journal, data_from_file, get_stack_name,
+ text_media_type, round_int, debug_output, find_group,
+ get_path, image_to_base64, data_to_string, data_to_file,
+ get_load_name, chooser_dialog)
try:
from util.RtfParser import RtfTextOnly
@@ -46,7 +56,6 @@ except ImportError:
from gettext import gettext as _
-media_blocks_dictionary = {} # new media blocks get added here
primitive_dictionary = {} # new block primitives get added here
@@ -79,7 +88,21 @@ class logoerror(Exception):
return str(self.value)
-class HiddenBlock:
+class NegativeRootError(BaseException):
+ """ Similar to the ZeroDivisionError, this error is raised at runtime
+ when trying to computer the square root of a negative number. """
+
+ DEFAULT_MESSAGE = 'square root of negative number'
+
+ def __init__(self, neg_value=None, message=DEFAULT_MESSAGE):
+ self.neg_value = neg_value
+ self.message = message
+
+ def __str__(self):
+ return str(self.message)
+
+
+class HiddenBlock(Block):
def __init__(self, name, value=None):
self.name = name
@@ -92,6 +115,7 @@ class HiddenBlock:
self.connections = []
self.docks = []
+
# Utility functions
@@ -142,7 +166,6 @@ class LogoCode:
self.hidden_turtle = None
- self.keyboard = 0
self.trace = 0
self.update_values = False
self.gplay = None
@@ -187,6 +210,14 @@ class LogoCode:
self.oblist[string] = sym
return sym
+ def get_prim_callable(self, name):
+ """ Return the callable primitive associated with the given name """
+ sym = self.oblist.get(name)
+ if sym is not None:
+ return sym.fcn
+ else:
+ return None
+
def run_blocks(self, code):
"""Run code generated by generate_code().
"""
@@ -234,27 +265,17 @@ class LogoCode:
blk = action_blk
for b in blocks:
- if b.name == 'hat1':
- code = self._blocks_to_code(b)
- self.stacks['stack1'] = self._readline(code)
- elif b.name == 'hat2':
- code = self._blocks_to_code(b)
- self.stacks['stack2'] = self._readline(code)
- elif b.name == 'hat':
- if b.connections is not None and len(b.connections) > 1 and \
- b.connections[1] is not None:
+ if b.name in ('hat', 'hat1', 'hat2'):
+ stack_name = get_stack_name(b)
+ if stack_name:
+ stack_key = self._get_stack_key(stack_name)
code = self._blocks_to_code(b)
- try:
- x = b.connections[1].values[0]
- except IndexError:
- self.tw.showlabel('#nostack')
- self.tw.showblocks()
- self.tw.running_blocks = False
- return None
- if isinstance(convert(x, float, False), float):
- if int(float(x)) == x:
- x = int(x)
- self.stacks['stack3' + str(x)] = self._readline(code)
+ self.stacks[stack_key] = self._readline(code)
+ else:
+ self.tw.showlabel('#nostack')
+ self.tw.showblocks()
+ self.tw.running_blocks = False
+ return None
code = self._blocks_to_code(blk)
@@ -278,7 +299,8 @@ class LogoCode:
return ['%nothing%', '%nothing%']
code = []
dock = blk.docks[0]
- if len(dock) > 4: # There could be a '(', ')', '[' or ']'.
+ # There could be a '(', ')', '[' or ']'.
+ if len(dock) > 4 and dock[4] in ('[', ']', ']['):
code.append(dock[4])
if blk.primitive is not None: # make a tuple (prim, blk)
if blk in self.tw.block_list.list:
@@ -286,37 +308,20 @@ class LogoCode:
self.tw.block_list.list.index(blk)))
else:
code.append(blk.primitive) # Hidden block
- elif len(blk.values) > 0: # Extract the value from content blocks.
- if blk.name == 'number':
- try:
- code.append(float(blk.values[0]))
- except ValueError:
- code.append(float(ord(blk.values[0][0])))
- elif blk.name == 'string' or \
- blk.name == 'title': # deprecated block
- if isinstance(blk.values[0], (float, int)):
- if int(blk.values[0]) == blk.values[0]:
- blk.values[0] = int(blk.values[0])
- code.append('#s' + str(blk.values[0]))
- else:
- code.append('#s' + blk.values[0])
- elif blk.name in PREFIX_DICTIONARY:
- if blk.values[0] is not None:
- code.append(PREFIX_DICTIONARY[blk.name] +
- str(blk.values[0]))
- else:
- code.append(PREFIX_DICTIONARY[blk.name] + 'None')
- elif blk.name in media_blocks_dictionary:
- code.append('#smedia_' + blk.name.upper())
- else:
+ elif blk.is_value_block(): # Extract the value from content blocks.
+ value = blk.get_value()
+ if value is None:
return ['%nothing%']
+ else:
+ code.append(value)
else:
return ['%nothing%']
if blk.connections is not None and len(blk.connections) > 0:
for i in range(1, len(blk.connections)):
b = blk.connections[i]
dock = blk.docks[i]
- if len(dock) > 4: # There could be a '(', ')', '[' or ']'.
+ # There could be a '(', ')', '[' or ']'.
+ if len(dock) > 4 and dock[4] in ('[', ']', ']['):
for c in dock[4]:
code.append(c)
if b is not None:
@@ -346,7 +351,9 @@ class LogoCode:
bindex = None
if isinstance(token, tuple):
(token, bindex) = token
- if isNumberType(token):
+ if isinstance(token, Media):
+ res.append(token)
+ elif isNumberType(token):
res.append(token)
elif token.isdigit():
res.append(float(token))
@@ -401,7 +408,7 @@ class LogoCode:
self.istack.append(self.step)
self.step = fcn(*(args))
- def evline(self, blklist):
+ def evline(self, blklist, call_me=True):
""" Evaluate a line of code from the list. """
oldiline = self.iline
self.iline = blklist[:]
@@ -432,7 +439,7 @@ class LogoCode:
(token, self.bindex) = self.iline[1]
# Process the token and any arguments.
- self.icall(self._eval)
+ self.icall(self._eval, call_me)
yield True
# Time to unhighlight the current block.
@@ -455,7 +462,7 @@ class LogoCode:
self.tw.display_coordinates()
yield True
- def _eval(self):
+ def _eval(self, call_me=True):
""" Evaluate the next token on the line of code we are processing. """
token = self.iline.pop(0)
bindex = None
@@ -467,7 +474,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, call_me)
yield True
# and unhighlight if everything was OK.
if not self.tw.hide and bindex is not None:
@@ -479,7 +486,7 @@ class LogoCode:
self.ireturn(res)
yield True
- def _evalsym(self, token):
+ def _evalsym(self, token, call_me):
""" Process primitive associated with symbol token """
self._undefined_check(token)
oldcfun, oldarglist = self.cfun, self.arglist
@@ -489,35 +496,53 @@ class LogoCode:
self.tw.showblocks()
self.tw.display_coordinates()
raise logoerror("#noinput")
+ is_Primitive = type(self.cfun.fcn).__name__ == 'Primitive'
+ is_PrimitiveDisjunction = type(self.cfun.fcn).__name__ == \
+ 'PrimitiveDisjunction'
+ call_args = not (is_Primitive or is_PrimitiveDisjunction)
for i in range(token.nargs):
self._no_args_check()
- self.icall(self._eval)
+ self.icall(self._eval, call_args)
yield True
self.arglist.append(self.iresult)
+ need_to_pop_istack = False
if self.cfun.rprim:
if isinstance(self.cfun.fcn, list):
# debug_output('evalsym rprim list: %s' % (str(token)),
# self.tw.running_sugar)
- self.icall(self._ufuncall, self.cfun.fcn)
+ self.icall(self._ufuncall, self.cfun.fcn, call_args)
yield True
+ need_to_pop_istack = True
+ result = None
else:
- self.icall(self.cfun.fcn, *self.arglist)
- yield True
- result = None
+ if call_me:
+ self.icall(self.cfun.fcn, *self.arglist)
+ yield True
+ need_to_pop_istack = True
+ result = None
+ else:
+ result = (self.cfun.fcn, ) + tuple(self.arglist)
else:
- result = self.cfun.fcn(self, *self.arglist)
+ need_to_pop_istack = True
+ if call_me:
+ result = self.cfun.fcn(self, *self.arglist)
+ else:
+ result = (self.cfun.fcn, self) + tuple(self.arglist)
self.cfun, self.arglist = oldcfun, oldarglist
if self.arglist is not None and result is None:
self.tw.showblocks()
raise logoerror("%s %s %s" %
(oldcfun.name, _("did not output to"),
self.cfun.name))
- self.ireturn(result)
- yield True
+ if need_to_pop_istack:
+ self.ireturn(result)
+ yield True
+ else:
+ self.iresult = result
- def _ufuncall(self, body):
+ def _ufuncall(self, body, call_me):
""" ufuncall """
- self.ijmp(self.evline, body)
+ self.ijmp(self.evline, body, call_me)
yield True
def doevalstep(self):
@@ -526,7 +551,9 @@ class LogoCode:
try:
while (_millisecond() - starttime) < 120:
try:
- if self.step is not None:
+ if self.step is None:
+ return False
+ if self.tw.running_turtleart:
try:
self.step.next()
except ValueError:
@@ -534,23 +561,60 @@ class LogoCode:
self.tw.running_sugar)
self.tw.running_blocks = False
return False
+ except TATypeError as tte:
+ # TODO insert the correct block name
+ # (self.cfun.name is only the name of the
+ # outermost block in this statement/ line of code)
+ # use logoerror("#notanumber") when possible
+ if (tte.req_type in TYPES_NUMERIC and
+ tte.bad_type not in TYPES_NUMERIC):
+ raise logoerror("#notanumber")
+ else:
+ raise logoerror(
+ "%s %s %s %s" %
+ (self.cfun.name, _("doesn't like"),
+ str(tte.bad_value), _("as input")))
+ except ZeroDivisionError:
+ raise logoerror("#zerodivide")
+ except NegativeRootError:
+ raise logoerror("#negroot")
+ except IndexError:
+ raise logoerror("#emptyheap")
else:
- return False
+ try:
+ self.step.next()
+ except BaseException as error:
+ if isinstance(error, (StopIteration,
+ logoerror)):
+ raise error
+ else:
+ traceback.print_exc()
+ self.tw.showlabel(
+ 'status', '%s: %s' %
+ (type(error).__name__, str(error)))
+ return False
except StopIteration:
- # self.tw.turtles.show_all()
- if self.hidden_turtle is not None:
- self.hidden_turtle.show()
- self.hidden_turtle = None
+ if self.tw.running_turtleart:
+ # self.tw.turtles.show_all()
+ if self.hidden_turtle is not None:
+ self.hidden_turtle.show()
+ self.hidden_turtle = None
+ else:
+ self.tw.turtles.get_active_turtle().show()
+ self.tw.running_blocks = False
+ return False
else:
- self.tw.turtles.get_active_turtle().show()
- self.tw.running_blocks = False
- return False
+ self.ireturn()
except logoerror, e:
- self.tw.showblocks()
- self.tw.display_coordinates()
- self.tw.showlabel('syntaxerror', str(e))
- self.tw.turtles.show_all()
- self.tw.running_blocks = False
+ if self.tw.running_turtleart:
+ self.tw.showblocks()
+ self.tw.display_coordinates()
+ self.tw.showlabel('syntaxerror', str(e))
+ self.tw.turtles.show_all()
+ self.tw.running_blocks = False
+ else:
+ traceback.print_exc()
+ self.tw.showlabel('status', 'logoerror: ' + str(e))
return False
return True
@@ -597,19 +661,265 @@ class LogoCode:
name.nargs, name.fcn = 0, body
name.rprim = True
+ def prim_start(self, *ignored_args):
+ ''' Start block: recenter '''
+ if self.tw.running_sugar:
+ self.tw.activity.recenter()
+
def prim_clear(self):
""" Clear screen """
self.tw.clear_plugins()
+ self.stop_playing_media()
+ self.reset_scale()
+ self.reset_timer()
+ self.clear_value_blocks()
+ self.reset_internals()
+ self.tw.canvas.clearscreen()
+ self.tw.turtles.reset_turtles()
+
+ def stop_playing_media(self):
if self.tw.gst_available:
from tagplay import stop_media
stop_media(self)
- self.tw.canvas.clearscreen()
- self.tw.turtles.reset_turtles()
+
+ def reset_scale(self):
self.scale = DEFAULT_SCALE
- self.hidden_turtle = None
+
+ def reset_timer(self):
self.start_time = time()
- self.clear_value_blocks()
- self.tw.activity.restore_state()
+
+ def get_start_time(self):
+ return self.start_time
+
+ def reset_internals(self):
+ self.hidden_turtle = None
+ if self.tw.running_turtleart:
+ self.tw.activity.restore_state()
+
+ def prim_loop(self, controller, blklist):
+ """ Execute a loop
+ controller -- iterator that yields True iff the loop should be run
+ once more OR a callable that returns such an iterator
+ blklist -- list of callables that form the loop body """
+ if not hasattr(controller, "next"):
+ if callable(controller):
+ controller = controller()
+ else:
+ raise TypeError("a loop controller must be either an iterator "
+ "or a callable that returns an iterator")
+ while next(controller):
+ self.icall(self.evline, blklist[:])
+ yield True
+ if self.procstop:
+ break
+ self.ireturn()
+ yield True
+
+ def prim_clamp(self, blklist):
+ """ Run clamp blklist """
+ self.icall(self.evline, blklist[:])
+ yield True
+ self.procstop = False
+ self.ireturn()
+ yield True
+
+ def set_scale(self, scale):
+ ''' Set scale for media blocks '''
+ self.scale = scale
+
+ def get_scale(self):
+ ''' Set scale for media blocks '''
+ return self.scale
+
+ def prim_stop_stack(self):
+ """ Stop execution of a stack """
+ self.procstop = True
+
+ def prim_turtle(self, name):
+ self.tw.turtles.set_turtle(name)
+
+ def prim_wait(self, wait_time):
+ """ Show the turtle while we wait """
+ self.tw.turtles.get_active_turtle().show()
+ endtime = _millisecond() + wait_time * 1000.
+ while _millisecond() < endtime:
+ sleep(wait_time / 10.)
+ yield True
+ self.tw.turtles.get_active_turtle().hide()
+ 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_set_box(self, name, value):
+ """ Store value in named box """
+ (key, is_native) = self._get_box_key(name)
+ self.boxes[key] = value
+ if is_native:
+ if self.update_values:
+ self.update_label_value(name, value)
+ else:
+ if self.update_values:
+ self.update_label_value('box', value, label=name)
+
+ def prim_get_box(self, name):
+ """ Retrieve value from named box """
+ (key, is_native) = self._get_box_key(name)
+ try:
+ return self.boxes[key]
+ except KeyError:
+ # FIXME this looks like a syntax error in the GUI
+ raise logoerror("#emptybox")
+
+ def _get_box_key(self, name):
+ """ Return the key used for this box in the boxes dictionary and a
+ boolean indicating whether it is a 'native' box """
+ if name in ('box1', 'box2'):
+ return (name, True)
+ else:
+ # make sure '5' and '5.0' point to the same box
+ if isinstance(name, (basestring, int, long)):
+ try:
+ name = float(name)
+ except ValueError:
+ pass
+ return ('box3_' + str(name), False)
+
+ def prim_define_stack(self, name):
+ """ Top of a named stack """
+ pass
+
+ def prim_invoke_stack(self, name):
+ """ Process a named stack """
+ key = self._get_stack_key(name)
+ if self.stacks.get(key) is None:
+ raise logoerror("#nostack")
+ self.icall(self.evline, self.stacks[key][:])
+ yield True
+ self.procstop = False
+ self.ireturn()
+ yield True
+
+ def _get_stack_key(self, name):
+ """ Return the key used for this stack in the stacks dictionary """
+ if name in ('stack1', 'stack2'):
+ return name
+ else:
+ # make sure '5' and '5.0' point to the same action stack
+ if isinstance(name, (int, long, float)):
+ if int(name) == name:
+ name = int(name)
+ else:
+ name = float(name)
+ return 'stack3' + str(name)
+
+ def load_heap(self, path):
+ """ Load FILO from file """
+ if self.tw.running_sugar:
+ # Choose a datastore object and push data to heap (Sugar only)
+ chooser_dialog(self.tw.parent, path, self.push_file_data_to_heap)
+ else:
+ if not os.path.exists(path):
+ path, self.tw.load_save_folder = get_load_name(
+ '.*', self.tw.load_save_folder)
+ if path is None:
+ return
+
+ data = data_from_file(path)
+ if data is not None:
+ for val in data:
+ self.heap.append(val)
+
+ def save_heap(self, path):
+ """ save FILO to file """
+ if self.tw.running_sugar:
+ from sugar import profile
+ from sugar.datastore import datastore
+ from sugar.activity import activity
+
+ # Save JSON-encoded heap to temporary file
+ heap_file = os.path.join(get_path(activity, 'instance'),
+ str(path) + '.txt')
+ data_to_file(self.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(path)
+ dsobject.metadata['icon-color'] = profile.get_color().to_string()
+ dsobject.metadata['mime_type'] = 'text/plain'
+ dsobject.set_file_path(heap_file)
+ datastore.write(dsobject)
+ dsobject.destroy()
+ else:
+ heap_file = path
+ data_to_file(self.heap, heap_file)
+
+ def get_heap(self):
+ return self.heap
+
+ def reset_heap(self):
+ """ Reset heap to an empty list """
+ # empty the list rather than setting it to a new empty list object,
+ # so the object references are preserved
+ while self.heap:
+ self.heap.pop()
+
+ def prim_myblock(self, *args):
+ """ Run Python code imported from Journal """
+ if self.bindex is not None and self.bindex in self.tw.myblock:
+ try:
+ myfunc_import(self, self.tw.myblock[self.bindex], args)
+ except:
+ raise logoerror("#syntaxerror")
+
+ def prim_myfunction(self, f, *args):
+ """ Programmable block (Call tajail.myfunc and convert any errors to
+ logoerrors) """
+ try:
+ y = myfunc(f, args)
+ if str(y) == 'nan':
+ debug_output('Python function returned NAN',
+ self.tw.running_sugar)
+ self.stop_logo()
+ raise logoerror("#notanumber")
+ else:
+ return y
+ except ZeroDivisionError:
+ self.stop_logo()
+ raise logoerror("#zerodivide")
+ except ValueError, e:
+ self.stop_logo()
+ raise logoerror('#' + str(e))
+ except SyntaxError, e:
+ self.stop_logo()
+ raise logoerror('#' + str(e))
+ except NameError, e:
+ self.stop_logo()
+ raise logoerror('#' + str(e))
+ except OverflowError:
+ self.stop_logo()
+ raise logoerror("#overflowerror")
+ except TypeError:
+ self.stop_logo()
+ raise logoerror("#notanumber")
def clear_value_blocks(self):
if not hasattr(self, 'value_blocks_to_update'):
@@ -686,6 +996,145 @@ class LogoCode:
for blk in drag_group:
blk.spr.move_relative((dx, 0))
+ def reskin(self, obj):
+ """ 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
+ self.dsobject = None
+
+ if os_path_exists(obj.value): # file path
+ self.filepath = obj.value
+ elif self.tw.running_sugar: # datastore object
+ from sugar.datastore import datastore
+ try:
+ self.dsobject = datastore.get(obj.value)
+ except:
+ debug_output("Couldn't find dsobject %s" %
+ (obj.value), self.tw.running_sugar)
+ if self.dsobject is not None:
+ self.filepath = self.dsobject.file_path
+
+ if self.filepath is 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)
+ debug_output("Couldn't open skin %s" % (self.filepath),
+ self.tw.running_sugar)
+ if pixbuf is not None:
+ self.tw.turtles.get_active_turtle().set_shapes([pixbuf])
+ pen_state = self.tw.turtles.get_active_turtle().get_pen_state()
+ if pen_state:
+ self.tw.turtles.get_active_turtle().set_pen_state(False)
+ self.tw.turtles.get_active_turtle().forward(0)
+ if pen_state:
+ self.tw.turtles.get_active_turtle().set_pen_state(True)
+
+ if self.tw.sharing():
+ if self.tw.running_sugar:
+ tmp_path = get_path(self.tw.activity, 'instance')
+ else:
+ tmp_path = '/tmp'
+ tmp_file = os.path.join(get_path(self.tw.activity, 'instance'),
+ 'tmpfile.png')
+ pixbuf.save(tmp_file, 'png', {'quality': '100'})
+ data = image_to_base64(tmp_file, tmp_path)
+ height = pixbuf.get_height()
+ width = pixbuf.get_width()
+ event = 'R|%s' % (data_to_string([self.tw.nick,
+ [round_int(width),
+ round_int(height),
+ data]]))
+ gobject.idle_add(self.tw.send_event, event)
+ os.remove(tmp_file)
+
+ def showlist(self, objects):
+ """ Display list of media objects """
+ x = (self.tw.turtles.get_active_turtle().get_xy()[0] /
+ self.tw.coord_scale)
+ y = (self.tw.turtles.get_active_turtle().get_xy()[1] /
+ self.tw.coord_scale)
+ for obj in objects:
+ self.tw.turtles.get_active_turtle().set_xy(x, y, pendown=False)
+ self.show(obj)
+ y -= int(self.tw.canvas.textsize * self.tw.lead)
+
+ def show(self, obj, center=False):
+ """ Show is the general-purpose media-rendering block. """
+ # media
+ if isinstance(obj, Media) and obj.value:
+ self.filepath = None
+ self.pixbuf = None # Camera writes directly to pixbuf
+ self.dsobject = None
+
+ # camera snapshot
+ if obj.value.lower() in media_blocks_dictionary:
+ media_blocks_dictionary[obj.value.lower()]()
+ # file path
+ elif os_path_exists(obj.value):
+ self.filepath = obj.value
+ # datastore object
+ elif self.tw.running_sugar:
+ from sugar.datastore import datastore
+ try:
+ self.dsobject = datastore.get(obj.value)
+ except:
+ debug_output("Couldn't find dsobject %s" %
+ (obj.value), self.tw.running_sugar)
+ if self.dsobject is not None:
+ self.filepath = self.dsobject.file_path
+
+ if self.pixbuf is not None:
+ self.insert_image(center=center, pixbuf=True)
+ elif self.filepath is None:
+ if self.dsobject is not None:
+ self.tw.showlabel(
+ 'nojournal',
+ self.dsobject.metadata['title'])
+ else:
+ self.tw.showlabel('nojournal', obj.value)
+ debug_output("Couldn't open %s" % (obj.value),
+ self.tw.running_sugar)
+ elif obj.type == 'media':
+ self.insert_image(center=center)
+ elif obj.type == '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 obj.type == 'audio':
+ self.play_sound()
+ elif obj.type == 'video':
+ self.play_video()
+
+ if self.dsobject is not None:
+ self.dsobject.destroy()
+
+ # text or number
+ elif isinstance(obj, (basestring, float, int)):
+ if isinstance(obj, (float, int)):
+ obj = round_int(obj)
+ x, y = self.x2tx(), self.y2ty()
+ if center:
+ y -= self.tw.canvas.textsize
+ self.tw.turtles.get_active_turtle().draw_text(
+ obj, x, y,
+ int(self.tw.canvas.textsize * self.scale / 100.),
+ self.tw.canvas.width - x)
+
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)
@@ -861,7 +1310,7 @@ class LogoCode:
def _expand_forever(self, b, blk, blocks):
""" Expand a while or until block into: forever, ifelse, stopstack
- Expand a forever block to run in a separate stack
+ Expand a forever block to run in a separate stack
Parameters: the loop block, the top block, all blocks.
Return the start block of the expanded loop, and all blocks."""
@@ -911,7 +1360,7 @@ class LogoCode:
first_label_blk = HiddenBlock('string', value=action_flow_name)
# Assign new connections and build the docks
- if inflow is not None:
+ if inflow is not None and b in inflow.connections:
i = inflow.connections.index(b)
if until_blk and whileflow is not None:
inflow.connections[i] = action_first
@@ -980,14 +1429,14 @@ class LogoCode:
# Create a separate stacks for the forever loop and the whileflow
code = self._blocks_to_code(forever_blk)
- self.stacks['stack3' + str(action_name)] = self._readline(code)
+ self.stacks[self._get_stack_key(action_name)] = self._readline(code)
if until_blk and whileflow is not None:
# Create a stack from the whileflow to be called from
# action_first, but then reconnect it to the ifelse block
c = whileflow.connections[0]
whileflow.connections[0] = None
code = self._blocks_to_code(whileflow)
- self.stacks['stack3' + str(action_flow_name)] = \
+ self.stacks[self._get_stack_key(action_flow_name)] = \
self._readline(code)
whileflow.connections[0] = c
diff --git a/TurtleArt/tapalette.py b/TurtleArt/tapalette.py
index 6fd347a..9332155 100644
--- a/TurtleArt/tapalette.py
+++ b/TurtleArt/tapalette.py
@@ -90,7 +90,6 @@ except ImportError:
HELP_PALETTE = False
from taconstants import (EXPANDABLE_STYLE, EXPANDABLE_FLOW)
-from tautils import debug_output
from gettext import gettext as _
@@ -224,7 +223,7 @@ class Palette():
def make_palette(palette_name, colors=None, help_string=None, position=None,
- init_on_start=False):
+ init_on_start=False, translation=None):
""" Palette helper function """
if colors is None:
palette = Palette(palette_name)
diff --git a/TurtleArt/taprimitive.py b/TurtleArt/taprimitive.py
new file mode 100644
index 0000000..b689de5
--- /dev/null
+++ b/TurtleArt/taprimitive.py
@@ -0,0 +1,1162 @@
+#Copyright (c) 2013 Marion Zepf
+
+#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 ast
+from gettext import gettext as _
+from math import sqrt
+from random import uniform
+import traceback
+
+#from ast_pprint import * # only used for debugging, safe to comment out
+
+from tablock import Media
+from tacanvas import TurtleGraphics
+from taconstants import (Color, CONSTANTS)
+from talogo import (LogoCode, logoerror, NegativeRootError)
+from taturtle import (Turtle, Turtles)
+from TurtleArt.tatype import (TYPE_CHAR, TYPE_INT, TYPE_FLOAT, TYPE_OBJECT,
+ TYPE_MEDIA, TYPE_COLOR, BOX_AST, ACTION_AST,
+ Type, TypeDisjunction, TATypeError, get_type,
+ TypedSubscript, TypedName, is_bound_method,
+ is_instancemethod, is_staticmethod,
+ identity, get_converter, convert, get_call_ast)
+from tautils import debug_output
+from tawindow import (TurtleArtWindow, global_objects, plugins_in_use)
+from util import ast_extensions
+
+
+class PyExportError(BaseException):
+ """ Error that is raised when something goes wrong while converting the
+ blocks to python code """
+
+ def __init__(self, message, block=None):
+ """ message -- the error message
+ block -- the block where the error occurred """
+ self.message = message
+ self.block = block
+
+ def __str__(self):
+ if self.block is not None:
+ return _("error in highlighted block") + ": " + str(self.message)
+ else:
+ return _("error") + ": " + str(self.message)
+
+
+class Primitive(object):
+ """ Something that can be called when the block code is executed in TA,
+ but that can also be transformed into a Python AST."""
+
+ _DEBUG = False
+
+ STANDARD_OPERATORS = {'plus': (ast.UAdd, ast.Add),
+ 'minus': (ast.USub, ast.Sub),
+ 'multiply': ast.Mult,
+ 'divide': ast.Div,
+ 'modulo': ast.Mod,
+ 'power': ast.Pow,
+ 'and_': ast.And,
+ 'or_': ast.Or,
+ 'not_': ast.Not,
+ 'equals': ast.Eq,
+ 'less': ast.Lt,
+ 'greater': ast.Gt}
+
+ def __init__(self, func, return_type=TYPE_OBJECT, arg_descs=None,
+ kwarg_descs=None, call_afterwards=None, export_me=True):
+ """ return_type -- the type (from the type hierarchy) that this
+ Primitive will return
+ arg_descs, kwarg_descs -- a list of argument descriptions and
+ a dictionary of keyword argument descriptions. An argument
+ description can be either an ArgSlot or a ConstantArg.
+ call_afterwards -- Code to call after this Primitive has been called
+ (e.g., for updating labels in LogoCode) (not used for creating
+ AST)
+ export_me -- True iff this Primitive should be exported to Python
+ code (the default case) """
+ self.func = func
+ self.return_type = return_type
+
+ if arg_descs is None:
+ self.arg_descs = []
+ else:
+ self.arg_descs = arg_descs
+
+ if kwarg_descs is None:
+ self.kwarg_descs = {}
+ else:
+ self.kwarg_descs = kwarg_descs
+
+ self.call_afterwards = call_afterwards
+ self.export_me = export_me
+
+ def copy(self):
+ """ Return a Primitive object with the same attributes as this one.
+ Shallow-copy the arg_descs and kwarg_descs attributes. """
+ arg_descs_copy = self.arg_descs[:]
+ if isinstance(self.arg_descs, ArgListDisjunction):
+ arg_descs_copy = ArgListDisjunction(arg_descs_copy)
+ return Primitive(self.func,
+ return_type=self.return_type,
+ arg_descs=arg_descs_copy,
+ kwarg_descs=self.kwarg_descs.copy(),
+ call_afterwards=self.call_afterwards,
+ export_me=self.export_me)
+
+ def __repr__(self):
+ return "Primitive(%s -> %s)" % (repr(self.func), str(self.return_type))
+
+ @property
+ def __name__(self):
+ return self.func.__name__
+
+ def get_name_for_export(self):
+ """ Return the expression (as a string) that represents this Primitive
+ in the exported Python code, e.g., 'turtle.forward'. """
+ func_name = ""
+ if self.wants_turtle():
+ func_name = "turtle."
+ elif self.wants_turtles():
+ func_name = "turtles."
+ elif self.wants_canvas():
+ func_name = "canvas."
+ elif self.wants_logocode():
+ func_name = "logo."
+ elif self.wants_heap():
+ func_name = "logo.heap."
+ elif self.wants_tawindow():
+ func_name = "tw."
+ else:
+ results, plugin = self.wants_plugin()
+ if results:
+ for k in global_objects.keys():
+ if k == plugin:
+ if k not in plugins_in_use:
+ plugins_in_use.append(k)
+ func_name = k.lower() + '.'
+ break
+
+ # get the name of the function directly from the function itself
+ func_name += self.func.__name__
+ return func_name
+
+ def are_slots_filled(self):
+ """ Return True iff none of the arg_descs or kwarg_descs is an
+ ArgSlot. """
+ for arg_desc in self.arg_descs:
+ if isinstance(arg_desc, ArgSlot):
+ return False
+ for key in self.kwarg_descs:
+ if isinstance(self.kwarg_descs[key], ArgSlot):
+ return False
+ return True
+
+ def fill_slots(self, arguments=None, keywords=None, convert_to_ast=False,
+ call_my_args=True):
+ """ Return a copy of this Primitive whose ArgSlots are filled with
+ the given arguments, turned into ConstantArgs. Call the arguments,
+ apply their wrappers, and check their types as appropriate. """
+ if arguments is None:
+ arguments = []
+ if keywords is None:
+ keywords = {}
+
+ new_prim = self.copy()
+
+ if isinstance(new_prim.arg_descs, ArgListDisjunction):
+ slot_list_alternatives = list(new_prim.arg_descs)
+ else:
+ slot_list_alternatives = [new_prim.arg_descs]
+
+ # arguments
+ error = None
+ filler = None
+ for slot_list in slot_list_alternatives:
+ error = None
+ new_slot_list = []
+ filler_list = list(arguments[:])
+ for slot in slot_list:
+ if isinstance(slot, ArgSlot):
+ filler = filler_list.pop(0)
+ try:
+ const = slot.fill(filler,
+ convert_to_ast=convert_to_ast,
+ call_my_args=call_my_args)
+ except TATypeError as error:
+ if Primitive._DEBUG:
+ traceback.print_exc()
+ break
+ else:
+ new_slot_list.append(const)
+ else:
+ new_slot_list.append(slot)
+ if error is None:
+ new_prim.arg_descs = new_slot_list
+ break
+ if error is not None:
+ raise error
+
+ # keyword arguments
+ for key in keywords:
+ kwarg_desc = new_prim.kwarg_descs[key]
+ if isinstance(kwarg_desc, ArgSlot):
+ const = kwarg_desc.fill(keywords[key],
+ convert_to_ast=convert_to_ast,
+ call_my_args=call_my_args)
+ new_prim.kwarg_descs[key] = const
+
+ return new_prim
+
+ def get_values_of_filled_slots(self, exportable_only=False):
+ """ Return the values of all filled argument slots as a list, and
+ the values of all filled keyword argument slots as a dictionary.
+ Ignore all un-filled (keyword) argument slots.
+ exportable_only -- return only exportable values and convert values
+ to ASTs instead of calling them """
+ new_args = []
+ for c_arg in self.arg_descs:
+ if (isinstance(c_arg, ConstantArg)
+ and (not exportable_only
+ or export_me(c_arg.value))):
+ new_args.append(c_arg.get(convert_to_ast=exportable_only))
+ new_kwargs = {}
+ for key in self.kwarg_descs:
+ if (isinstance(self.kwarg_descs[key], ConstantArg)
+ and (not exportable_only
+ or export_me(self.kwarg_descs[key].value))):
+ new_kwargs[key] = self.kwarg_descs[key].get(
+ convert_to_ast=exportable_only)
+ return (new_args, new_kwargs)
+
+ def allow_call_args(self, recursive=False):
+ """ Set call_args attribute of all argument descriptions to True
+ recursive -- recursively call allow_call_args on all constant args
+ that are Primitives """
+ for arg_desc in self.arg_descs:
+ arg_desc.call_arg = True
+ if (recursive and isinstance(arg_desc, ConstantArg) and
+ isinstance(arg_desc.value, Primitive)):
+ arg_desc.value.allow_call_args(recursive=True)
+ for kwarg_desc in self.kwarg_descs:
+ kwarg_desc.call_arg = True
+ if (recursive and isinstance(kwarg_desc, ConstantArg) and
+ isinstance(kwarg_desc.value, Primitive)):
+ kwarg_desc.value.allow_call_args(recursive=True)
+
+ def __call__(self, *runtime_args, **runtime_kwargs):
+ """ Execute the function, passing it the arguments received at
+ runtime. Also call the function in self.call_afterwards and pass it
+ all runtime_args and runtime_kwargs.
+ If the very first argument is a LogoCode instance, it is removed.
+ The active turtle, the Turtles object, the canvas, the LogoCode
+ object, or the TurtleArtWindow object will be prepended to the
+ arguments (depending on what this Primitive wants). """
+
+ # remove the first argument if it is a LogoCode instance
+ if runtime_args and isinstance(runtime_args[0], LogoCode):
+ runtime_args = runtime_args[1:]
+
+ if Primitive._DEBUG:
+ debug_output(repr(self))
+ debug_output(" runtime_args: " + repr(runtime_args))
+ # fill the ArgSlots with the runtime arguments
+ new_prim = self.fill_slots(runtime_args, runtime_kwargs,
+ convert_to_ast=False)
+ if not new_prim.are_slots_filled():
+ raise logoerror("#syntaxerror")
+ if Primitive._DEBUG:
+ debug_output(" new_prim.arg_descs: " + repr(new_prim.arg_descs))
+
+ # extract the actual values from the (now constant) arguments
+ (new_args, new_kwargs) = new_prim.get_values_of_filled_slots()
+ if Primitive._DEBUG:
+ debug_output(" new_args: " + repr(new_args))
+ debug_output("end " + repr(self))
+
+ # what does this primitive want as its first argument?
+ first_arg = None
+ if not is_bound_method(new_prim.func):
+ if new_prim.wants_turtle():
+ first_arg = global_objects["turtles"].get_active_turtle()
+ elif new_prim.wants_turtles():
+ first_arg = global_objects["turtles"]
+ elif new_prim.wants_canvas():
+ first_arg = global_objects["canvas"]
+ elif new_prim.wants_logocode():
+ first_arg = global_objects["logo"]
+ elif new_prim.wants_heap():
+ first_arg = global_objects["logo"].heap
+ elif new_prim.wants_tawindow():
+ first_arg = global_objects["window"]
+ else:
+ result, plugin = new_prim.wants_plugin()
+ if result:
+ first_arg = plugin
+
+ # execute the actual function
+ if first_arg is None:
+ return_value = new_prim.func(*new_args, **new_kwargs)
+ else:
+ return_value = new_prim.func(first_arg, *new_args, **new_kwargs)
+
+ if new_prim.call_afterwards is not None:
+ new_prim.call_afterwards(*new_args, **new_kwargs)
+
+ return return_value
+
+ def get_ast(self, *arg_asts, **kwarg_asts):
+ """Transform this object into a Python AST. When serialized and
+ executed, the AST will do exactly the same as calling this
+ object."""
+
+ if Primitive._DEBUG:
+ debug_output(repr(self))
+ debug_output(" arg_asts: " + repr(arg_asts))
+ new_prim = self.fill_slots(arg_asts, kwarg_asts, convert_to_ast=True)
+ if not new_prim.are_slots_filled():
+ raise PyExportError("not enough arguments")
+ if Primitive._DEBUG:
+ debug_output(" new_prim.arg_descs: " + repr(new_prim.arg_descs))
+
+ # extract the actual values from the (now constant) arguments
+ (new_arg_asts, new_kwarg_asts) = new_prim.get_values_of_filled_slots(
+ exportable_only=True)
+ if Primitive._DEBUG:
+ debug_output(" new_arg_asts: " + repr(new_arg_asts))
+ debug_output("end " + repr(self))
+
+ # SPECIAL HANDLING #
+
+ # loops
+ if self == LogoCode.prim_loop:
+ controller = self._get_loop_controller()
+ if controller == Primitive.controller_repeat:
+ # 'repeat' loop
+ num_repetitions = new_arg_asts[0]
+ if num_repetitions.func.id == 'controller_repeat':
+ num_repetitions = num_repetitions.args[0]
+ repeat_iter = get_call_ast("range", [num_repetitions])
+ # TODO use new variable name in nested loops
+ loop_ast = ast.For(target=ast.Name(id="i", ctx=ast.Store),
+ iter=repeat_iter,
+ body=new_arg_asts[1],
+ orelse=[])
+ return loop_ast
+ else:
+ if controller == Primitive.controller_forever:
+ condition_ast = ast.Name(id="True", ctx=ast.Load)
+ elif controller == Primitive.controller_while:
+ condition_ast = new_arg_asts[0].args[0]
+ elif controller == Primitive.controller_until:
+ pos_cond_ast = new_arg_asts[0].args[0]
+ condition_ast = ast.UnaryOp(op=ast.Not,
+ operand=pos_cond_ast)
+ else:
+ raise PyExportError("unknown loop controller: " +
+ repr(controller))
+ loop_ast = ast.While(test=condition_ast,
+ body=new_arg_asts[1],
+ orelse=[])
+ return loop_ast
+
+ # conditionals
+ elif self in (LogoCode.prim_if, LogoCode.prim_ifelse):
+ test = new_arg_asts[0]
+ body = new_arg_asts[1]
+ if len(new_arg_asts) > 2:
+ orelse = new_arg_asts[2]
+ else:
+ orelse = []
+ if_ast = ast.If(test=test, body=body, orelse=orelse)
+ return if_ast
+
+ # boxes
+ elif self == LogoCode.prim_set_box:
+ target_ast = ast.Subscript(value=BOX_AST,
+ slice=ast.Index(value=new_arg_asts[0]),
+ ctx=ast.Store)
+ return ast.Assign(targets=[target_ast], value=new_arg_asts[1])
+ elif self == LogoCode.prim_get_box:
+ return ast.Subscript(value=BOX_AST,
+ slice=ast.Index(value=new_arg_asts[0]),
+ ctx=ast.Load)
+
+ # action stacks
+ elif self == LogoCode.prim_define_stack:
+ return
+ elif self == LogoCode.prim_invoke_stack:
+ stack_func = ast.Subscript(
+ value=ACTION_AST,
+ slice=ast.Index(value=new_arg_asts[0]), ctx=ast.Load)
+ call_ast = get_call_ast('logo.icall', [stack_func])
+ return [call_ast, ast_yield_true()]
+
+ # stop stack
+ elif self == LogoCode.prim_stop_stack:
+ return ast.Return()
+
+ # sleep/ wait
+ elif self == LogoCode.prim_wait:
+ return [get_call_ast('sleep', new_arg_asts), ast_yield_true()]
+
+ # standard operators
+ elif self.func.__name__ in Primitive.STANDARD_OPERATORS:
+ op = Primitive.STANDARD_OPERATORS[self.func.__name__]
+ # 'divide': prevent unwanted integer division
+ if self == Primitive.divide:
+ def _is_float(x):
+ return get_type(x)[0] == TYPE_FLOAT
+ if (not _is_float(new_arg_asts[0]) and
+ not _is_float(new_arg_asts[1])):
+ new_arg_asts[0] = get_call_ast('float', [new_arg_asts[0]],
+ return_type=TYPE_FLOAT)
+ if len(new_arg_asts) == 1:
+ if isinstance(op, tuple):
+ op = op[0]
+ return ast.UnaryOp(op=op, operand=new_arg_asts[0])
+ elif len(new_arg_asts) == 2:
+ if isinstance(op, tuple):
+ op = op[1]
+ (left, right) = new_arg_asts
+ if issubclass(op, ast.boolop):
+ return ast.BoolOp(op=op, values=[left, right])
+ elif issubclass(op, ast.cmpop):
+ return ast.Compare(left=left, ops=[op],
+ comparators=[right])
+ else:
+ return ast.BinOp(op=op, left=left, right=right)
+
+ # f(x)
+ elif self == LogoCode.prim_myfunction:
+ param_asts = []
+ for id_ in ['x', 'y', 'z'][:len(new_arg_asts)-1]:
+ param_asts.append(ast.Name(id=id_, ctx=ast.Param))
+ func_ast = ast_extensions.LambdaWithStrBody(
+ body_str=new_arg_asts[0].s, args=param_asts)
+ return get_call_ast(func_ast, new_arg_asts[1:],
+ return_type=self.return_type)
+
+ # square root
+ elif self == Primitive.square_root:
+ return get_call_ast('sqrt', new_arg_asts, new_kwarg_asts,
+ return_type=self.return_type)
+
+ # random
+ elif self in (Primitive.random_char, Primitive.random_int):
+ uniform_ast = get_call_ast('uniform', new_arg_asts)
+ round_ast = get_call_ast('round', [uniform_ast, ast.Num(n=0)])
+ int_ast = get_call_ast('int', [round_ast], return_type=TYPE_INT)
+ if self == Primitive.random_char:
+ chr_ast = get_call_ast('chr', [int_ast], return_type=TYPE_CHAR)
+ return chr_ast
+ else:
+ return int_ast
+
+ # identity
+ elif self == Primitive.identity:
+ return new_arg_asts[0]
+
+ # constant
+ elif self == CONSTANTS.get:
+ return TypedSubscript(value=ast.Name(id='CONSTANTS', ctx=ast.Load),
+ slice_=ast.Index(value=new_arg_asts[0]),
+ return_type=self.return_type)
+
+ # group of Primitives or sandwich-clamp block
+ elif self in (Primitive.group, LogoCode.prim_clamp):
+ ast_list = []
+ for prim in new_arg_asts[0]:
+ if export_me(prim):
+ new_ast = value_to_ast(prim)
+ if isinstance(new_ast, ast.AST):
+ ast_list.append(new_ast)
+ return ast_list
+
+ # set turtle
+ elif self == LogoCode.prim_turtle:
+ text = 'turtle = turtles.get_active_turtle()'
+ return [get_call_ast('logo.prim_turtle', new_arg_asts),
+ ast_extensions.ExtraCode(text)]
+
+ # comment
+ elif self == Primitive.comment:
+ if isinstance(new_arg_asts[0], ast.Str):
+ text = ' ' + str(new_arg_asts[0].s)
+ else:
+ text = ' ' + str(new_arg_asts[0])
+ return ast_extensions.Comment(text)
+
+ # print
+ elif self == TurtleArtWindow.print_:
+ func_name = self.get_name_for_export()
+ call_ast = get_call_ast(func_name, new_arg_asts)
+ print_ast = ast.Print(values=new_arg_asts[:1], dest=None, nl=True)
+ return [call_ast, print_ast]
+
+ # heap
+ elif self == LogoCode.get_heap:
+ return TypedName(id_='logo.heap', return_type=self.return_type)
+ elif self == LogoCode.reset_heap:
+ target_ast = ast.Name(id='logo.heap', ctx=ast.Store)
+ value_ast = ast.List(elts=[], ctx=ast.Load)
+ return ast.Assign(targets=[target_ast], value=value_ast)
+
+ # NORMAL FUNCTION CALL #
+
+ else:
+ func_name = self.get_name_for_export()
+ return get_call_ast(func_name, new_arg_asts, new_kwarg_asts,
+ return_type=self.return_type)
+
+ def __eq__(self, other):
+ """ Two Primitives are equal iff their all their properties are equal.
+ Consider bound and unbound methods equal. """
+ # other is a Primitive
+ if isinstance(other, Primitive):
+ return (self == other.func and
+ self.return_type == other.return_type and
+ self.arg_descs == other.arg_descs and
+ self.kwarg_descs == other.kwarg_descs and
+ self.call_afterwards == other.call_afterwards and
+ self.export_me == other.export_me)
+
+ # other is a callable
+ elif callable(other):
+ if is_instancemethod(self.func) != is_instancemethod(other):
+ return False
+ elif is_instancemethod(self.func): # and is_instancemethod(other):
+ return (self.func.im_class == other.im_class and
+ self.func.im_func == other.im_func)
+ else:
+ return self.func == other
+
+ elif is_staticmethod(other):
+ return self.func == other.__func__
+
+ # other is neither a Primitive nor a callable
+ else:
+ return False
+
+ def wants_turtle(self):
+ """Does this Primitive want to get the active turtle as its first
+ argument?"""
+ return self._wants(Turtle)
+
+ def wants_turtles(self):
+ """ Does this Primitive want to get the Turtles instance as its
+ first argument? """
+ return self._wants(Turtles)
+
+ def wants_canvas(self):
+ """ Does this Primitive want to get the canvas as its first
+ argument? """
+ return self._wants(TurtleGraphics)
+
+ def wants_logocode(self):
+ """ Does this Primitive want to get the LogoCode instance as its
+ first argument? """
+ return (self.func.__name__ == '<lambda>' or self._wants(LogoCode))
+
+ def wants_heap(self):
+ """ Does this Primitive want to get the heap as its first argument? """
+ return ((hasattr(self.func, '__self__') and
+ isinstance(self.func.__self__, list)) or
+ self.func in list.__dict__.values())
+
+ def wants_tawindow(self):
+ """ Does this Primitive want to get the TurtleArtWindow instance
+ as its first argument? """
+ return self._wants(TurtleArtWindow)
+
+ def wants_plugin(self):
+ """Does this Primitive want to get a plugin instance as its first
+ argument? """
+ for obj in global_objects.keys():
+ if self._wants(global_objects[obj].__class__):
+ return True, obj
+ return False, None
+
+ def wants_nothing(self):
+ """Does this Primitive want nothing as its first argument? I.e. does
+ it want to be passed all the arguments of the block and
+ nothing else?"""
+ return not is_instancemethod(self.func)
+
+ def _wants(self, theClass):
+ return is_instancemethod(self.func) and self.func.im_class == theClass
+
+ # treat the following methods in a special way when converting the
+ # Primitive to an AST
+
+ @staticmethod
+ def controller_repeat(num):
+ """ Loop controller for the 'repeat' block """
+ for i in range(num):
+ yield True
+ yield False
+
+ @staticmethod
+ def controller_forever():
+ """ Loop controller for the 'forever' block """
+ while True:
+ yield True
+
+ @staticmethod
+ def controller_while(condition):
+ """ Loop controller for the 'while' block
+ condition -- Primitive that is evaluated every time through the
+ loop """
+ condition.allow_call_args(recursive=True)
+ while condition():
+ yield True
+ yield False
+
+ @staticmethod
+ def controller_until(condition):
+ """ Loop controller for the 'until' block
+ condition -- Primitive that is evaluated every time through the
+ loop """
+ condition.allow_call_args(recursive=True)
+ while not condition():
+ yield True
+ yield False
+
+ LOOP_CONTROLLERS = [controller_repeat, controller_forever,
+ controller_while, controller_until]
+
+ def _get_loop_controller(self):
+ """ Return the controller for this loop Primitive. Raise a
+ ValueError if no controller was found. """
+ def _is_loop_controller(candidate):
+ return (callable(candidate)
+ and candidate in Primitive.LOOP_CONTROLLERS)
+
+ for desc in self.arg_descs:
+ if isinstance(desc, ConstantArg):
+ value = desc.value
+ if _is_loop_controller(value):
+ return value
+ elif isinstance(desc, ArgSlot):
+ wrapper = desc.wrapper
+ if _is_loop_controller(wrapper):
+ return wrapper
+
+ # no controller found
+ raise PyExportError("found no loop controller for " + repr(self))
+
+ @staticmethod
+ def do_nothing():
+ pass
+
+ @staticmethod
+ def identity(arg):
+ """ Return the argument unchanged """
+ return arg
+
+ @staticmethod
+ def group(prim_list):
+ """ Group together multiple Primitives into one. Treat each Primitive
+ as a separate line of code. """
+ return_val = None
+ for prim in prim_list:
+ return_val = prim()
+ return return_val
+
+ @staticmethod
+ def plus(arg1, arg2=None):
+ """ If only one argument is given, prefix it with '+'. If two
+ arguments are given, add the second to the first. If the first
+ argument is a tuple of length 2 and the second is None, use the
+ values in the tuple as arg1 and arg2. """
+ if isinstance(arg1, (list, tuple)) and len(arg1) == 2 and arg2 is None:
+ (arg1, arg2) = arg1
+ if arg2 is None:
+ return + arg1
+ else:
+ return arg1 + arg2
+
+ @staticmethod
+ def minus(arg1, arg2=None):
+ """ If only one argument is given, change its sign. If two
+ arguments are given, subtract the second from the first. """
+ if arg2 is None:
+ return - arg1
+ else:
+ return arg1 - arg2
+
+ @staticmethod
+ def multiply(arg1, arg2):
+ """ Multiply the two arguments """
+ return arg1 * arg2
+
+ @staticmethod
+ def divide(arg1, arg2):
+ """ Divide the first argument by the second """
+ return float(arg1) / arg2
+
+ @staticmethod
+ def modulo(arg1, arg2):
+ """ Return the remainder of dividing the first argument by the second.
+ If the first argument is a string, format it with the value(s) in
+ the second argument. """
+ return arg1 % arg2
+
+ @staticmethod
+ def power(arg1, arg2):
+ """ Raise the first argument to the power given by the second """
+ return arg1 ** arg2
+
+ @staticmethod
+ def square_root(arg1):
+ """ Return the square root of the argument. If it is a negative
+ number, raise a NegativeRootError. """
+ if arg1 < 0:
+ raise NegativeRootError(neg_value=arg1)
+ return sqrt(arg1)
+
+ @staticmethod
+ def and_(arg1, arg2):
+ """ Logcially conjoin the two arguments (using short-circuting) """
+ return arg1 and arg2
+
+ @staticmethod
+ def or_(arg1, arg2):
+ """ Logically disjoin the two arguments (using short-circuting) """
+ return arg1 or arg2
+
+ @staticmethod
+ def not_(arg):
+ """ Return True if the argument evaluates to False, and False
+ otherwise. """
+ return not arg
+
+ @staticmethod
+ def equals(arg1, arg2):
+ """ Return arg1 == arg2 """
+ return arg1 == arg2
+
+ @staticmethod
+ def less(arg1, arg2):
+ """ Return arg1 < arg2 """
+ return arg1 < arg2
+
+ @staticmethod
+ def greater(arg1, arg2):
+ """ Return arg1 > arg2 """
+ return arg1 > arg2
+
+ @staticmethod
+ def comment(text):
+ """In 'snail' execution mode, display the comment. Else, do
+ nothing."""
+ tw = global_objects["window"]
+ if not tw.hide and tw.step_time != 0:
+ tw.showlabel('print', text)
+
+ @staticmethod
+ def random_int(lower, upper):
+ """ Choose a random integer between lower and upper, which must be
+ integers """
+ return int(round(uniform(lower, upper), 0))
+
+ @staticmethod
+ def random_char(lower, upper):
+ """ Choose a random Unicode code point between lower and upper,
+ which must be integers """
+ return chr(Primitive.random_int(lower, upper))
+
+
+class Disjunction(tuple):
+ """ Abstract disjunction class (not to be instantiated directly) """
+
+ def __init__(self, iterable):
+ self = tuple(iterable)
+
+ def __repr__(self):
+ s = ["("]
+ for disj in self:
+ s.append(repr(disj))
+ s.append(" or ")
+ s.pop()
+ s.append(")")
+ return "".join(s)
+
+ def get_alternatives(self):
+ """ Return a tuple of alternatives, i.e. self """
+ return self
+
+
+class PrimitiveDisjunction(Disjunction, Primitive):
+ """ Disjunction of two or more Primitives. PrimitiveDisjunctions may not
+ be nested. """
+
+ @property
+ def return_type(self):
+ """ Tuple of the return_types of all disjuncts """
+ return TypeDisjunction((prim.return_type for prim in self))
+
+ def __call__(self, *runtime_args, **runtime_kwargs):
+ """ Loop over the disjunct Primitives and try to fill their slots
+ with the given args and kwargs. Call the first Primitives whose
+ slots could be filled successfully. If all disjunct Primitives
+ fail, raise the last error that occurred. """
+
+ # remove the first argument if it is a LogoCode instance
+ if runtime_args and isinstance(runtime_args[0], LogoCode):
+ runtime_args = runtime_args[1:]
+
+ error = None
+ for prim in self:
+ try:
+ new_prim = prim.fill_slots(runtime_args, runtime_kwargs,
+ convert_to_ast=False)
+ except TATypeError as error:
+ # on failure, try the next one
+ continue
+ else:
+ # on success, call this Primitive
+ return new_prim()
+
+ # if we get here, all disjuncts failed
+ if error is not None:
+ raise error
+
+
+class ArgListDisjunction(Disjunction):
+ """ Disjunction of two or more argument lists """
+ pass
+
+
+class ArgSlot(object):
+ """ Description of the requirements that a Primitive demands from an
+ argument or keyword argument. An ArgSlot is filled at runtime, based
+ on the block program structure. """
+
+ def __init__(self, type_, call_arg=True, wrapper=None):
+ """
+ type_ -- what type of the type hierarchy the argument should have
+ (after the wrapper has been applied)
+ call_arg -- if this argument is callable, should it be called and
+ its return value passed to the parent Primitive (True, the
+ default), or should it be passed as it is (False)?
+ wrapper -- a Primitive that is 'wrapped around' the argument before
+ it gets passed to its parent Primitive. Wrappers can be nested
+ infinitely. """
+ self.type = type_
+ self.call_arg = call_arg
+ self.wrapper = wrapper
+
+ def __repr__(self):
+ s = ["ArgSlot(type="]
+ s.append(repr(self.type))
+ if not self.call_arg:
+ s.append(", call=")
+ s.append(repr(self.call_arg))
+ if self.wrapper is not None:
+ s.append(", wrapper=")
+ s.append(repr(self.wrapper))
+ s.append(")")
+ return "".join(s)
+
+ def get_alternatives(self):
+ """ Return a tuple of slot alternatives, i.e. (self, ) """
+ return (self, )
+
+ def fill(self, argument, convert_to_ast=False, call_my_args=True):
+ """ Try to fill this argument slot with the given argument. Return
+ a ConstantArg containing the result. If there is a type problem,
+ raise a TATypeError. """
+ if isinstance(argument, ast.AST):
+ convert_to_ast = True
+
+ # 1. can the argument be called?
+ (func_disjunction, args) = (None, [])
+ if (isinstance(argument, tuple) and argument
+ and callable(argument[0])):
+ func_disjunction = argument[0]
+ if len(argument) >= 2 and isinstance(argument[1], LogoCode):
+ args = argument[2:]
+ else:
+ args = argument[1:]
+ elif callable(argument):
+ func_disjunction = argument
+
+ # make sure we can loop over func_disjunction
+ if not isinstance(func_disjunction, PrimitiveDisjunction):
+ func_disjunction = PrimitiveDisjunction((func_disjunction, ))
+
+ error = None
+ bad_value = argument # the value that caused the TATypeError
+ for func in func_disjunction:
+ error = None
+ for slot in self.get_alternatives():
+
+ if isinstance(slot.wrapper, PrimitiveDisjunction):
+ wrapper_disjunction = slot.wrapper
+ else:
+ wrapper_disjunction = PrimitiveDisjunction((slot.wrapper,))
+
+ for wrapper in wrapper_disjunction:
+
+ # check if the argument can fill this slot (type-wise)
+ # (lambda functions are always accepted)
+ if getattr(func, '__name__', None) == '<lambda>':
+ converter = identity
+ old_type = TYPE_OBJECT
+ new_type = slot.type
+ else:
+ if wrapper is not None:
+ arg_types = get_type(wrapper)[0]
+ bad_value = wrapper
+ elif func is not None:
+ arg_types = get_type(func)[0]
+ bad_value = func
+ else:
+ arg_types = get_type(argument)[0]
+ bad_value = argument
+ converter = None
+ if not isinstance(arg_types, TypeDisjunction):
+ arg_types = TypeDisjunction((arg_types, ))
+ if isinstance(slot.type, TypeDisjunction):
+ slot_types = slot.type
+ else:
+ slot_types = TypeDisjunction((slot.type, ))
+ for old_type in arg_types:
+ for new_type in slot_types:
+ converter = get_converter(old_type, new_type)
+ if converter is not None:
+ break
+ if converter is not None:
+ break
+ # unable to convert, try next wrapper/ slot/ func
+ if converter is None:
+ continue
+
+ # 1. (cont'd) call the argument or pass it on as a callable
+ called_argument = argument
+ if func is not None:
+ func_prim = func
+ if not isinstance(func_prim, Primitive):
+ func_prim = Primitive(
+ func_prim,
+ [ArgSlot(TYPE_OBJECT)] * len(args))
+ try:
+ func_prim = func_prim.fill_slots(
+ args,
+ convert_to_ast=convert_to_ast,
+ call_my_args=(slot.call_arg and call_my_args))
+ except TATypeError as error:
+ if Primitive._DEBUG:
+ traceback.print_exc()
+ # on failure, try next wrapper/ slot/ func
+ bad_value = error.bad_value
+ continue
+ if convert_to_ast:
+ called_argument = func_prim.get_ast()
+ else:
+ if slot.call_arg and call_my_args:
+ # call and pass on the return value
+ called_argument = func_prim()
+ else:
+ # don't call and pass on the callable
+ called_argument = func_prim
+
+ # 2. apply any wrappers
+ wrapped_argument = called_argument
+ if wrapper is not None:
+ if convert_to_ast:
+ if not hasattr(wrapper, "get_ast"):
+ raise PyExportError(
+ ("cannot convert callable"
+ " %s to an AST") % (repr(wrapper)))
+ wrapped_argument = wrapper.get_ast(
+ called_argument)
+ else:
+ if slot.call_arg and call_my_args:
+ wrapped_argument = wrapper(called_argument)
+ else:
+ wrapped_argument = wrapper.fill_slots(
+ [called_argument], call_my_args=False)
+
+ # last chance to convert raw values to ASTs
+ # (but not lists of ASTs)
+ if (convert_to_ast and
+ not isinstance(wrapped_argument, ast.AST) and
+ not (isinstance(wrapped_argument, list) and
+ wrapped_argument and
+ isinstance(wrapped_argument[0], ast.AST))):
+ wrapped_argument = value_to_ast(wrapped_argument)
+
+ # 3. check the type and convert the argument if necessary
+ converted_argument = wrapped_argument
+ if slot.call_arg and call_my_args:
+ try:
+ converted_argument = convert(
+ wrapped_argument,
+ new_type, old_type=old_type,
+ converter=converter)
+ except TATypeError as error:
+ if Primitive._DEBUG:
+ traceback.print_exc()
+ # on failure, try next wrapper/ slot/ func
+ bad_value = wrapped_argument
+ continue
+ elif converter != identity:
+ converted_argument = Primitive(
+ converter,
+ return_type=new_type,
+ arg_descs=[ConstantArg(wrapped_argument,
+ value_type=old_type,
+ call_arg=False)])
+ # on success, return the result
+ return ConstantArg(
+ converted_argument,
+ value_type=new_type,
+ call_arg=(slot.call_arg and call_my_args))
+
+ # if we haven't returned anything yet, then all alternatives failed
+ if error is not None:
+ raise error
+ else:
+ raise TATypeError(bad_value=bad_value, bad_type=old_type,
+ req_type=new_type)
+
+
+class ArgSlotDisjunction(Disjunction, ArgSlot):
+ """ Disjunction of two or more argument slots """
+ pass
+
+
+class ConstantArg(object):
+ """ A constant argument or keyword argument to a Primitive. It is
+ independent of the block program structure. """
+
+ def __init__(self, value, call_arg=True, value_type=None):
+ """ call_arg -- call the value before returning it?
+ value_type -- the type of the value (from the TA type system). This
+ is useful to store e.g., the return type of call ASTs. """
+ self.value = value
+ self.call_arg = call_arg
+ self.value_type = value_type
+
+ def get(self, convert_to_ast=False):
+ """ If call_arg is True and the value is callable, call the value
+ and return its return value. Else, return the value unchanged.
+ convert_to_ast -- return the equivalent AST instead of a raw value """
+ if self.call_arg and callable(self.value):
+ if convert_to_ast:
+ return value_to_ast(self.value)
+ else:
+ return self.value()
+ else:
+ if convert_to_ast and not isinstance(self.value, list):
+ return value_to_ast(self.value)
+ else:
+ return self.value
+
+ def get_value_type(self):
+ """ If this ConstantArg has stored the type of its value, return
+ that. Else, use get_type(...) to guess the type of the value. """
+ if self.value_type is None:
+ return get_type(self.value)[0]
+ else:
+ return self.value_type
+
+ def __repr__(self):
+ s = ["ConstantArg("]
+ s.append(repr(self.value))
+ if not self.call_arg:
+ s.append(", call=")
+ s.append(repr(self.call_arg))
+ s.append(")")
+ return "".join(s)
+
+
+def or_(*disjuncts):
+ """ Return a disjunction object of the same type as the disjuncts. If
+ the item type cannot be linked to a Disjunction class, return a tuple
+ of the disjuncts. """
+ if isinstance(disjuncts[0], Primitive):
+ return PrimitiveDisjunction(disjuncts)
+ elif isinstance(disjuncts[0], (list, ArgListDisjunction)):
+ return ArgListDisjunction(disjuncts)
+ elif isinstance(disjuncts[0], ArgSlot):
+ return ArgSlotDisjunction(disjuncts)
+ elif isinstance(disjuncts[0], Type):
+ return TypeDisjunction(disjuncts)
+ else:
+ return tuple(disjuncts)
+
+
+def value_to_ast(value, *args_for_prim, **kwargs_for_prim):
+ """ Turn a value into an AST. Supported types: Primitive, int, float,
+ bool, basestring, list
+ If the value is already an AST, return it unchanged.
+ If the value is a non-exportable Primitive, return None. """
+ # already an AST
+ if isinstance(value, ast.AST):
+ return value
+ # Primitive
+ elif isinstance(value, Primitive):
+ if value.export_me:
+ return value.get_ast(*args_for_prim, **kwargs_for_prim)
+ else:
+ return None
+ # boolean
+ elif isinstance(value, bool):
+ return ast.Name(id=str(value), ctx=ast.Load)
+ # number
+ elif isinstance(value, (int, float)):
+ return ast.Num(n=value)
+ # string
+ elif isinstance(value, basestring):
+ return ast.Str(value)
+ # list (recursively transform to an AST)
+ elif isinstance(value, list):
+ ast_list = []
+ for item in value:
+ item_ast = value_to_ast(item)
+ if item_ast is not None:
+ ast_list.append(item_ast)
+ return ast.List(elts=ast_list, ctx=ast.Load)
+ # color
+ elif isinstance(value, Color):
+ # call to the Color constructor with this object's values,
+ # e.g., Color('red', 0, 50, 100)
+ return get_call_ast('Color', [value.name, value.color,
+ value.shade, value.gray],
+ return_type=TYPE_COLOR)
+ # media
+ elif isinstance(value, Media):
+ args = [value_to_ast(value.type), value_to_ast(value.value)]
+ return get_call_ast('Media', args, return_type=TYPE_MEDIA)
+ # unknown
+ else:
+ raise PyExportError("unknown type of raw value: " + repr(type(value)))
+
+
+def ast_yield_true():
+ return ast.Yield(value=ast.Name(id='True', ctx=ast.Load))
+
+
+def export_me(something):
+ """ Return True iff this is not a Primitive or its export_me attribute
+ is True, i.e. everything is exportable except for Primitives with
+ export_me == False """
+ return not isinstance(something, Primitive) or something.export_me
diff --git a/TurtleArt/taturtle.py b/TurtleArt/taturtle.py
index ac72bdb..c35125b 100644
--- a/TurtleArt/taturtle.py
+++ b/TurtleArt/taturtle.py
@@ -28,7 +28,7 @@ import cairo
from random import uniform
from math import sin, cos, pi, sqrt
from taconstants import (TURTLE_LAYER, DEFAULT_TURTLE_COLORS, DEFAULT_TURTLE,
- COLORDICT)
+ Color)
from tasprite_factory import SVG, svg_str_to_pixbuf
from tacanvas import wrap100, COLOR_TABLE
from sprites import Sprite
@@ -347,12 +347,7 @@ class Turtle:
def set_heading(self, heading, share=True):
''' Set the turtle heading (one shape per 360/SHAPES degrees) '''
- try:
- self._heading = heading
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ self._heading = heading
self._heading %= 360
self._update_sprite_heading()
@@ -373,24 +368,18 @@ class Turtle:
def set_color(self, color=None, share=True):
''' Set the pen color for this turtle. '''
+ if color is None:
+ color = self._pen_color
# Special case for color blocks
- if color is not None and color in COLORDICT:
- self.set_shade(COLORDICT[color][1], share)
- self.set_gray(COLORDICT[color][2], share)
- if COLORDICT[color][0] is not None:
- self.set_color(COLORDICT[color][0], share)
- color = COLORDICT[color][0]
+ elif isinstance(color, Color):
+ self.set_shade(color.shade, share)
+ self.set_gray(color.gray, share)
+ if color.color is not None:
+ color = color.color
else:
color = self._pen_color
- elif color is None:
- color = self._pen_color
- try:
- self._pen_color = color
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ self._pen_color = color
self._turtles.turtle_window.canvas.set_fgcolor(shade=self._pen_shade,
gray=self._pen_gray,
@@ -404,12 +393,7 @@ class Turtle:
def set_gray(self, gray=None, share=True):
''' Set the pen gray level for this turtle. '''
if gray is not None:
- try:
- self._pen_gray = gray
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ self._pen_gray = gray
if self._pen_gray < 0:
self._pen_gray = 0
@@ -428,12 +412,7 @@ class Turtle:
def set_shade(self, shade=None, share=True):
''' Set the pen shade for this turtle. '''
if shade is not None:
- try:
- self._pen_shade = shade
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ self._pen_shade = shade
self._turtles.turtle_window.canvas.set_fgcolor(shade=self._pen_shade,
gray=self._pen_gray,
@@ -447,12 +426,7 @@ class Turtle:
def set_pen_size(self, pen_size=None, share=True):
''' Set the pen size for this turtle. '''
if pen_size is not None:
- try:
- self._pen_size = max(0, pen_size)
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ self._pen_size = max(0, pen_size)
self._turtles.turtle_window.canvas.set_pen_size(
self._pen_size * self._turtles.turtle_window.coord_scale)
@@ -547,12 +521,7 @@ class Turtle:
def right(self, degrees, share=True):
''' Rotate turtle clockwise '''
- try:
- self._heading += degrees
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ self._heading += degrees
self._heading %= 360
self._update_sprite_heading()
@@ -562,6 +531,10 @@ class Turtle:
round_int(self._heading)]))
self._turtles.turtle_window.send_event(event)
+ def left(self, degrees, share=True):
+ degrees = 0 - degrees
+ self.right(degrees, share)
+
def _draw_line(self, old, new, pendown):
if self._pen_state and pendown:
self._turtles.turtle_window.canvas.set_source_rgb()
@@ -578,13 +551,8 @@ class Turtle:
scaled_distance = distance * self._turtles.turtle_window.coord_scale
old = self.get_xy()
- try:
- xcor = old[0] + scaled_distance * sin(self._heading * DEGTOR)
- ycor = old[1] + scaled_distance * cos(self._heading * DEGTOR)
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ xcor = old[0] + scaled_distance * sin(self._heading * DEGTOR)
+ ycor = old[1] + scaled_distance * cos(self._heading * DEGTOR)
self._draw_line(old, (xcor, ycor), True)
self.move_turtle((xcor, ycor))
@@ -594,19 +562,18 @@ class Turtle:
int(distance)]))
self._turtles.turtle_window.send_event(event)
+ def backward(self, distance, share=True):
+ distance = 0 - distance
+ self.forward(distance, share)
+
def set_xy(self, x, y, share=True, pendown=True, dragging=False):
old = self.get_xy()
- try:
- if dragging:
- xcor = x
- ycor = y
- else:
- xcor = x * self._turtles.turtle_window.coord_scale
- ycor = y * self._turtles.turtle_window.coord_scale
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ if dragging:
+ xcor = x
+ ycor = y
+ else:
+ xcor = x * self._turtles.turtle_window.coord_scale
+ ycor = y * self._turtles.turtle_window.coord_scale
self._draw_line(old, (xcor, ycor), pendown)
self.move_turtle((xcor, ycor))
@@ -621,15 +588,10 @@ class Turtle:
''' Draw an arc '''
if self._pen_state:
self._turtles.turtle_window.canvas.set_source_rgb()
- try:
- if a < 0:
- pos = self.larc(-a, r)
- else:
- pos = self.rarc(a, r)
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ if a < 0:
+ pos = self.larc(-a, r)
+ else:
+ pos = self.rarc(a, r)
self.move_turtle(pos)
@@ -736,6 +698,19 @@ class Turtle:
round_int(w)]]))
self._turtles.turtle_window.send_event(event)
+ 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.get_pixel()
+ self._turtles.turtle_window.lc.heap.append(b)
+ self._turtles.turtle_window.lc.heap.append(g)
+ self._turtles.turtle_window.lc.heap.append(r)
+
+ def get_color_index(self):
+ r, g, b, a = self.get_pixel()
+ color_index = self._turtles.turtle_window.canvas.get_color_index(
+ r, g, b)
+ return color_index
+
def get_name(self):
return self._name
diff --git a/TurtleArt/tatype.py b/TurtleArt/tatype.py
new file mode 100644
index 0000000..3ca47b9
--- /dev/null
+++ b/TurtleArt/tatype.py
@@ -0,0 +1,443 @@
+#Copyright (c) 2013 Marion Zepf
+
+#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.
+
+""" type system for Primitives and their arguments """
+
+import ast
+
+from tablock import Media
+from taconstants import (Color, CONSTANTS)
+
+
+class Type(object):
+ """ A type in the type hierarchy. """
+
+ def __init__(self, constant_name, value):
+ """ constant_name -- the name of the constant that points to this Type
+ object
+ value -- an arbitrary integer that is different from the values of
+ all other Types. The order of the integers doesn't matter. """
+ self.constant_name = constant_name
+ self.value = value
+
+ def __eq__(self, other):
+ if other is None:
+ return False
+ if not isinstance(other, Type):
+ return False
+ return self.value == other.value
+
+ def __str__(self):
+ return str(self.constant_name)
+ __repr__ = __str__
+
+
+class TypeDisjunction(tuple,Type):
+ """ Disjunction of two or more Types (from the type hierarchy) """
+
+ def __init__(self, iterable):
+ self = tuple(iterable)
+
+ def __str__(self):
+ s = ["("]
+ for disj in self:
+ s.append(str(disj))
+ s.append(" or ")
+ s.pop()
+ s.append(")")
+ return "".join(s)
+
+
+# individual types
+TYPE_OBJECT = Type('TYPE_OBJECT', 0)
+TYPE_BOOL = Type('TYPE_BOOL', 5)
+TYPE_BOX = Type('TYPE_BOX', 8) # special type for the unknown content of a box
+TYPE_CHAR = Type('TYPE_CHAR', 1)
+TYPE_COLOR = Type('TYPE_COLOR', 2)
+TYPE_FLOAT = Type('TYPE_FLOAT', 3)
+TYPE_INT = Type('TYPE_INT', 4)
+TYPE_MEDIA = Type('TYPE_MEDIA', 10)
+TYPE_NUMBER = Type('TYPE_NUMBER', 6) # shortcut to avoid a TypeDisjunction
+ # between TYPE_FLOAT and TYPE_INT
+TYPE_NUMERIC_STRING = Type('TYPE_NUMERIC_STRING', 7)
+TYPE_STRING = Type('TYPE_STRING', 9)
+
+# groups/ classes of types
+TYPES_NUMERIC = (TYPE_FLOAT, TYPE_INT, TYPE_NUMBER)
+
+
+BOX_AST = ast.Name(id='BOX', ctx=ast.Load)
+ACTION_AST = ast.Name(id='ACTION', ctx=ast.Load)
+
+
+def get_type(x):
+ """ Return the most specific type in the type hierarchy that applies to x
+ and a boolean indicating whether x is an AST. If the type cannot be
+ determined, return TYPE_OBJECT as the type. """
+ # non-AST types
+ if isinstance(x, (int, long)):
+ return (TYPE_INT, False)
+ elif isinstance(x, float):
+ return (TYPE_FLOAT, False)
+ elif isinstance(x, basestring):
+ if len(x) == 1:
+ return (TYPE_CHAR, False)
+ try:
+ float(x)
+ except ValueError:
+ return (TYPE_STRING, False)
+ else:
+ return (TYPE_NUMERIC_STRING, False)
+ elif isinstance(x, Color):
+ return (TYPE_COLOR, False)
+ elif isinstance(x, Media):
+ return (TYPE_MEDIA, False)
+ elif hasattr(x, "return_type"):
+ return (x.return_type, False)
+
+ # AST types
+ elif isinstance(x, ast.Num):
+ return (get_type(x.n)[0], True)
+ elif isinstance(x, ast.Str):
+ return (get_type(x.s)[0], True)
+ elif isinstance(x, ast.Name):
+ try:
+ # we need to have imported CONSTANTS for this to work
+ value = eval(x.id)
+ except NameError:
+ return (TYPE_OBJECT, True)
+ else:
+ return (get_type(value)[0], True)
+ elif isinstance(x, ast.Subscript):
+ if x.value == BOX_AST:
+ return (TYPE_BOX, True)
+ elif isinstance(x, ast.Call):
+ if isinstance(x.func, ast.Name):
+ if x.func.id == 'float':
+ return (TYPE_FLOAT, True)
+ elif x.func.id in ('int', 'ord'):
+ return (TYPE_INT, True)
+ elif x.func.id == 'chr':
+ return (TYPE_CHAR, True)
+ elif x.func.id in ('repr', 'str', 'unicode'):
+ return (TYPE_STRING, True)
+ elif x.func.id == 'Color':
+ return (TYPE_COLOR, True)
+ elif x.func.id == 'Media':
+ return (TYPE_MEDIA, True)
+ # unary operands never change the type of their argument
+ elif isinstance(x, ast.UnaryOp):
+ if issubclass(x.op, ast.Not):
+ # 'not' always returns a boolean
+ return (TYPE_BOOL, True)
+ else:
+ return get_type(x.operand)
+ # boolean and comparison operators always return a boolean
+ if isinstance(x, (ast.BoolOp, ast.Compare)):
+ return (TYPE_BOOL, True)
+ # other binary operators
+ elif isinstance(x, ast.BinOp):
+ type_left = get_type(x.left)[0]
+ type_right = get_type(x.right)[0]
+ if type_left == TYPE_STRING or type_right == TYPE_STRING:
+ return (TYPE_STRING, True)
+ if type_left == type_right == TYPE_INT:
+ return (TYPE_INT, True)
+ else:
+ return (TYPE_FLOAT, True)
+
+ return (TYPE_OBJECT, isinstance(x, ast.AST))
+
+
+def is_instancemethod(method):
+ # TODO how to access the type `instancemethod` directly?
+ return type(method).__name__ == "instancemethod"
+
+def is_bound_method(method):
+ return ((is_instancemethod(method) and method.im_self is not None) or
+ (hasattr(method, '__self__') and method.__self__ is not None))
+
+def is_staticmethod(method):
+ # TODO how to access the type `staticmethod` directly?
+ return type(method).__name__ == "staticmethod"
+
+
+def identity(x):
+ return x
+
+TYPE_CONVERTERS = {
+ # Type hierarchy: If there is a converter A -> B, then A is a subtype of B.
+ # The converter from A to B is stored under TYPE_CONVERTERS[A][B].
+ # The relation describing the type hierarchy must be transitive, i.e.
+ # converting A -> C must yield the same result as converting A -> B -> C.
+ # TYPE_OBJECT is the supertype of everything.
+ TYPE_BOX: {
+ TYPE_FLOAT: float,
+ TYPE_INT: int,
+ TYPE_NUMBER: float,
+ TYPE_STRING: str},
+ TYPE_CHAR: {
+ TYPE_INT: ord,
+ TYPE_STRING: identity},
+ TYPE_COLOR: {
+ TYPE_FLOAT: float,
+ TYPE_INT: int,
+ TYPE_NUMBER: int,
+ TYPE_STRING: Color.get_number_string},
+ TYPE_FLOAT: {
+ TYPE_INT: int,
+ TYPE_NUMBER: identity,
+ TYPE_STRING: str},
+ TYPE_INT: {
+ TYPE_FLOAT: float,
+ TYPE_NUMBER: identity,
+ TYPE_STRING: str},
+ TYPE_NUMBER: {
+ TYPE_FLOAT: float,
+ TYPE_INT: int,
+ TYPE_STRING: str},
+ TYPE_NUMERIC_STRING: {
+ TYPE_FLOAT: float,
+ TYPE_STRING: identity}
+}
+
+
+class TATypeError(BaseException):
+ """ TypeError with the types from the hierarchy, not with Python types """
+
+ def __init__(self, bad_value, bad_type=None, req_type=None, message=''):
+ """ bad_value -- the mis-typed value that caused the error
+ bad_type -- the type of the bad_value
+ req_type -- the type that the value was expected to have
+ message -- short statement about the cause of the error. It is
+ not shown to the user, but may appear in debugging output. """
+ self.bad_value = bad_value
+ self.bad_type = bad_type
+ self.req_type = req_type
+ self.message = message
+
+ def __str__(self):
+ msg = []
+ if self.message:
+ msg.append(self.message)
+ msg.append(" (")
+ msg.append("bad value: ")
+ msg.append(repr(self.bad_value))
+ if self.bad_type is not None:
+ msg.append(", bad type: ")
+ msg.append(repr(self.bad_type))
+ if self.req_type is not None:
+ msg.append(", req type: ")
+ msg.append(repr(self.req_type))
+ if self.message:
+ msg.append(")")
+ return "".join(msg)
+ __repr__ = __str__
+
+
+def get_converter(old_type, new_type):
+ """ If there is a converter old_type -> new_type, return it. Else return
+ None. If a chain of converters is necessary, return it as a tuple or
+ list (starting with the innermost, first-to-apply converter). """
+ # every type can be converted to TYPE_OBJECT
+ if new_type == TYPE_OBJECT:
+ return identity
+ # every type can be converted to itself
+ if old_type == new_type:
+ return identity
+
+ # is there a converter for this pair of types?
+ converters_from_old = TYPE_CONVERTERS.get(old_type)
+ if converters_from_old is None:
+ return None
+ converter = converters_from_old.get(new_type)
+ if converter is not None:
+ return converter
+ else:
+ # form the transitive closure of all types that old_type can be
+ # converted to, and look for new_type there
+ backtrace = converters_from_old.copy()
+ new_backtrace = backtrace.copy()
+ break_all = False
+ while True:
+ newest_backtrace = {}
+ for t in new_backtrace:
+ for new_t in TYPE_CONVERTERS.get(t, {}):
+ if new_t not in backtrace:
+ newest_backtrace[new_t] = t
+ backtrace[new_t] = t
+ if new_t == new_type:
+ break_all = True
+ break
+ if break_all:
+ break
+ if break_all or not newest_backtrace:
+ break
+ new_backtrace = newest_backtrace
+ # use the backtrace to find the path from old_type to new_type
+ if new_type in backtrace:
+ converter_chain = []
+ t = new_type
+ while t in backtrace and isinstance(backtrace[t], Type):
+ converter_chain.insert(0, TYPE_CONVERTERS[backtrace[t]][t])
+ t = backtrace[t]
+ converter_chain.insert(0, TYPE_CONVERTERS[old_type][t])
+ return converter_chain
+ return None
+
+
+def convert(x, new_type, old_type=None, converter=None):
+ """ Convert x to the new type if possible.
+ old_type -- the type of x. If not given, it is computed. """
+ if not isinstance(new_type, Type):
+ raise ValueError('%s is not a type in the type hierarchy'
+ % (repr(new_type)))
+ # every type can be converted to TYPE_OBJECT
+ if new_type == TYPE_OBJECT:
+ return x
+ if not isinstance(old_type, Type):
+ (old_type, is_an_ast) = get_type(x)
+ else:
+ is_an_ast = isinstance(x, ast.AST)
+ # every type can be converted to itself
+ if old_type == new_type:
+ return x
+
+ # special case: 'box' block (or 'pop' block) as an AST
+ if is_an_ast and old_type == TYPE_BOX:
+ new_type_ast = ast.Name(id=new_type.constant_name)
+ return get_call_ast('convert', [x, new_type_ast], return_type=new_type)
+
+ # if the converter is not given, try to find one
+ if converter is None:
+ converter = get_converter(old_type, new_type)
+ if converter is None:
+ # no converter available
+ raise TATypeError(bad_value=x, bad_type=old_type,
+ req_type=new_type, message=("found no converter"
+ " for this type combination"))
+
+ def _apply_converter(converter, y):
+ try:
+ if is_an_ast:
+ if converter == identity:
+ return y
+ elif is_instancemethod(converter):
+ func = ast.Attribute(value=y,
+ attr=converter.im_func.__name__,
+ ctx=ast.Load)
+ return get_call_ast(func)
+ else:
+ func_name = converter.__name__
+ return get_call_ast(func_name, [y])
+ else:
+ return converter(y)
+ except BaseException:
+ raise TATypeError(bad_value=x, bad_type=old_type,
+ req_type=new_type, message=("error during "
+ "conversion"))
+
+ if isinstance(converter, (list, tuple)):
+ # apply the converter chain recursively
+ result = x
+ for conv in converter:
+ result = _apply_converter(conv, result)
+ return result
+ elif converter is not None:
+ return _apply_converter(converter, x)
+
+
+class TypedAST(ast.AST):
+
+ @property
+ def return_type(self):
+ if self._return_type is None:
+ return get_type(self.func)[0]
+ else:
+ return self._return_type
+
+
+class TypedCall(ast.Call,TypedAST):
+ """ Like a Call AST, but with a return type """
+
+ def __init__(self, func, args=None, keywords=None, starargs=None,
+ kwargs=None, return_type=None):
+
+ if args is None:
+ args = []
+ if keywords is None:
+ keywords = []
+
+ ast.Call.__init__(self, func=func, args=args, keywords=keywords,
+ starargs=starargs, kwargs=kwargs)
+
+ self._return_type = return_type
+
+
+class TypedSubscript(ast.Subscript,TypedAST):
+ """ Like a Subscript AST, but with a type """
+
+ def __init__(self, value, slice_, ctx=ast.Load, return_type=None):
+
+ ast.Subscript.__init__(self, value=value, slice=slice_, ctx=ctx)
+
+ self._return_type = return_type
+
+
+class TypedName(ast.Name,TypedAST):
+ """ Like a Name AST, but with a type """
+
+ def __init__(self, id_, ctx=ast.Load, return_type=None):
+
+ ast.Name.__init__(self, id=id_, ctx=ctx)
+
+ self._return_type = return_type
+
+
+def get_call_ast(func_name, args=None, kwargs=None, return_type=None):
+ """ Return an AST representing the call to a function with the name
+ func_name, passing it the arguments args (given as a list) and the
+ keyword arguments kwargs (given as a dictionary).
+ func_name -- either the name of a callable as a string, or an AST
+ representing a callable expression
+ return_type -- if this is not None, return a TypedCall object with this
+ return type instead """
+ if args is None:
+ args = []
+ # convert keyword argument dict to a list of (key, value) pairs
+ keywords = []
+ if kwargs is not None:
+ for (key, value) in kwargs.iteritems():
+ keywords.append(ast.keyword(arg=key, value=value))
+ # get or generate the AST representing the callable
+ if isinstance(func_name, ast.AST):
+ func_ast = func_name
+ else:
+ func_ast = ast.Name(id=func_name, ctx=ast.Load)
+ # if no return type is given, return a simple Call AST
+ if return_type is None:
+ return ast.Call(func=func_ast, args=args, keywords=keywords,
+ starargs=None, kwargs=None)
+ # if a return type is given, return a TypedCall AST
+ else:
+ return TypedCall(func=func_ast, args=args, keywords=keywords,
+ return_type=return_type)
+
+
diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py
index b0aa368..ff30b19 100644
--- a/TurtleArt/tautils.py
+++ b/TurtleArt/tautils.py
@@ -51,7 +51,7 @@ except (ImportError, AttributeError):
from StringIO import StringIO
from taconstants import (HIT_HIDE, HIT_SHOW, XO1, XO15, XO175, XO4, UNKNOWN,
- MAGICNUMBER, SUFFIX)
+ MAGICNUMBER, SUFFIX, ARG_MUST_BE_NUMBER)
import logging
_logger = logging.getLogger('turtleart-activity')
@@ -107,7 +107,7 @@ def chr_to_ord(x):
''' Try to comvert a string to an ord '''
if strtype(x) and len(x) == 1:
try:
- return ord(x[0]), True
+ return ord(x), True
except ValueError:
return x, False
return x, False
@@ -115,9 +115,7 @@ def chr_to_ord(x):
def strtype(x):
''' Is x a string type? '''
- if isinstance(x, (str, unicode)):
- return True
- return False
+ return isinstance(x, basestring)
def increment_name(name):
@@ -311,7 +309,7 @@ def get_save_name(filefilter, load_save_folder, save_file_name):
gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_SAVE, gtk.RESPONSE_OK))
dialog.set_default_response(gtk.RESPONSE_OK)
- if filefilter in ['.png', '.svg', '.lg']:
+ if filefilter in ['.png', '.svg', '.lg', '.py']:
suffix = filefilter
else:
suffix = SUFFIX[1]
@@ -679,14 +677,11 @@ def arithmetic_check(blk1, blk2, dock1, dock2):
return False
if dock1 == 2 and zero_arg(blk2.values[0]):
return False
- elif blk1.name \
- in ['product2', 'minus2', 'random', 'remainder2', 'string'] and \
- blk2.name \
- in ['product2', 'minus2', 'random', 'remainder2', 'string']:
+ elif blk1.name in ARG_MUST_BE_NUMBER and blk2.name in ARG_MUST_BE_NUMBER:
if blk1.name == 'string':
if not numeric_arg(blk1.values[0]):
return False
- elif blk1.name == 'string':
+ elif blk2.name == 'string':
if not numeric_arg(blk2.values[0]):
return False
elif blk1.name in ['greater2', 'less2'] and blk2.name == 'string':
@@ -808,6 +803,30 @@ def find_blk_below(blk, namelist):
return None
+def get_stack_name(blk):
+ ''' Return the name of the action stack that the given block belongs to.
+ If the top block of this stack is not a stack-defining block, return
+ None. '''
+ top_block = find_top_block(blk)
+ if top_block.name == 'start':
+ return 'start'
+ elif top_block.name == 'hat1':
+ return 'stack1'
+ elif top_block.name == 'hat2':
+ return 'stack2'
+ elif top_block.name == 'hat':
+ try:
+ return str(top_block.connections[1].values[0])
+ except (AttributeError, TypeError, IndexError):
+ # AttributeError: t_b has no attribute 'connections' or t_b.c[1]
+ # has no attribute 'value'
+ # TypeError: t_b.c or t_b.c[1].v is not a subscriptable sequence
+ # IndexError: t_b.c or t_b.c[1].v is too short
+ return None
+ else:
+ return None
+
+
def get_hardware():
''' Determine whether we are using XO 1.0, 1.5, ... or 'unknown'
hardware '''
diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py
index 5072ab2..10867dc 100644
--- a/TurtleArt/tawindow.py
+++ b/TurtleArt/tawindow.py
@@ -58,16 +58,17 @@ from taconstants import (HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE,
PYTHON_SKIN, PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK,
EXPANDABLE_ARGS, XO1, XO15, XO175, XO30, XO4, TITLEXY,
CONTENT_ARGS, CONSTANTS, EXPAND_SKIN, PROTO_LAYER,
- EXPANDABLE_FLOW, SUFFIX, TMP_SVG_PATH)
+ EXPANDABLE_FLOW, SUFFIX, TMP_SVG_PATH, Color,
+ KEY_DICT)
from tapalette import (palette_names, palette_blocks, expandable_blocks,
block_names, content_blocks, default_values,
special_names, block_styles, help_strings,
hidden_proto_blocks, string_or_number_args,
make_palette, palette_name_to_index,
- palette_init_on_start)
-from talogo import (LogoCode, primitive_dictionary, logoerror)
+ palette_init_on_start, palette_i18n_names)
+from talogo import (LogoCode, logoerror)
from tacanvas import TurtleGraphics
-from tablock import (Blocks, Block)
+from tablock import (Blocks, Block, Media, media_blocks_dictionary)
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,
@@ -77,10 +78,11 @@ from tautils import (magnitude, get_load_name, get_save_name, data_from_file,
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, convert, find_hat, find_bot_block,
+ error_output, find_hat, find_bot_block,
restore_clamp, collapse_clamp, data_from_string,
increment_name, get_screen_dpi)
from tasprite_factory import (SVG, svg_str_to_pixbuf, svg_from_file)
+from tapalette import block_primitives
from sprites import (Sprites, Sprite)
if _GST_AVAILABLE:
@@ -95,6 +97,10 @@ _UNFULLSCREEN_VISIBILITY_TIMEOUT = 2
_PLUGIN_SUBPATH = 'plugins'
_MACROS_SUBPATH = 'macros'
+# the global instances of single-instance classes
+global_objects = {}
+plugins_in_use = []
+
class TurtleArtWindow():
''' TurtleArt Window class abstraction '''
@@ -168,6 +174,7 @@ class TurtleArtWindow():
self._autohide_shape = True
self.keypress = ''
self.keyvalue = 0
+ self.keyboard = 0
self._focus_out_id = None
self._insert_text_id = None
self._text_to_check = False
@@ -246,6 +253,7 @@ class TurtleArtWindow():
self.selected_selector = None
self.previous_selector = None
self.selector_shapes = []
+ self._highlighted_blk = None
self.selected_blk = None
self.selected_spr = None
self.selected_turtle = None
@@ -300,12 +308,26 @@ class TurtleArtWindow():
from tabasics import Palettes
self._basic_palettes = Palettes(self)
+ global_objects["window"] = self
+ global_objects["canvas"] = self.canvas
+ global_objects["logo"] = self.lc
+ global_objects["turtles"] = self.turtles
+
if self.interactive_mode:
gobject.idle_add(self._lazy_init)
else:
self._init_plugins()
self._setup_plugins()
+ def get_global_objects(self):
+ return global_objects
+
+ def get_init_complete(self):
+ if self.running_turtleart:
+ return self.activity.init_complete
+ else:
+ return True
+
def _lazy_init(self):
self._init_plugins()
self._setup_plugins()
@@ -325,8 +347,8 @@ class TurtleArtWindow():
regenerate=True,
show=True)
- if self.running_sugar:
- self.activity.check_buttons_for_fit()
+ if self.running_sugar:
+ self.activity.check_buttons_for_fit()
def _set_screen_dpi(self):
dpi = get_screen_dpi()
@@ -379,10 +401,11 @@ class TurtleArtWindow():
# Add the icon dir to the icon_theme search path
self._add_plugin_icon_dir(os.path.join(self._get_plugin_home(),
plugin_dir))
+ # Add the plugin to the list of global objects
+ global_objects[plugin_class] = self.turtleart_plugins[-1]
except Exception as e:
debug_output('Failed to load %s: %s' % (plugin_class, str(e)),
self.running_sugar)
-
def _add_plugin_icon_dir(self, dirname):
''' If there is an icon subdir, add it to the search path. '''
@@ -413,7 +436,7 @@ class TurtleArtWindow():
# If setup fails, remove the plugin from the list
self.turtleart_plugins.remove(plugin)
- def _start_plugins(self):
+ def start_plugins(self):
''' Start is called everytime we execute blocks. '''
for plugin in self.turtleart_plugins:
if hasattr(plugin, 'start'):
@@ -762,6 +785,9 @@ class TurtleArtWindow():
self.draw_overlay('Cartesian')
return
+ def get_coord_scale(self):
+ return self.coord_scale
+
def set_polar(self, flag):
''' Turn on/off polar coordinates '''
self.draw_overlay('polar')
@@ -1477,11 +1503,25 @@ class TurtleArtWindow():
self.button_press(event.get_state() & gtk.gdk.CONTROL_MASK, x, y)
return True
+ def get_mouse_flag(self):
+ return self.mouse_flag
+
+ def get_mouse_button(self):
+ return self.mouse_flag == 1
+
+ def get_mouse_x(self):
+ return int(self.mouse_x - (self.canvas.width / 2))
+
+ def get_mouse_y(self):
+ return int((self.canvas.height / 2) - self.mouse_y)
+
def button_press(self, mask, x, y):
- if self.running_sugar:
- self._show_unfullscreen_button()
+ if self.running_turtleart:
+ if self.running_sugar:
+ self._show_unfullscreen_button()
- self.activity.hide_store()
+ if self.interactive_mode:
+ self.activity.hide_store()
# Find out what was clicked
spr = self.sprite_list.find_sprite((x, y))
@@ -1491,7 +1531,7 @@ class TurtleArtWindow():
blk = self.block_list.spr_to_block(spr)
if blk is not None:
# Make sure stop button is visible
- if self.running_sugar:
+ if self.running_sugar and self.running_turtleart:
self.activity.stop_turtle_button.set_icon("stopiton")
self.activity.stop_turtle_button.set_tooltip(
_('Stop turtle'))
@@ -1562,9 +1602,10 @@ before making changes to your program'))
self.status_spr.hide()
self._autohide_shape = True
- def _look_for_a_blk(self, spr, x, y):
+ def _look_for_a_blk(self, spr, x, y, blk=None):
# From the sprite at x, y, look for a corresponding block
- blk = self.block_list.spr_to_block(spr)
+ if blk is None:
+ blk = self.block_list.spr_to_block(spr)
''' If we were copying and didn't click on a block... '''
if self.copying_blocks or self.sharing_blocks or self.saving_blocks:
if blk is None or blk.type != 'block':
@@ -1586,9 +1627,9 @@ before making changes to your program'))
self._restore_from_trash(find_top_block(blk))
elif blk.type == 'proto':
if self.deleting_blocks:
- if 'my blocks' in palette_names and \
+ if 'myblocks' in palette_names and \
self.selected_palette == \
- palette_names.index('my blocks'):
+ palette_names.index('myblocks'):
self._delete_stack_alert(blk)
self.parent.get_window().set_cursor(
gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
@@ -1797,7 +1838,7 @@ before making changes to your program'))
error_output('Could not remove macro %s: %s' %
(macro_path, e))
return
- i = palette_names.index('my blocks')
+ i = palette_names.index('myblocks')
palette_blocks[i].remove(blk.name)
for pblk in self.palettes[i]:
if pblk.name == blk.name:
@@ -3166,7 +3207,7 @@ before making changes to your program'))
if self.canvas.cr_svg is None:
self.canvas.setup_svg_surface()
self.running_blocks = True
- self._start_plugins() # Let the plugins know we are running.
+ self.start_plugins() # Let the plugins know we are running.
top = find_top_block(blk)
code = self.lc.generate_code(top, self.just_blocks())
self.lc.run_blocks(code)
@@ -3584,15 +3625,67 @@ before making changes to your program'))
elif keyname == 'g':
self._align_to_grid()
- elif self.selected_blk is not None and \
- self.selected_blk.name != 'proto':
- self._process_keyboard_commands(keyname, block_flag=True)
+ elif keyname == 'Tab':
+ # For the first pass, just tab through palettes
+ if self.selected_palette is None:
+ print 'selected palette is None'
+ return True
+ else:
+ p = self.palettes[self.selected_palette]
+ i = 0
+ if self._highlighted_blk is not None:
+ self._highlighted_blk.unhighlight()
+ if self._highlighted_blk in p:
+ i = p.index(self._highlighted_blk)
+ if i == len(p) - 1:
+ i = 0
+ else:
+ i += 1
+ while(not p[i].get_visibility()):
+ i += 1
+ if i == len(p) - 1:
+ i = 0
+ if i < len(p):
+ self._highlighted_blk = p[i]
+ self._highlighted_blk.highlight()
+ self.selected_blk = p[i]
+
+ elif self.selected_blk is not None:
+ if self.selected_blk.type == 'proto':
+ if keyname == 'Return':
+ (x, y) = self.selected_blk.spr.get_xy()
+ if self.orientation == 0:
+ x += 20
+ y += PALETTE_HEIGHT + self.toolbar_offset
+ else:
+ x += PALETTE_WIDTH
+ y += 20
+ self._look_for_a_blk(None, x, y, blk=self.selected_blk)
+ self._unselect_all_blocks()
+ self.selected_spr = None
+ self.drag_group = None
+ else:
+ self._process_keyboard_commands(keyname, block_flag=True)
elif self.turtles.spr_to_turtle(self.selected_spr) is not None:
self._process_keyboard_commands(keyname, block_flag=False)
return True
+ def get_keyboard_input(self):
+ """ Query keyboard and update cached keyboard input """
+ self.window.grab_focus()
+ if len(self.keypress) == 1:
+ self.keyboard = ord(self.keypress[0])
+ elif self.keypress in KEY_DICT:
+ self.keyboard = KEY_DICT[self.keypress]
+ else:
+ self.keyboard = 0
+
+ def get_keyboard(self):
+ """ Return cached keyboard input """
+ return self.keyboard
+
def _process_keyboard_commands(self, keyname, block_flag=True):
''' Use the keyboard to move blocks and turtle '''
mov_dict = {'KP_Up': [0, 20], 'j': [0, 20], 'Up': [0, 20],
@@ -3631,7 +3724,7 @@ before making changes to your program'))
if block is not None:
self.selected_spr = block.spr
block.highlight()
- else:
+ elif blk is not None:
self._jog_block(blk, mov_dict[keyname][0],
mov_dict[keyname][1])
elif not block_flag:
@@ -4349,6 +4442,60 @@ before making changes to your program'))
elif self.interactive_mode:
self.parent.set_title(text)
+ def print_(self, n, flag):
+ """ Print object n to the bar at the bottom of the screen """
+ if flag and (self.hide or self.step_time == 0):
+ return
+
+ # list
+ if isinstance(n, list):
+ heap_as_string = str(self.lc.heap)
+ if len(heap_as_string) > 80:
+ self.showlabel('print', str(self.lc.heap)[0:79] + '…')
+ else:
+ self.showlabel('print', str(self.lc.heap))
+ # color
+ elif isinstance(n, Color):
+ if n.color is None:
+ self.showlabel('print', '%s %d, %s %d' %
+ (_('shade'), n.shade,
+ _('gray'), n.gray))
+ else:
+ self.showlabel('print', '%s %d, %s %d, %s %d' %
+ (_('color'), n.color,
+ _('shade'), n.shade,
+ _('gray'), n.gray))
+ # media
+ elif isinstance(n, Media):
+ if (n.type == 'media' and
+ n.value.lower() not in media_blocks_dictionary):
+ try:
+ if self.running_sugar:
+ from sugar.datastore import datastore
+ try:
+ dsobject = datastore.get(n.value)
+ except:
+ debug_output("Couldn't open %s" % (n.value),
+ self.running_sugar)
+ self.showlabel('print', dsobject.metadata['title'])
+ dsobject.destroy()
+ else:
+ self.showlabel('print', n.value)
+ except IOError:
+ self.showlabel('print', str(n))
+ else:
+ self.showlabel('print', str(n))
+ # string
+ elif isinstance(n, basestring):
+ self.showlabel('print', n)
+ # integer
+ elif isinstance(n, int):
+ self.showlabel('print', n)
+ # other number
+ else:
+ self.showlabel(
+ 'print',
+ str(round_int(n)).replace('.', self.decimal_point))
def showlabel(self, shp, label=''):
''' Display a message on a status block '''
@@ -4371,6 +4518,9 @@ before making changes to your program'))
elif shp[0] == '#':
shp = shp[1:]
label = ''
+ if self.running_sugar and \
+ shp not in ['print', 'status', 'info', 'help']:
+ self.activity.error_list.append(shp)
self.status_spr.set_shape(self.status_shapes[shp])
self.status_spr.set_label_attributes(12.0, rescale=False)
if shp == 'status':
@@ -4620,7 +4770,6 @@ before making changes to your program'))
palette = make_palette('blocks')
# Create a new block prototype.
- primitive_dictionary['stack'] = self._prim_stack
palette.add_block('stack_%s' % (name),
style='basic-style-1arg',
label=name,
@@ -4629,7 +4778,6 @@ before making changes to your program'))
logo_command='action',
default=name,
help_string=_('invokes named action stack'))
- self.lc.def_prim('stack', 1, primitive_dictionary['stack'], True)
# Regenerate the palette, which will now include the new block.
self.show_toolbar_palette(palette_name_to_index('blocks'),
@@ -4649,7 +4797,6 @@ before making changes to your program'))
palette = make_palette('blocks')
# Create a new block prototype.
- primitive_dictionary['box'] = self._prim_box
palette.add_block('box_%s' % (name),
style='number-style-1strarg',
label=name,
@@ -4658,8 +4805,6 @@ before making changes to your program'))
default=name,
logo_command='box',
help_string=_('named variable (numeric value)'))
- self.lc.def_prim('box', 1,
- lambda self, x: primitive_dictionary['box'](x))
# Regenerate the palette, which will now include the new block.
self.show_toolbar_palette(palette_name_to_index('blocks'),
@@ -4679,7 +4824,6 @@ before making changes to your program'))
palette = make_palette('blocks')
# Create a new block prototype.
- primitive_dictionary['setbox'] = self._prim_setbox
palette.add_block('storein_%s' % (name),
style='basic-style-2arg',
label=[_('store in'), name, _('value')],
@@ -4689,52 +4833,11 @@ before making changes to your program'))
default=[name, 100],
help_string=_('stores numeric value in named \
variable'))
- self.lc.def_prim(
- 'storeinbox',
- 2,
- lambda self, x, y: primitive_dictionary['setbox']('box3', x, y))
# Regenerate the palette, which will now include the new block.
self.show_toolbar_palette(palette_name_to_index('blocks'),
regenerate=True)
- def _prim_stack(self, x):
- ''' Process a named stack '''
- if isinstance(convert(x, float, False), float):
- if int(float(x)) == x:
- x = int(x)
- if 'stack3' + str(x) not in self.lc.stacks or \
- self.lc.stacks['stack3' + str(x)] is None:
- raise logoerror('#nostack')
- self.lc.icall(self.lc.evline,
- self.lc.stacks['stack3' + str(x)][:])
- yield True
- self.lc.procstop = False
- self.lc.ireturn()
- yield True
-
- def _prim_box(self, x):
- ''' Retrieve value from named box '''
- if isinstance(convert(x, float, False), float):
- if int(float(x)) == x:
- x = int(x)
- try:
- return self.lc.boxes['box3' + str(x)]
- except KeyError:
- raise logoerror('#emptybox')
-
- def _prim_setbox(self, name, x, val):
- ''' Define value of named box '''
- if x is not None:
- if isinstance(convert(x, float, False), float):
- if int(float(x)) == x:
- x = int(x)
- self.lc.boxes[name + str(x)] = val
- self.lc.update_label_value('box', val, label=x)
- else:
- self.lc.boxes[name] = val
- self.lc.update_label_value(name, val)
-
def dock_dx_dy(self, block1, dock1n, block2, dock2n):
''' Find the distance between the dock points of two blocks. '''
# Cannot dock a block to itself
@@ -4786,3 +4889,100 @@ variable'))
(b1x, b1y) = block1.spr.get_xy()
(b2x, b2y) = block2.spr.get_xy()
return ((b1x + d1x) - (b2x + d2x), (b1y + d1y) - (b2y + d2y))
+
+ def prim_load_block(self, *args):
+ ''' Load a block on to the canvas '''
+ # Place the block at the active turtle (x, y) and move the turtle
+ # into position to place the next block in the stack.
+ # TODO: Add expandable argument, media block arguments
+ name = args[0]
+ pos = self.turtles.get_active_turtle().get_xy()
+ values = []
+ for i in range(len(args) - 1):
+ values.append(args[i + 1])
+ if len(values) > 0:
+ dy = int(self._find_block(name, pos[0], pos[1], values))
+ else:
+ if name == 'delete':
+ for blk in self.just_blocks():
+ if blk.status == 'load block':
+ blk.type = 'trash'
+ blk.spr.hide()
+ dy = 0
+ else:
+ dy = int(self._find_block(name, pos[0], pos[1]))
+
+ # Reposition turtle to end of flow
+ pos = self.turtles.get_active_turtle().get_xy()
+ pos[1] -= dy
+ self.turtles.get_active_turtle().move_turtle(pos)
+
+ def _make_block(self, name, x, y, defaults):
+ if defaults is None:
+ self._new_block(name, x, y, defaults)
+ else:
+ for i, v in enumerate(defaults):
+ if isinstance(v, float) and int(v) == v:
+ defaults[i] = int(v)
+ self._new_block(name, x, y, defaults)
+
+ # Find the block we just created and attach it to a stack.
+ self.drag_group = None
+ spr = self.sprite_list.find_sprite((x, y))
+ if spr is not None:
+ blk = self.block_list.spr_to_block(spr)
+ if blk is not None:
+ self.drag_group = find_group(blk)
+ for b in self.drag_group:
+ b.status = 'load block'
+ self._snap_to_dock()
+
+ # Disassociate new block from mouse.
+ self.drag_group = None
+ return blk.docks[-1][3]
+
+ def _find_block(self, 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. """
+ x, y = self.turtles.turtle_to_screen_coordinates((x, y))
+ for name in block_names:
+ # Translate label name into block/prim name.
+ if blkname in block_names[name]: # block label is an array
+ # print 'found a match', blkname, name, block_names[name]
+ if name in content_blocks or \
+ (name in block_primitives and
+ block_primitives[name] == name):
+ # print '_make_block', blkname, name
+ return self._make_block(name, x, y, defaults)
+ elif blkname in block_names:
+ # print '_make_block', blkname
+ return self._make_block(blkname, x, y, defaults)
+ for name in special_names:
+ # Translate label name into block/prim name.
+ if blkname in special_names[name]:
+ return self._make_block(name, x, y, defaults)
+ # Check for a macro
+ if blkname in MACROS:
+ self.new_macro(blkname, x, y)
+ return 0 # Fix me: calculate flow position
+ # Block not found
+ raise logoerror("#syntaxerror")
+ return -1
+
+ def prim_load_palette(self, arg):
+ ''' Select a palette '''
+ if type(arg) in [int, float]:
+ if int(arg) < 0 or int(arg) > len(palette_names):
+ raise logoerror("#syntaxerror")
+ else:
+ self.show_toolbar_palette(int(arg))
+ else:
+ if type(arg) == unicode:
+ arg = arg.encode('utf-8')
+ if arg in palette_names or arg in palette_i18n_names:
+ self.show_toolbar_palette(palette_name_to_index(arg))
+ else:
+ raise logoerror("#syntaxerror")
diff --git a/TurtleArtActivity.py b/TurtleArtActivity.py
index 23cecce..e41795d 100644
--- a/TurtleArtActivity.py
+++ b/TurtleArtActivity.py
@@ -41,7 +41,7 @@ except ImportError:
HAS_TOOLBARBOX = False
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.radiotoolbutton import RadioToolButton
-from sugar.graphics.alert import (ConfirmationAlert, NotifyAlert, Alert)
+from sugar.graphics.alert import (ConfirmationAlert, Alert)
from sugar.graphics import style
from sugar.graphics.icon import Icon
from sugar.graphics.xocolor import XoColor
@@ -68,10 +68,12 @@ from TurtleArt.tapalette import (palette_names, help_strings, help_palettes,
from TurtleArt.taconstants import (BLOCK_SCALE, XO1, XO15, XO175, XO4,
MIMETYPE)
from TurtleArt.taexportlogo import save_logo
+from TurtleArt.taexportpython import save_python
from TurtleArt.tautils import (data_to_file, data_to_string, data_from_string,
get_path, chooser_dialog, get_hardware)
from TurtleArt.tawindow import TurtleArtWindow
from TurtleArt.tacollaboration import Collaboration
+from TurtleArt.taprimitive import PyExportError
if HAS_TOOLBARBOX:
from util.helpbutton import (HelpButton, add_section, add_paragraph)
@@ -92,6 +94,8 @@ class TurtleArtActivity(activity.Activity):
self.tw = None
self.init_complete = False
+ self.error_list = []
+
self.palette_buttons = []
self._palette_names = []
self._overflow_buttons = []
@@ -200,21 +204,65 @@ class TurtleArtActivity(activity.Activity):
def do_save_as_logo_cb(self, button):
''' Write UCB logo code to datastore. '''
self.save_as_logo.set_icon('logo-saveon')
+ if hasattr(self, 'get_window'):
+ if hasattr(self.get_window(), 'get_cursor'):
+ self._old_cursor = self.get_window().get_cursor()
+ self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+ gobject.timeout_add(250, self.__save_as_logo)
+
+ def __save_as_logo(self):
logo_code_path = self._dump_logo_code()
- if logo_code_path is None:
- return
+ if logo_code_path is not None:
+ dsobject = datastore.create()
+ dsobject.metadata['title'] = self.metadata['title'] + '.lg'
+ dsobject.metadata['mime_type'] = 'text/plain'
+ dsobject.metadata['icon-color'] = profile.get_color().to_string()
+ dsobject.set_file_path(logo_code_path)
+ datastore.write(dsobject)
+ dsobject.destroy()
+ os.remove(logo_code_path)
+ self.save_as_logo.set_icon('logo-saveoff')
+ if hasattr(self, 'get_window'):
+ self.get_window().set_cursor(self._old_cursor)
- dsobject = datastore.create()
- dsobject.metadata['title'] = self.metadata['title'] + '.lg'
- dsobject.metadata['mime_type'] = 'text/plain'
- dsobject.metadata['icon-color'] = profile.get_color().to_string()
- dsobject.set_file_path(logo_code_path)
- datastore.write(dsobject)
- dsobject.destroy()
+ def do_save_as_python_cb(self, widget):
+ ''' Callback for saving the project as Python code. '''
+ self.save_as_python.set_icon('python-saveon')
+ if hasattr(self, 'get_window'):
+ if hasattr(self.get_window(), 'get_cursor'):
+ self._old_cursor = self.get_window().get_cursor()
+ self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+ gobject.timeout_add(250, self.__save_as_python)
+
+ def __save_as_python(self):
+ # catch PyExportError and display a user-friendly message instead
+ try:
+ pythoncode = save_python(self.tw)
+ except PyExportError as pyee:
+ if pyee.block is not None:
+ pyee.block.highlight()
+ self.tw.showlabel('status', str(pyee))
+ _logger.debug(pyee)
+
+ if pythoncode:
+ datapath = get_path(activity, 'instance')
+ python_code_path = os.path.join(datapath, 'tmpfile.py')
+ f = file(python_code_path, 'w')
+ f.write(pythoncode)
+ f.close()
+
+ dsobject = datastore.create()
+ dsobject.metadata['title'] = self.metadata['title'] + '.py'
+ dsobject.metadata['mime_type'] = 'text/x-python'
+ dsobject.metadata['icon-color'] = profile.get_color().to_string()
+ dsobject.set_file_path(python_code_path)
+ datastore.write(dsobject)
+ dsobject.destroy()
- os.remove(logo_code_path)
- gobject.timeout_add(250, self.save_as_logo.set_icon, 'logo-saveoff')
- self._notify_successful_save(title=_('Save as Logo'))
+ os.remove(python_code_path)
+ self.save_as_python.set_icon('python-saveoff')
+ if hasattr(self, 'get_window'):
+ self.get_window().set_cursor(self._old_cursor)
def do_load_ta_project_cb(self, button, new=False):
''' Load a project from the Journal. '''
@@ -241,7 +289,6 @@ class TurtleArtActivity(activity.Activity):
def do_load_ta_plugin_cb(self, button):
''' Load a plugin from the Journal. '''
- # While the file is loading, use the watch cursor
if hasattr(self, 'get_window'):
if hasattr(self.get_window(), 'get_cursor'):
self._old_cursor = self.get_window().get_cursor()
@@ -268,13 +315,27 @@ class TurtleArtActivity(activity.Activity):
''' Save the canvas to the Journal. '''
self.save_as_image.set_icon('image-saveon')
_logger.debug('saving image to journal')
+ if hasattr(self, 'get_window'):
+ if hasattr(self.get_window(), 'get_cursor'):
+ self._old_cursor = self.get_window().get_cursor()
+ self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+ gobject.timeout_add(250, self.__save_as_image)
+ def __save_as_image(self):
self.tw.save_as_image()
- gobject.timeout_add(250, self.save_as_image.set_icon, 'image-saveoff')
- self._notify_successful_save(title=_('Save as image'))
+ self.save_as_image.set_icon('image-saveoff')
+ if hasattr(self, 'get_window'):
+ self.get_window().set_cursor(self._old_cursor)
def do_keep_cb(self, button):
''' Save a snapshot of the project to the Journal. '''
+ if hasattr(self, 'get_window'):
+ if hasattr(self.get_window(), 'get_cursor'):
+ self._old_cursor = self.get_window().get_cursor()
+ self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+ gobject.timeout_add(250, self.__keep)
+
+ def __keep(self):
tmpfile = self._dump_ta_code()
if tmpfile is not None:
dsobject = datastore.create()
@@ -287,7 +348,9 @@ class TurtleArtActivity(activity.Activity):
datastore.write(dsobject)
dsobject.destroy()
os.remove(tmpfile)
- self._notify_successful_save(title=_('Save snapshot'))
+
+ if hasattr(self, 'get_window'):
+ self.get_window().set_cursor(self._old_cursor)
# Main/palette toolbar button callbacks
@@ -724,7 +787,7 @@ class TurtleArtActivity(activity.Activity):
self._toolbox.show()
if self.has_toolbarbox:
- self.edit_toolbar_button.set_expanded(True)
+ self.edit_toolbar_button.set_expanded(True)
self.edit_toolbar_button.set_expanded(False)
self.palette_toolbar_button.set_expanded(True)
else:
@@ -809,6 +872,7 @@ class TurtleArtActivity(activity.Activity):
add_section(help_box, _('Save/Load'), icon='turtleoff')
add_paragraph(help_box, _('Save as image'), icon='image-saveoff')
add_paragraph(help_box, _('Save as Logo'), icon='logo-saveoff')
+ add_paragraph(help_box, _('Save as Python'), icon='python-saveoff')
add_paragraph(help_box, _('Save snapshot'), icon='filesaveoff')
add_paragraph(help_box, _('Load project'), icon='load-from-journal')
home = os.environ['HOME']
@@ -994,6 +1058,10 @@ class TurtleArtActivity(activity.Activity):
self.save_as_logo, label = self._add_button_and_label(
'logo-saveoff', _('Save as Logo'), self.do_save_as_logo_cb,
None, button_box)
+ self.save_as_python, label = self._add_button_and_label(
+ 'python-saveoff', _('Save as Python'),
+ self.do_save_as_python_cb,
+ None, button_box)
self.keep_button2, self.keep_label2 = self._add_button_and_label(
'filesaveoff', _('Save snapshot'), self.do_keep_cb,
None, button_box)
@@ -1030,6 +1098,10 @@ class TurtleArtActivity(activity.Activity):
self.save_as_logo = self._add_button(
'logo-saveoff', _('Save as Logo'), self.do_save_as_logo_cb,
toolbar)
+ self.save_as_python = self._add_button(
+ 'python-saveoff', _('Save as Python'),
+ self.do_save_as_python_cb,
+ toolbar)
self.keep_button = self._add_button(
'filesaveoff', _('Save snapshot'), self.do_keep_cb, toolbar)
self.load_ta_project = self._add_button(
@@ -1227,6 +1299,14 @@ class TurtleArtActivity(activity.Activity):
self.metadata['turtle blocks'] = ''.join(self.tw.used_block_list)
self.metadata['public'] = data_to_string(['activity count',
'turtle blocks'])
+ if len(self.error_list) > 0:
+ errors = []
+ if 'error_list' in self.metadata:
+ for error in data_from_string(self.metadata['error_list']):
+ errors.append(error)
+ for error in self.error_list:
+ errors.append(error)
+ self.metadata['error_list'] = data_to_string(errors)
_logger.debug('Wrote to file: %s' % (file_path))
def _load_a_plugin(self, tmp_dir):
@@ -1630,19 +1710,6 @@ in order to use the plugin.'))
button_and_label.show()
return button, label
- def _notify_successful_save(self, title='', msg=''):
- ''' Notify user when saves are completed '''
-
- def _notification_alert_response_cb(alert, response_id, self):
- self.remove_alert(alert)
-
- alert = NotifyAlert()
- alert.props.title = title
- alert.connect('response', _notification_alert_response_cb, self)
- alert.props.msg = msg
- self.add_alert(alert)
- alert.show()
-
def restore_state(self):
''' Anything that needs restoring after a clear screen can go here '''
pass
@@ -1656,7 +1723,7 @@ in order to use the plugin.'))
self._sample_box = gtk.EventBox()
self._sample_window = gtk.ScrolledWindow()
self._sample_window.set_policy(gtk.POLICY_NEVER,
- gtk.POLICY_AUTOMATIC)
+ gtk.POLICY_AUTOMATIC)
width = gtk.gdk.screen_width() / 2
height = gtk.gdk.screen_height() / 2
self._sample_window.set_size_request(width, height)
@@ -1708,6 +1775,10 @@ in order to use the plugin.'))
self._selected_sample = image_path
self._sample_window.hide()
+ self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+ gobject.idle_add(self._sample_loader)
+
+ def _sample_loader(self):
# Convert from thumbnail path to sample path
basename = os.path.basename(self._selected_sample)[:-4]
for suffix in ['.ta', '.tb']:
@@ -1718,6 +1789,7 @@ in order to use the plugin.'))
break
self.tw.load_save_folder = os.path.join(activity.get_bundle_path(),
'samples')
+ self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
def _fill_samples_list(self, store):
'''
diff --git a/plugins/accelerometer/accelerometer.py b/plugins/accelerometer/accelerometer.py
index 26aefd4..57aadea 100644
--- a/plugins/accelerometer/accelerometer.py
+++ b/plugins/accelerometer/accelerometer.py
@@ -22,8 +22,8 @@ from gettext import gettext as _
from plugins.plugin import Plugin
from TurtleArt.tapalette import make_palette
-from TurtleArt.talogo import primitive_dictionary
from TurtleArt.tautils import debug_output
+from TurtleArt.taprimitive import Primitive
import logging
_logger = logging.getLogger('turtleart-activity accelerometer plugin')
@@ -35,6 +35,7 @@ ACCELEROMETER_DEVICE = '/sys/devices/platform/lis3lv02d/position'
class Accelerometer(Plugin):
def __init__(self, parent):
+ Plugin.__init__(self)
self._parent = parent
if os.path.exists(ACCELEROMETER_DEVICE):
self._status = True
@@ -49,7 +50,6 @@ class Accelerometer(Plugin):
help_string=_('Palette of sensor blocks'),
position=6)
- primitive_dictionary['xyz'] = self.prim_xyz
if self._status:
palette.add_block('xyz',
style='basic-style-extended-vertical',
@@ -67,7 +67,8 @@ class Accelerometer(Plugin):
prim_name='xyz')
self._parent.lc.def_prim(
- 'xyz', 0, lambda self: primitive_dictionary['xyz']())
+ 'xyz', 0,
+ Primitive(self.prim_xyz))
def _status_report(self):
debug_output('Reporting accelerator status: %s' % (str(self._status)))
diff --git a/plugins/audio_sensors/audio_sensors.py b/plugins/audio_sensors/audio_sensors.py
index 8d45395..d62ca65 100644
--- a/plugins/audio_sensors/audio_sensors.py
+++ b/plugins/audio_sensors/audio_sensors.py
@@ -18,7 +18,6 @@
from gettext import gettext as _
try:
- from numpy import append
from numpy.fft import rfft
PITCH_AVAILABLE = True
except:
@@ -33,8 +32,9 @@ from plugins.audio_sensors.ringbuffer import RingBuffer1d
from TurtleArt.tapalette import make_palette
from TurtleArt.taconstants import XO1, XO15, XO175, XO30, XO4
-from TurtleArt.talogo import primitive_dictionary
from TurtleArt.tautils import debug_output
+from TurtleArt.taprimitive import (ConstantArg, Primitive)
+from TurtleArt.tatype import TYPE_NUMBER
import logging
_logger = logging.getLogger('turtleart-activity audio sensors plugin')
@@ -57,7 +57,9 @@ def _avg(array, abs_value=False):
class Audio_sensors(Plugin):
def __init__(self, parent):
+ Plugin.__init__(self)
self._parent = parent
+ self.audio_started = False
self._status = True # TODO: test for audio device
# These flags are referenced by audiograb
self.hw = self._parent.hw
@@ -65,152 +67,145 @@ class Audio_sensors(Plugin):
def setup(self):
''' set up audio-sensor-specific blocks '''
+ self._sound = [0, 0]
+ self._volume = [0, 0]
+ self._pitch = [0, 0]
+ self._resistance = [0, 0]
+ self._voltage = [0, 0]
self.max_samples = 1500
self.input_step = 1
-
self.ringbuffer = []
palette = make_palette('sensor',
colors=["#FF6060", "#A06060"],
help_string=_('Palette of sensor blocks'),
position=6)
-
- primitive_dictionary['sound'] = self.prim_sound
- primitive_dictionary['volume'] = self.prim_volume
+ hidden = True
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')
+ hidden = False
+
+ palette.add_block('sound',
+ hidden=hidden,
+ style='box-style',
+ label=_('sound'),
+ help_string=_('raw microphone input signal'),
+ value_block=True,
+ prim_name='sound')
+ palette.add_block('volume',
+ hidden=hidden,
+ 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'](0))
+ 'sound', 0,
+ Primitive(self.prim_sound,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'channel': ConstantArg(0)},
+ call_afterwards=self.after_sound))
+
self._parent.lc.def_prim(
- 'volume', 0, lambda self: primitive_dictionary['volume'](0))
+ 'volume', 0,
+ Primitive(self.prim_volume,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'channel': ConstantArg(0)},
+ call_afterwards=self.after_volume))
- primitive_dictionary['pitch'] = self.prim_pitch
+ hidden = True
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'](0))
-
- primitive_dictionary['resistance'] = self.prim_resistance
- primitive_dictionary['voltage'] = self.prim_voltage
+ hidden = False
+
+ palette.add_block('pitch',
+ hidden=hidden,
+ style='box-style',
+ label=_('pitch'),
+ help_string=_('microphone input pitch'),
+ value_block=True,
+ prim_name='pitch')
+ self._parent.lc.def_prim(
+ 'pitch', 0,
+ Primitive(self.prim_pitch,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'channel': ConstantArg(0)},
+ call_afterwards=self.after_pitch))
+
+ hidden = True
if self.hw in [XO1, XO15, XO175, XO4, XO30] and self._status:
+ # Calibration based on http://bugs.sugarlabs.org/ticket/4649
if self.hw == XO1:
self.voltage_gain = 0.000022
self.voltage_bias = 1.14
elif self.hw == XO15:
self.voltage_gain = -0.00015
self.voltage_bias = 1.70
- elif self.hw in [XO175, XO4]: # recalibrate in light of #3675?
- self.voltage_gain = 0.000071
- self.voltage_bias = 0.55
+ elif self.hw == XO175: # Range 0.01V to 3.01V
+ self.voltage_gain = 0.0000516
+ self.voltage_bias = 1.3598
+ elif self.hw == XO4: # Range 0.17V to 3.08V
+ self.voltage_gain = 0.0004073
+ self.voltage_bias = 1.6289
else: # XO 3.0
self.voltage_gain = 0.000077
self.voltage_bias = 0.72
- 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'),
- prim_name='resistance')
- palette.add_block('voltage',
- hidden=True,
- style='box-style',
- label=_('voltage'),
- help_string=_('microphone input voltage'),
- prim_name='voltage')
-
- # FIXME: Only add stereo capture for XO15 (broken on ARM #3675)
+ hidden = False
+
+ palette.add_block('resistance',
+ hidden=hidden,
+ style='box-style',
+ label=_('resistance'),
+ help_string=_('microphone input resistance'),
+ prim_name='resistance')
+ palette.add_block('voltage',
+ hidden=hidden,
+ style='box-style',
+ label=_('voltage'),
+ help_string=_('microphone input voltage'),
+ prim_name='voltage')
+
+ hidden = True
+ # Only add stereo capture for XO15 (broken on ARM #3675)
if self.hw in [XO15] and self._status:
- palette.add_block('resistance2',
- style='box-style',
- label=_('resistance') + '2',
- help_string=_('microphone input resistance'),
- value_block=True,
- prim_name='resistance2')
- palette.add_block('voltage2',
- style='box-style',
- label=_('voltage') + '2',
- help_string=_('microphone input voltage'),
- value_block=True,
- prim_name='voltage2')
- else:
- palette.add_block('resistance2',
- hidden=True,
- style='box-style',
- label=_('resistance') + '2',
- help_string=_('microphone input resistance'),
- prim_name='resistance2')
- palette.add_block('voltage2',
- hidden=True,
- style='box-style',
- label=_('voltage') + '2',
- help_string=_('microphone input voltage'),
- prim_name='voltage2')
+ hidden = False
+
+ palette.add_block('resistance2',
+ hidden=hidden,
+ style='box-style',
+ label=_('resistance') + '2',
+ help_string=_('microphone input resistance'),
+ prim_name='resistance2')
+ palette.add_block('voltage2',
+ hidden=hidden,
+ style='box-style',
+ label=_('voltage') + '2',
+ help_string=_('microphone input voltage'),
+ prim_name='voltage2')
self._parent.lc.def_prim(
'resistance', 0,
- lambda self: primitive_dictionary['resistance'](0))
+ Primitive(self.prim_resistance,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'channel': ConstantArg(0)},
+ call_afterwards=self.after_resistance))
self._parent.lc.def_prim(
- 'voltage', 0, lambda self: primitive_dictionary['voltage'](0))
+ 'voltage', 0,
+ Primitive(self.prim_voltage,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'channel': ConstantArg(0)},
+ call_afterwards=self.after_voltage))
self._parent.lc.def_prim(
'resistance2', 0,
- lambda self: primitive_dictionary['resistance'](1))
+ Primitive(self.prim_resistance,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'channel': ConstantArg(1)},
+ call_afterwards=self.after_resistance))
self._parent.lc.def_prim(
- 'voltage2', 0, lambda self: primitive_dictionary['voltage'](1))
+ 'voltage2', 0,
+ Primitive(self.prim_voltage,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'channel': ConstantArg(1)},
+ call_afterwards=self.after_voltage))
- self.audio_started = False
if self.hw in [XO175, XO30, XO4]:
self.PARAMETERS = {
SENSOR_AC_BIAS: (False, True, 80, True),
@@ -240,6 +235,11 @@ class Audio_sensors(Plugin):
''' Start grabbing audio if there is an audio block in use '''
if not self._status:
return
+ self._sound = [0, 0]
+ self._volume = [0, 0]
+ self._pitch = [0, 0]
+ self._resistance = [0, 0]
+ self._voltage = [0, 0]
if self.audio_started:
self.audiograb.stop_grabbing()
if len(self._parent.block_list.get_similar_blocks(
@@ -292,177 +292,181 @@ class Audio_sensors(Plugin):
self._parent.running_sugar)
return self._status
- # Block primitives used in talogo
+ # Block primitives
- def prim_volume(self, channel):
+ def prim_sound(self, channel=0):
if not self._status:
return 0
+ self._prim_sound(0)
# Return average of both channels if sampling in stereo
if self._channels == 2:
- chan0 = self._prim_volume(0)
- chan1 = self._prim_volume(1)
- return (chan0 + chan1) / 2
+ self._prim_sound(1)
+ return (self._sound[0] + self._sound[1]) / 2.0
else:
- return self._prim_volume(0)
+ return self._sound[0]
- def _prim_volume(self, channel):
- ''' return mic in value '''
+ def _prim_sound(self, channel):
+ ''' return raw mic in value '''
buf = self.ringbuffer[channel].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
+ self._sound[channel] = float(buf[0])
else:
- return 0
+ self._sound[channel] = 0
+
+ def after_sound(self, channel=0):
+ if self._parent.lc.update_values:
+ self._parent.lc.update_label_value('sound', self._sound[channel])
- def prim_sound(self, channel):
+ def prim_volume(self, channel=0):
if not self._status:
return 0
+ self._prim_volume(0)
# Return average of both channels if sampling in stereo
if self._channels == 2:
- chan0 = self._prim_sound(0)
- chan1 = self._prim_sound(1)
- return (chan0 + chan1) / 2
+ self._prim_volume(1)
+ return (self._volume[0] + self._volume[1]) / 2.0
else:
- return self._prim_sound(0)
+ return self._volume[0]
- def _prim_sound(self, channel):
+ def _prim_volume(self, channel):
''' return raw mic in value '''
buf = self.ringbuffer[channel].read(None, self.input_step)
if len(buf) > 0:
- sound = float(buf[0])
- if self._parent.lc.update_values:
- self._parent.lc.update_label_value('sound', sound)
- return sound
+ self._volume[channel] = float(_avg(buf, abs_value=True))
else:
- return 0
+ self._volume[channel] = 0
+
+ def after_volume(self, channel=0):
+ if self._parent.lc.update_values:
+ self._parent.lc.update_label_value('volume', self._volume[channel])
- def prim_pitch(self, channel):
- if not PITCH_AVAILABLE or not self._status:
+ def prim_pitch(self, channel=0):
+ if not self._status:
return 0
+ self._prim_pitch(0)
# Return average of both channels if sampling in stereo
if self._channels == 2:
- chan0 = self._prim_pitch(0)
- chan1 = self._prim_pitch(1)
- return (chan0 + chan1) / 2
+ self._prim_pitch(1)
+ return (self._pitch[0] + self._pitch[1]) / 2.0
else:
- return self._prim_pitch(0)
+ return self._pitch[0]
def _prim_pitch(self, channel):
- ''' return index of max value in fft of mic in values '''
+ ''' return raw mic in value '''
buf = self.ringbuffer[channel].read(None, self.input_step)
if len(buf) > 0:
buf = rfft(buf)
buf = abs(buf)
maxi = buf.argmax()
if maxi == 0:
- pitch = 0
+ self._pitch[channel] = 0
else: # Simple interpolation
a, b, c = buf[maxi - 1], buf[maxi], buf[maxi + 1]
maxi -= a / float(a + b + c)
maxi += c / float(a + b + c)
- pitch = maxi * 48000 / (len(buf) * 2)
-
- if self._parent.lc.update_values:
- self._parent.lc.update_label_value('pitch', pitch)
- return pitch
+ self._pitch[channel] = maxi * 48000 / (len(buf) * 2)
else:
- return 0
+ self._pitch[channel] = 0
+
+ def after_pitch(self, channel=0):
+ if self._parent.lc.update_values:
+ self._parent.lc.update_label_value('pitch', self._pitch[channel])
- def prim_resistance(self, channel):
+ def prim_resistance(self, channel=0):
if not self.hw in [XO1, XO15, XO175, XO30, XO4] or not self._status:
return 0
if self.hw in [XO1, XO4]:
- resistance = self._prim_resistance(0)
- if self._parent.lc.update_values:
- self._update_resistance_labels(0, resistance)
- return resistance
+ self._prim_resistance(0)
+ return self._resistance[0]
elif self.hw == XO15:
- resistance = self._prim_resistance(channel)
- if self._parent.lc.update_values:
- self._update_resistance_labels(channel, resistance)
- return resistance
- # FIXME: For XO175: channel assignment is seemingly random
- # (#3675), so sum both channels (one of them will be 0)
+ self._prim_resistance(channel)
+ return self._resistance[channel]
+ # For XO175: channel assignment is seemingly random
+ # (#3675), one of them will be 0
else:
- chan0 = self._prim_resistance(0)
- chan1 = self._prim_resistance(1)
- resistance = chan0 + chan1
- if self._parent.lc.update_values:
- self._update_resistance_labels(0, resistance)
- return resistance
+ self._prim_resistance(0)
+ if self._resistance[0] != 999999999:
+ return self._resistance[0]
+ else:
+ self._prim_resistance(1)
+ return self._resistance[1]
def _prim_resistance(self, channel):
''' return resistance sensor value '''
buf = self.ringbuffer[channel].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, XO 1.75
+ # See http://bugs.sugarlabs.org/ticket/552#comment:7
+ # and http://bugs.sugarlabs.org/ticket/4649
avg_buf = float(_avg(buf))
if self.hw == XO1:
- resistance = 2.718 ** ((avg_buf * 0.000045788) + 8.0531)
+ self._resistance[channel] = \
+ 2.718 ** ((avg_buf * 0.000045788) + 8.0531)
elif self.hw == XO15:
if avg_buf > 0:
- resistance = (420000000 / avg_buf) - 13500
+ self._resistance[channel] = (420000000 / avg_buf) - 13500
else:
- resistance = 420000000
- elif self.hw in [XO175, XO4]:
- if avg_buf < 30700:
- resistance = .12 * ((180000000 / (30700 - avg_buf)) - 3150)
+ self._resistance[channel] = 420000000
+ elif self.hw == XO175: # Range 0 to inf ohms
+ if avg_buf < 30519:
+ self._resistance[channel] = \
+ (92000000. / (30519 - avg_buf)) - 1620
else:
- resistance = 999999999
+ self._resistance[channel] = 999999999
+ elif self.hw == XO4: # Range 0 to inf ohms
+ if avg_buf < 6629:
+ self._resistance[channel] = \
+ (50000000. / (6629 - avg_buf)) - 3175
+ else:
+ self._resistance[channel] = 999999999
else: # XO 3.0
if avg_buf < 30514:
- resistance = (46000000 / (30514 - avg_buf)) - 1150
+ self._resistance[channel] = \
+ (46000000. / (30514 - avg_buf)) - 1150
else:
- resistance = 999999999
- if resistance < 0:
- resistance = 0
- return resistance
+ self._resistance[channel] = 999999999
+ if self._resistance[channel] < 0:
+ self._resistance[channel] = 0
else:
- return 0
+ self._resistance[channel] = 0
- def _update_resistance_labels(self, channel, resistance):
- if channel == 0:
- self._parent.lc.update_label_value('resistance', resistance)
- else:
- self._parent.lc.update_label_value('resistance2', resistance)
+ def after_resistance(self, channel=0):
+ if self._parent.lc.update_values:
+ self._parent.lc.update_label_value(
+ ['resistance', 'resistance2'][channel],
+ self._resistance[channel])
- def prim_voltage(self, channel):
+ def prim_voltage(self, channel=0):
if not self.hw in [XO1, XO15, XO175, XO30, XO4] or not self._status:
return 0
if self.hw in [XO1, XO4]:
- voltage = self._prim_voltage(0)
- if self._parent.lc.update_values:
- self._update_voltage_labels(0, voltage)
- return voltage
+ self._prim_voltage(0)
+ return self._voltage[0]
elif self.hw == XO15:
- voltage = self._prim_voltage(channel)
- if self._parent.lc.update_values:
- self._update_voltage_labels(channel, voltage)
- return voltage
+ self._prim_voltage(channel)
+ return self._voltage[channel]
# FIXME: For XO175: channel assignment is seemingly random
- # (#3675), so sum both channels (one of them will be 0)
+ # (#3675), one of them will be 0
else:
- chan0 = self._prim_voltage(0)
- chan1 = self._prim_voltage(1)
- voltage = chan0 + chan1
- if self._parent.lc.update_values:
- self._update_voltage_labels(0, voltage)
- return voltage
+ self._prim_voltage(0)
+ if self._voltage[0] != 0:
+ return self._voltage[0]
+ else:
+ self._prim_voltage(1)
+ return self._voltage[1]
def _prim_voltage(self, channel):
''' return voltage sensor value '''
buf = self.ringbuffer[channel].read(None, self.input_step)
+ buf = self.ringbuffer[channel].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
- return voltage
+ self._voltage[channel] = \
+ float(_avg(buf)) * self.voltage_gain + self.voltage_bias
else:
- return 0
+ self._voltage[channel] = 0
- def _update_voltage_labels(self, channel, voltage):
- if channel == 0:
- self._parent.lc.update_label_value('voltage', voltage)
- else:
- self._parent.lc.update_label_value('voltage2', voltage)
+ def after_voltage(self, channel=0):
+ if self._parent.lc.update_values:
+ self._parent.lc.update_label_value(
+ ['voltage', 'voltage2'][channel],
+ self._voltage[channel])
diff --git a/plugins/camera_sensor/camera_sensor.py b/plugins/camera_sensor/camera_sensor.py
index 6509a88..5f79d15 100644
--- a/plugins/camera_sensor/camera_sensor.py
+++ b/plugins/camera_sensor/camera_sensor.py
@@ -30,21 +30,25 @@ from plugins.camera_sensor.v4l2 import v4l2_control, V4L2_CID_AUTOGAIN, \
from plugins.plugin import Plugin
from TurtleArt.tapalette import make_palette
-from TurtleArt.talogo import media_blocks_dictionary, primitive_dictionary
+from TurtleArt.talogo import media_blocks_dictionary
from TurtleArt.tautils import get_path, debug_output
from TurtleArt.taconstants import MEDIA_SHAPES, NO_IMPORT, SKIN_PATHS, \
BLOCKS_WITH_SKIN
+from TurtleArt.taprimitive import (ConstantArg, Primitive)
+from TurtleArt.tatype import TYPE_NUMBER
class Camera_sensor(Plugin):
def __init__(self, parent):
+ Plugin.__init__(self)
''' Make sure there is a camera device '''
self._parent = parent
self._status = False
self._ag_control = None
self.devices = []
self.cameras = []
+ self.luminance = 0
if os.path.exists('/dev/video0'):
self.devices.append('/dev/video0')
@@ -68,7 +72,6 @@ class Camera_sensor(Plugin):
position=7)
# set up camera-specific blocks
- primitive_dictionary['read_camera'] = self.prim_read_camera
media_blocks_dictionary['camera'] = self.prim_take_picture0
media_blocks_dictionary['camera1'] = self.prim_take_picture1
@@ -82,9 +85,12 @@ class Camera_sensor(Plugin):
'light level detected by camera'),
value_block=True,
prim_name='luminance')
- self._parent.lc.def_prim('luminance', 0,
- lambda self: primitive_dictionary['read_camera'](
- luminance_only=True))
+ self._parent.lc.def_prim(
+ 'luminance', 0,
+ Primitive(self.prim_read_camera,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'luminance_only': ConstantArg(True)},
+ call_afterwards=self.after_luminance))
# Depreciated block
sensors_palette.add_block('read_camera',
@@ -96,8 +102,10 @@ class Camera_sensor(Plugin):
is pushed to the stack'),
value_block=True,
prim_name='read_camera')
- self._parent.lc.def_prim('read_camera', 0,
- lambda self: primitive_dictionary['read_camera']())
+ self._parent.lc.def_prim(
+ 'read_camera', 0,
+ Primitive(self.prim_read_camera,
+ kwarg_descs={'luminance_only': ConstantArg(False)}))
media_palette.add_block('camera',
style='box-style-media',
@@ -130,9 +138,12 @@ is pushed to the stack'),
_('light level detected by camera'),
value_block=True,
prim_name='read_camera')
- self._parent.lc.def_prim('luminance', 0,
- lambda self: primitive_dictionary['read_camera'](
- luminance_only=True))
+ self._parent.lc.def_prim(
+ 'luminance', 0,
+ Primitive(self.prim_read_camera,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'luminance_only': ConstantArg(True)},
+ call_afterwards=self.after_luminance))
# Depreciated block
sensors_palette.add_block('read_camera',
@@ -144,8 +155,11 @@ is pushed to the stack'),
is pushed to the stack'),
value_block=True,
prim_name='read_camera')
- self._parent.lc.def_prim('read_camera', 0,
- lambda self: primitive_dictionary['read_camera']())
+ self._parent.lc.def_prim(
+ 'read_camera', 0,
+ Primitive(self.prim_read_camera,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'luminance_only': ConstantArg(False)}))
media_palette.add_block('camera',
hidden=True,
@@ -227,19 +241,19 @@ is pushed to the stack'),
self._parent.lc.heap.append(-1)
self._parent.lc.heap.append(-1)
self._parent.lc.heap.append(-1)
- return
+ return
array = None
self._set_autogain(0, camera=camera) # disable AUTOGAIN
self._get_pixbuf_from_camera(camera=camera)
self.calc_luminance(camera=camera)
if self.luminance_only:
- self._parent.lc.update_label_value('luminance', self.luminance)
- return self.luminance
+ return int(self.luminance)
else:
self._parent.lc.heap.append(self.b)
self._parent.lc.heap.append(self.g)
self._parent.lc.heap.append(self.r)
+ return
def calc_luminance(self, camera=0):
array = self.cameras[camera].pixbuf.get_pixels()
@@ -281,6 +295,10 @@ is pushed to the stack'),
self.g = -1
self.b = -1
+ def after_luminance(self, luminance_only=False):
+ if self._parent.lc.update_values and luminance_only:
+ self._parent.lc.update_label_value('luminance', self.luminance)
+
def _set_autogain(self, state, camera=0):
''' 0 is off; 1 is on '''
if self._ag_control is not None and self._ag_control.value == state:
diff --git a/plugins/light_sensor/light_sensor.py b/plugins/light_sensor/light_sensor.py
index bd5655e..dc4ebb1 100644
--- a/plugins/light_sensor/light_sensor.py
+++ b/plugins/light_sensor/light_sensor.py
@@ -22,8 +22,9 @@ from gettext import gettext as _
from plugins.plugin import Plugin
from TurtleArt.tapalette import make_palette
-from TurtleArt.talogo import primitive_dictionary
from TurtleArt.tautils import debug_output
+from TurtleArt.taprimitive import Primitive
+from TurtleArt.tatype import TYPE_NUMBER
import logging
_logger = logging.getLogger('turtleart-activity light-sensor plugin')
@@ -35,11 +36,13 @@ LIGHT_SENSOR_DEVICE = '/sys/devices/platform/olpc-ols.0/level'
class Light_sensor(Plugin):
def __init__(self, parent):
+ Plugin.__init__(self)
self._parent = parent
if os.path.exists(LIGHT_SENSOR_DEVICE):
self._status = True
else:
self._status = False
+ self._light = 0
self.running_sugar = self._parent.running_sugar
def setup(self):
@@ -49,11 +52,11 @@ class Light_sensor(Plugin):
help_string=_('Palette of sensor blocks'),
position=6)
- primitive_dictionary['lightsensor'] = self.prim_lightsensor
if self._status:
palette.add_block('lightsensor',
style='box-style',
label=_('brightness'),
+ value_block=True,
help_string=\
_('light level detected by light sensor'),
prim_name='lightsensor')
@@ -61,6 +64,7 @@ class Light_sensor(Plugin):
palette.add_block('lightsensor',
style='box-style',
label=_('brightness'),
+ value_block=True,
help_string=\
_('light level detected by light sensor'),
hidden=True,
@@ -68,20 +72,26 @@ class Light_sensor(Plugin):
self._parent.lc.def_prim(
'lightsensor', 0,
- lambda self: primitive_dictionary['lightsensor']())
+ Primitive(self.prim_lightsensor,
+ return_type=TYPE_NUMBER,
+ call_afterwards=self.after_light))
def _status_report(self):
debug_output('Reporting light-sensor status: %s' % (str(self._status)))
return self._status
- # Block primitives used in talogo
+ # Block primitives
def prim_lightsensor(self):
- ''' push accelerometer xyz to stack '''
if not self._status:
return -1
else:
fh = open(LIGHT_SENSOR_DEVICE)
string = fh.read()
fh.close()
- return float(string)
+ self._light = float(string)
+ return self._light
+
+ def after_light(self):
+ if self._parent.lc.update_values:
+ self._parent.lc.update_label_value('lightsensor', self._light)
diff --git a/plugins/rfid/rfid.py b/plugins/rfid/rfid.py
index de96a1f..c8742ff 100644
--- a/plugins/rfid/rfid.py
+++ b/plugins/rfid/rfid.py
@@ -24,8 +24,9 @@ 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
+from TurtleArt.taprimitive import Primitive
+from TurtleArt.tatype import TYPE_STRING
import logging
_logger = logging.getLogger('turtleart-activity RFID plugin')
@@ -41,6 +42,7 @@ REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\
class Rfid(Plugin):
def __init__(self, parent):
+ Plugin.__init__(self)
self._parent = parent
self._status = False
@@ -80,7 +82,6 @@ class Rfid(Plugin):
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'),
@@ -103,7 +104,10 @@ class Rfid(Plugin):
prim_name='rfid')
self._parent.lc.def_prim(
- 'rfid', 0, lambda self: primitive_dictionary['rfid']())
+ 'rfid', 0,
+ Primitive(self.prim_read_rfid,
+ return_type=TYPE_STRING,
+ call_afterwards=self.after_rfid))
def _status_report(self):
debug_output('Reporting RFID status: %s' % (str(self._status)))
@@ -149,3 +153,7 @@ class Rfid(Plugin):
return self.rfid_idn
else:
return '0'
+
+ def after_rfid(self):
+ if self._parent.lc.update_values:
+ self._parent.lc.update_label_value('rfid', self.rfid_idn)
diff --git a/plugins/turtle_blocks_extras/turtle_blocks_extras.py b/plugins/turtle_blocks_extras/turtle_blocks_extras.py
index e31cb27..1e9b2e4 100644
--- a/plugins/turtle_blocks_extras/turtle_blocks_extras.py
+++ b/plugins/turtle_blocks_extras/turtle_blocks_extras.py
@@ -15,8 +15,6 @@
# 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
-import gobject
from time import time
import os
import glob
@@ -24,37 +22,19 @@ import glob
from gettext import gettext as _
from plugins.plugin import Plugin
-from TurtleArt.tapalette import (make_palette, define_logo_function,
- block_names, block_primitives, special_names,
- content_blocks, palette_name_to_index,
- palette_names, palette_i18n_names)
-from TurtleArt.talogo import (primitive_dictionary, logoerror,
- media_blocks_dictionary)
-from TurtleArt.taconstants import (DEFAULT_SCALE, ICON_SIZE, CONSTANTS,
- MEDIA_SHAPES, SKIN_PATHS, BLOCKS_WITH_SKIN,
- PYTHON_SKIN, PREFIX_DICTIONARY, VOICES,
- MACROS, COLORDICT)
-from TurtleArt.tautils import (round_int, debug_output, get_path,
- data_to_string, find_group, image_to_base64,
- hat_on_top, listify, data_from_file,
- data_to_file, chooser_dialog, get_load_name)
-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 _millisecond():
- """ Current time in milliseconds """
- return time() * 1000
+from TurtleArt.tapalette import (make_palette, define_logo_function)
+from TurtleArt.talogo import (primitive_dictionary, logoerror)
+from TurtleArt.taconstants import (CONSTANTS, MACROS, KEY_DICT, MEDIA_SHAPES,
+ REVERSE_KEY_DICT, SKIN_PATHS,
+ BLOCKS_WITH_SKIN, PYTHON_SKIN,
+ MEDIA_BLOCK2TYPE, VOICES)
+from TurtleArt.tautils import (debug_output, get_path, data_to_string,
+ hat_on_top, listify, data_from_file)
+from TurtleArt.taprimitive import (ArgSlot, ConstantArg, Primitive)
+from TurtleArt.tatype import (TYPE_BOOL, TYPE_BOX, TYPE_CHAR, TYPE_INT,
+ TYPE_FLOAT, TYPE_OBJECT, TYPE_STRING,
+ TYPE_NUMBER)
+from TurtleArt.taturtle import Turtle
class Turtle_blocks_extras(Plugin):
@@ -62,13 +42,13 @@ class Turtle_blocks_extras(Plugin):
from Turtle Art """
def __init__(self, turtle_window):
+ Plugin.__init__(self)
self.tw = turtle_window
def setup(self):
SKIN_PATHS.append('plugins/turtle_blocks_extras/images')
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
@@ -91,7 +71,6 @@ class Turtle_blocks_extras(Plugin):
colors=["#FFC000", "#A08000"],
help_string=_('Palette of flow operators'))
- # internally expanded macro
palette.add_block('while',
style='clamp-style-boolean',
label=_('while'),
@@ -100,8 +79,19 @@ class Turtle_blocks_extras(Plugin):
special_name=_('while'),
help_string=_('do-while-True operator that uses \
boolean operators from Numbers palette'))
+ self.tw.lc.def_prim(
+ 'while', 2,
+ Primitive(self.tw.lc.prim_loop,
+ arg_descs=[
+ ArgSlot(TYPE_OBJECT,
+ call_arg=False,
+ wrapper=Primitive(
+ Primitive.controller_while,
+ arg_descs=[ArgSlot(TYPE_BOOL,
+ call_arg=False)])),
+ ArgSlot(TYPE_OBJECT)]),
+ True)
- # internally expanded macro
palette.add_block('until',
style='clamp-style-boolean',
label=_('until'),
@@ -110,23 +100,39 @@ boolean operators from Numbers palette'))
special_name=_('until'),
help_string=_('do-until-True operator that uses \
boolean operators from Numbers palette'))
+ self.tw.lc.def_prim(
+ 'until', 2,
+ Primitive(self.tw.lc.prim_loop,
+ arg_descs=[
+ ArgSlot(TYPE_OBJECT,
+ call_arg=False,
+ # TODO can we use controller_while in
+ # combination with not_?
+ wrapper=Primitive(
+ Primitive.controller_until,
+ arg_descs=[ArgSlot(TYPE_BOOL,
+ call_arg=False)])),
+ ArgSlot(TYPE_OBJECT)]),
+ True)
- primitive_dictionary['clamp'] = self._prim_clamp
palette.add_block('sandwichclamp',
style='clamp-style-collapsible',
label=' ',
special_name=_('top'),
prim_name='clamp',
help_string=_('top of a collapsible stack'))
- self.tw.lc.def_prim('clamp', 1, primitive_dictionary['clamp'], True)
+ self.tw.lc.def_prim('clamp', 1,
+ Primitive(self.tw.lc.prim_clamp,
+ arg_descs=[ArgSlot(TYPE_OBJECT)]),
+ True)
def _media_palette(self):
- debug_output('creating %s palette' % _('media'),
- self.tw.running_sugar)
+
palette = make_palette('media',
colors=["#A0FF00", "#80A000"],
help_string=_('Palette of media objects'),
- position=7)
+ position=7,
+ translation=_('media'))
palette.add_block('journal',
style='box-style-media',
@@ -134,7 +140,7 @@ boolean operators from Numbers palette'))
default='None',
special_name=_('journal'),
help_string=_('Sugar Journal media object'))
- PREFIX_DICTIONARY['journal'] = '#smedia_'
+ MEDIA_BLOCK2TYPE['journal'] = 'media'
BLOCKS_WITH_SKIN.append('journal')
MEDIA_SHAPES.append('journalsmall')
MEDIA_SHAPES.append('journaloff')
@@ -147,7 +153,7 @@ boolean operators from Numbers palette'))
default='None',
help_string=_('Sugar Journal audio object'))
BLOCKS_WITH_SKIN.append('audio')
- PREFIX_DICTIONARY['audio'] = '#saudio_'
+ MEDIA_BLOCK2TYPE['audio'] = 'audio'
MEDIA_SHAPES.append('audiosmall')
MEDIA_SHAPES.append('audiooff')
MEDIA_SHAPES.append('audioon')
@@ -159,7 +165,7 @@ boolean operators from Numbers palette'))
default='None',
help_string=_('Sugar Journal video object'))
BLOCKS_WITH_SKIN.append('video')
- PREFIX_DICTIONARY['video'] = '#svideo_'
+ MEDIA_BLOCK2TYPE['video'] = 'video'
MEDIA_SHAPES.append('videosmall')
MEDIA_SHAPES.append('videooff')
MEDIA_SHAPES.append('videoon')
@@ -171,7 +177,7 @@ boolean operators from Numbers palette'))
default='None',
help_string=_('Sugar Journal description field'))
BLOCKS_WITH_SKIN.append('description')
- PREFIX_DICTIONARY['description'] = '#sdescr_'
+ MEDIA_BLOCK2TYPE['description'] = 'descr'
MEDIA_SHAPES.append('descriptionsmall')
MEDIA_SHAPES.append('descriptionoff')
MEDIA_SHAPES.append('descriptionon')
@@ -183,7 +189,6 @@ boolean operators from Numbers palette'))
special_name=_('text'),
help_string=_('string value'))
- primitive_dictionary['show'] = self._prim_show
palette.add_block('show',
style='basic-style-1arg',
label=_('show'),
@@ -193,8 +198,9 @@ boolean operators from Numbers palette'))
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))
+ Primitive(self.tw.lc.show,
+ arg_descs=[ArgSlot(TYPE_OBJECT),
+ ConstantArg(True)]))
palette.add_block('showaligned',
hidden=True,
@@ -207,10 +213,10 @@ Journal'))
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))
+ Primitive(self.tw.lc.show,
+ arg_descs=[ArgSlot(TYPE_OBJECT),
+ ConstantArg(False)]))
- primitive_dictionary['setscale'] = self._prim_setscale
palette.add_block('setscale',
style='basic-style-1arg',
label=_('set scale'),
@@ -218,11 +224,13 @@ Journal'))
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))
+ self.tw.lc.def_prim(
+ 'setscale', 1,
+ Primitive(self.tw.lc.set_scale,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=lambda value: self.after_set(
+ 'scale', value)))
- primitive_dictionary['savepix'] = self._prim_save_picture
palette.add_block('savepix',
style='basic-style-1arg',
label=_('save picture'),
@@ -231,9 +239,9 @@ Journal'))
help_string=_('saves a picture to the Sugar \
Journal'))
self.tw.lc.def_prim('savepix', 1,
- lambda self, x: primitive_dictionary['savepix'](x))
+ Primitive(self.tw.save_as_image,
+ arg_descs=[ArgSlot(TYPE_STRING)]))
- primitive_dictionary['savesvg'] = self._prim_save_svg
palette.add_block('savesvg',
style='basic-style-1arg',
label=_('save SVG'),
@@ -242,7 +250,9 @@ Journal'))
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))
+ Primitive(self.tw.save_as_image,
+ arg_descs=[ArgSlot(TYPE_STRING)],
+ kwarg_descs={'svg': ConstantArg(True)}))
palette.add_block('scale',
style='box-style',
@@ -251,7 +261,9 @@ in the Sugar Journal'))
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)
+ self.tw.lc.def_prim('scale', 0,
+ Primitive(self.tw.lc.get_scale,
+ return_type=TYPE_NUMBER))
palette.add_block('mediawait',
style='basic-style-extended-vertical',
@@ -282,7 +294,6 @@ complete'))
help_string=_('resume playing video or audio'))
self.tw.lc.def_prim('mediaplay', 0, self.tw.lc.media_play, True)
- primitive_dictionary['speak'] = self._prim_speak
palette.add_block('speak',
style='basic-style-1arg',
label=_('speak'),
@@ -290,9 +301,9 @@ complete'))
default=_('hello'),
help_string=_('speaks text'))
self.tw.lc.def_prim('speak', 1,
- lambda self, x: primitive_dictionary['speak'](x))
+ Primitive(self.prim_speak,
+ arg_descs=[ArgSlot(TYPE_STRING)]))
- primitive_dictionary['sinewave'] = self._prim_sinewave
palette.add_block('sinewave',
style='basic-style-3arg',
# TRANS: pitch, duration, amplitude
@@ -303,18 +314,19 @@ complete'))
help_string=_('plays a sinewave at frequency, \
amplitude, and duration (in seconds)'))
self.tw.lc.def_prim('sinewave', 3,
- lambda self, x, y, z:
- primitive_dictionary['sinewave'](x, y, z))
+ Primitive(self.prim_sinewave,
+ arg_descs=[ArgSlot(TYPE_NUMBER),
+ ArgSlot(TYPE_NUMBER),
+ ArgSlot(TYPE_NUMBER)]))
def _sensor_palette(self):
- debug_output('creating %s palette' % _('sensor'),
- self.tw.running_sugar)
+
palette = make_palette('sensor',
colors=["#FF6060", "#A06060"],
help_string=_('Palette of sensor blocks'),
- position=6)
+ position=6,
+ translation=_('sensor'))
- primitive_dictionary['mousebutton'] = self._prim_mouse_button
palette.add_block('mousebutton',
hidden=True,
style='box-style',
@@ -324,9 +336,9 @@ amplitude, and duration (in seconds)'))
help_string=_('returns 1 if mouse button is \
pressed'))
self.tw.lc.def_prim('mousebutton', 0,
- lambda self: primitive_dictionary['mousebutton']())
+ Primitive(self.tw.get_mouse_flag,
+ return_type=TYPE_NUMBER))
- primitive_dictionary['mousebutton2'] = self._prim_mouse_button_bool
palette.add_block('mousebutton2',
style='boolean-block-style',
label=_('button down'),
@@ -335,10 +347,9 @@ pressed'))
help_string=_('returns True if mouse button is \
pressed'))
self.tw.lc.def_prim('mousebutton2', 0,
- lambda self:
- primitive_dictionary['mousebutton2']())
+ Primitive(self.tw.get_mouse_button,
+ return_type=TYPE_BOOL))
- primitive_dictionary['mousex'] = self._prim_mouse_x
palette.add_block('mousex',
style='box-style',
label=_('mouse x'),
@@ -346,10 +357,10 @@ pressed'))
value_block=True,
help_string=_('returns mouse x coordinate'))
self.tw.lc.def_prim('mousex', 0,
- lambda self:
- primitive_dictionary['mousex']())
+ Primitive(self.tw.get_mouse_x,
+ return_type=TYPE_NUMBER,
+ call_afterwards=self.after_mouse_x))
- primitive_dictionary['mousey'] = self._prim_mouse_y
palette.add_block('mousey',
style='box-style',
label=_('mouse y'),
@@ -357,10 +368,10 @@ pressed'))
value_block=True,
help_string=_('returns mouse y coordinate'))
self.tw.lc.def_prim('mousey', 0,
- lambda self:
- primitive_dictionary['mousey']())
+ Primitive(self.tw.get_mouse_y,
+ return_type=TYPE_NUMBER,
+ call_afterwards=self.after_mouse_y))
- primitive_dictionary['kbinput'] = self._prim_kbinput
palette.add_block('kbinput',
style='basic-style-extended-vertical',
label=_('query keyboard'),
@@ -368,9 +379,9 @@ pressed'))
help_string=_('query for keyboard input (results \
stored in keyboard block)'))
self.tw.lc.def_prim('kbinput', 0,
- lambda self: primitive_dictionary['kbinput']())
+ Primitive(self.tw.get_keyboard_input,
+ call_afterwards=self.after_keypress))
- primitive_dictionary['keyboard'] = self._prim_keyboard
palette.add_block('keyboard',
style='box-style',
label=_('keyboard'),
@@ -380,9 +391,9 @@ stored in keyboard block)'))
help_string=_('holds results of query-keyboard \
block as ASCII'))
self.tw.lc.def_prim('keyboard', 0,
- lambda self: primitive_dictionary['keyboard']())
+ Primitive(self.tw.get_keyboard,
+ return_type=TYPE_NUMBER))
- primitive_dictionary['readpixel'] = self._prim_readpixel
palette.add_block('readpixel',
style='basic-style-extended-vertical',
label=_('read pixel'),
@@ -391,9 +402,8 @@ block as ASCII'))
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(Turtle.read_pixel))
- primitive_dictionary['see'] = self._prim_see
palette.add_block('see',
style='box-style',
label=_('turtle sees'),
@@ -402,9 +412,10 @@ to the stack'))
help_string=_('returns the color that the turtle \
"sees"'))
self.tw.lc.def_prim('see', 0,
- lambda self: primitive_dictionary['see']())
+ Primitive(Turtle.get_color_index,
+ return_type=TYPE_NUMBER,
+ call_afterwards=self.after_see))
- primitive_dictionary['time'] = self._prim_time
palette.add_block('time',
style='box-style',
label=_('time'),
@@ -412,18 +423,33 @@ to the stack'))
value_block=True,
help_string=_('elapsed time (in seconds) since \
program started'))
- self.tw.lc.def_prim('time', 0,
- lambda self: primitive_dictionary['time']())
+ self.tw.lc.def_prim(
+ 'time', 0,
+ Primitive(
+ Primitive.identity,
+ return_type=TYPE_INT,
+ arg_descs=[
+ ConstantArg(
+ Primitive(
+ int,
+ arg_descs=[ConstantArg(
+ Primitive(Primitive.minus,
+ arg_descs=[
+ ConstantArg(Primitive(time)),
+ ConstantArg(Primitive(
+ self.tw.lc.get_start_time))])
+ )]
+ ))],
+ call_afterwards=self.after_time))
def _extras_palette(self):
- debug_output('creating %s palette' % _('extras'),
- self.tw.running_sugar)
+
palette = make_palette('extras',
colors=["#FF0000", "#A00000"],
help_string=_('Palette of extra options'),
- position=8)
+ position=8,
+ translation=_('extras'))
- primitive_dictionary['push'] = self._prim_push
palette.add_block('push',
style='basic-style-1arg',
#TRANS: push adds a new item to the program stack
@@ -432,12 +458,14 @@ program started'))
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))
+ self.tw.lc.def_prim(
+ 'push', 1,
+ Primitive(self.tw.lc.heap.append,
+ arg_descs=[ArgSlot(TYPE_OBJECT)],
+ call_afterwards=self.after_push))
define_logo_function('tapush', 'to tapush :foo\nmake "taheap fput \
:foo :taheap\nend\nmake "taheap []\n')
- primitive_dictionary['printheap'] = self._prim_printheap
palette.add_block('printheap',
style='basic-style-extended-vertical',
label=_('show heap'),
@@ -445,12 +473,14 @@ last-out heap)'))
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']())
+ self.tw.lc.def_prim(
+ 'printheap', 0,
+ Primitive(self.tw.print_,
+ arg_descs=[ConstantArg(Primitive(self.tw.lc.get_heap)),
+ ConstantArg(False)]))
define_logo_function('taprintheap', 'to taprintheap \nprint :taheap\n\
end\n')
- primitive_dictionary['clearheap'] = self._prim_emptyheap
palette.add_block('clearheap',
style='basic-style-extended-vertical',
label=_('empty heap'),
@@ -458,12 +488,12 @@ end\n')
logo_command='taclearheap',
help_string=_('emptys FILO (first-in-last-out \
heap)'))
- self.tw.lc.def_prim('clearheap', 0,
- lambda self: primitive_dictionary['clearheap']())
+ self.tw.lc.def_prim(
+ 'clearheap', 0,
+ Primitive(self.tw.lc.reset_heap, call_afterwards=self.after_pop))
define_logo_function('taclearheap', 'to taclearheap\nmake "taheap []\n\
end\n')
- primitive_dictionary['pop'] = self._prim_pop
palette.add_block('pop',
style='box-style',
#TRANS: pop removes a new item from the program stack
@@ -473,12 +503,13 @@ end\n')
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']())
+ self.tw.lc.def_prim(
+ 'pop', 0,
+ Primitive(self.tw.lc.heap.pop, return_type=TYPE_BOX,
+ call_afterwards=self.after_pop))
define_logo_function('tapop', 'to tapop\nif emptyp :taheap [stop]\n\
make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n')
- primitive_dictionary['isheapempty'] = self._prim_is_heap_empty
palette.add_block('isheapempty',
hidden=True,
style='box-style',
@@ -486,10 +517,15 @@ make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n')
prim_name='isheapempty',
value_block=True,
help_string=_('returns True if heap is empty'))
- self.tw.lc.def_prim('isheapempty', 0,
- lambda self: primitive_dictionary['isheapempty']())
+ self.tw.lc.def_prim(
+ 'isheapempty', 0,
+ Primitive(int, return_type=TYPE_INT,
+ arg_descs=[ConstantArg(
+ Primitive(Primitive.not_, return_type=TYPE_BOOL,
+ arg_descs=[ConstantArg(
+ Primitive(self.tw.lc.get_heap,
+ return_type=TYPE_BOOL))]))]))
- primitive_dictionary['saveheap'] = self._prim_save_heap
palette.add_block('saveheap',
style='basic-style-1arg',
label=_('save heap to file'),
@@ -498,9 +534,9 @@ make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n')
help_string=_('saves FILO (first-in \
last-out heap) to a file'))
self.tw.lc.def_prim('saveheap', 1,
- lambda self, x: primitive_dictionary['saveheap'](x))
+ Primitive(self.tw.lc.save_heap,
+ arg_descs=[ArgSlot(TYPE_STRING)]))
- primitive_dictionary['loadheap'] = self._prim_load_heap
palette.add_block('loadheap',
style='basic-style-1arg',
label=_('load heap from file'),
@@ -509,20 +545,26 @@ last-out heap) to a file'))
help_string=_('loads FILO (first-in \
last-out heap) from a file'))
self.tw.lc.def_prim('loadheap', 1,
- lambda self, x: primitive_dictionary['loadheap'](x))
+ Primitive(self.tw.lc.load_heap,
+ arg_descs=[ArgSlot(TYPE_STRING)],
+ return_type=TYPE_STRING,
+ call_afterwards=self.after_push))
- primitive_dictionary['isheapempty2'] = self._prim_is_heap_empty_bool
palette.add_block('isheapempty2',
style='boolean-block-style',
label=_('empty heap?'),
prim_name='isheapempty2',
value_block=True,
help_string=_('returns True if heap is empty'))
- self.tw.lc.def_prim('isheapempty2', 0,
- lambda self:
- primitive_dictionary['isheapempty2']())
+ self.tw.lc.def_prim(
+ 'isheapempty2', 0,
+ # Python automatically converts the heap to a boolean in contexts
+ # where a boolean is needed
+ Primitive(Primitive.not_, return_type=TYPE_BOOL,
+ arg_descs=[ConstantArg(
+ Primitive(self.tw.lc.get_heap,
+ return_type=TYPE_BOOL))]))
- primitive_dictionary['print'] = self._prim_print
palette.add_block('comment',
style='basic-style-1arg',
label=_('comment'),
@@ -530,9 +572,9 @@ last-out heap) from a file'))
default=_('comment'),
string_or_number=True,
help_string=_('places a comment in your code'))
- self.tw.lc.def_prim('comment', 1,
- lambda self, x:
- primitive_dictionary['print'](x, True))
+ self.tw.lc.def_prim(
+ 'comment', 1,
+ Primitive(Primitive.comment, arg_descs=[ArgSlot(TYPE_STRING)]))
palette.add_block('print',
style='basic-style-1arg',
@@ -542,27 +584,32 @@ last-out heap) from a file'))
string_or_number=True,
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))
+ self.tw.lc.def_prim(
+ 'print', 1,
+ Primitive(self.tw.print_,
+ arg_descs=[ArgSlot(TYPE_OBJECT), ConstantArg(False)]))
- primitive_dictionary['chr'] = self._prim_chr
palette.add_block('chr',
style='number-style-1arg',
label='chr',
prim_name='chr',
help_string=_('Python chr operator'))
- self.tw.lc.def_prim('chr', 1,
- lambda self, x: primitive_dictionary['chr'](x))
+ self.tw.lc.def_prim(
+ 'chr', 1,
+ Primitive(chr, return_type=TYPE_CHAR,
+ arg_descs=[ArgSlot(TYPE_INT)]))
- primitive_dictionary['int'] = self._prim_int
palette.add_block('int',
style='number-style-1arg',
label='int',
prim_name='int',
help_string=_('Python int operator'))
- self.tw.lc.def_prim('int', 1,
- lambda self, x: primitive_dictionary['int'](x))
+ self.tw.lc.def_prim(
+ 'int', 1,
+ # leave over the actual work to the type system, and just demand
+ # that the argument be converted to an integer
+ Primitive(Primitive.identity, return_type=TYPE_INT,
+ arg_descs=[ArgSlot(TYPE_INT)]))
palette.add_block('polar',
style='basic-style-extended-vertical',
@@ -572,7 +619,6 @@ bottom of the screen'))
self.tw.lc.def_prim('polar', 0,
lambda self: self.tw.set_polar(True))
- primitive_dictionary['myfunction'] = self._prim_myfunction
palette.add_block('myfunc1arg',
style='number-style-var-arg',
label=[_('Python'), 'f(x)', 'x'],
@@ -581,9 +627,10 @@ bottom of the screen'))
string_or_number=True,
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]))
+ self.tw.lc.def_prim(
+ 'myfunction', 2,
+ Primitive(self.tw.lc.prim_myfunction, return_type=TYPE_FLOAT,
+ arg_descs=[ArgSlot(TYPE_STRING), ArgSlot(TYPE_FLOAT)]))
palette.add_block('myfunc2arg',
hidden=True,
@@ -595,9 +642,11 @@ advanced single-variable math equations, e.g., sin(x)'))
string_or_number=True,
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]))
+ self.tw.lc.def_prim(
+ 'myfunction2', 3,
+ Primitive(self.tw.lc.prim_myfunction, return_type=TYPE_FLOAT,
+ arg_descs=[ArgSlot(TYPE_STRING), ArgSlot(TYPE_FLOAT),
+ ArgSlot(TYPE_FLOAT)]))
palette.add_block('myfunc3arg',
hidden=True,
@@ -609,9 +658,11 @@ advanced multi-variable math equations, e.g., sqrt(x*x+y*y)'))
string_or_number=True,
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]))
+ self.tw.lc.def_prim(
+ 'myfunction3', 4,
+ Primitive(self.tw.lc.prim_myfunction, return_type=TYPE_FLOAT,
+ arg_descs=[ArgSlot(TYPE_STRING), ArgSlot(TYPE_FLOAT),
+ ArgSlot(TYPE_FLOAT), ArgSlot(TYPE_FLOAT)]))
palette.add_block('cartesian',
style='basic-style-extended-vertical',
@@ -621,7 +672,6 @@ advanced multi-variable math equations, e.g., sin(x+y+z)'))
self.tw.lc.def_prim('cartesian', 0,
lambda self: self.tw.set_cartesian(True))
- primitive_dictionary['userdefined'] = self._prim_myblock
palette.add_block('userdefined',
style='basic-style-var-arg',
label=' ',
@@ -632,8 +682,8 @@ advanced multi-variable math equations, e.g., sin(x+y+z)'))
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]))
+ Primitive(self.tw.lc.prim_myblock,
+ arg_descs=[ArgSlot(TYPE_OBJECT)]))
BLOCKS_WITH_SKIN.append('userdefined')
PYTHON_SKIN.append('userdefined')
@@ -649,8 +699,9 @@ module found in the Journal'))
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]))
+ Primitive(self.tw.lc.prim_myblock,
+ arg_descs=[ArgSlot(TYPE_OBJECT),
+ ArgSlot(TYPE_OBJECT)]))
BLOCKS_WITH_SKIN.append('userdefined2args')
PYTHON_SKIN.append('userdefined2args')
@@ -666,15 +717,16 @@ module found in the Journal'))
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]))
+ Primitive(self.tw.lc.prim_myblock,
+ arg_descs=[ArgSlot(TYPE_OBJECT),
+ ArgSlot(TYPE_OBJECT),
+ ArgSlot(TYPE_OBJECT)]))
BLOCKS_WITH_SKIN.append('userdefined3args')
PYTHON_SKIN.append('userdefined3args')
MEDIA_SHAPES.append('pythonsmall')
MEDIA_SHAPES.append('pythonoff')
MEDIA_SHAPES.append('pythonon')
- primitive_dictionary['loadblock'] = self._prim_load_block
palette.add_block('loadblock',
style='basic-style-var-arg',
label=_('load'),
@@ -682,8 +734,9 @@ module found in the Journal'))
default=_('forward'),
help_string=_('loads a block'))
self.tw.lc.def_prim('loadblock', 1,
- lambda self, x:
- primitive_dictionary['loadblock'](x))
+ Primitive(self.tw.prim_load_block,
+ export_me=False,
+ arg_descs=[ArgSlot(TYPE_STRING)]))
palette.add_block('loadblock2arg',
style='basic-style-var-arg',
@@ -694,8 +747,10 @@ module found in the Journal'))
default=[_('forward'), 100],
help_string=_('loads a block'))
self.tw.lc.def_prim('loadblock2', 2,
- lambda self, x, y:
- primitive_dictionary['loadblock']([x, y]))
+ Primitive(self.tw.prim_load_block,
+ export_me=False,
+ arg_descs=[ArgSlot(TYPE_STRING),
+ ArgSlot(TYPE_OBJECT)]))
palette.add_block('loadblock3arg',
style='basic-style-var-arg',
@@ -706,10 +761,12 @@ module found in the Journal'))
default=[_('setxy'), 0, 0],
help_string=_('loads a block'))
self.tw.lc.def_prim('loadblock3', 3,
- lambda self, x, y, z:
- primitive_dictionary['loadblock']([x, y, z]))
+ Primitive(self.tw.prim_load_block,
+ export_me=False,
+ arg_descs=[ArgSlot(TYPE_STRING),
+ ArgSlot(TYPE_OBJECT),
+ ArgSlot(TYPE_OBJECT)]))
- primitive_dictionary['loadpalette'] = self._prim_load_palette
palette.add_block('loadpalette',
style='basic-style-1arg',
string_or_number=True,
@@ -718,8 +775,9 @@ module found in the Journal'))
default=_('turtle'),
help_string=_('selects a palette'))
self.tw.lc.def_prim('loadpalette', 1,
- lambda self, x:
- primitive_dictionary['loadpalette'](x))
+ Primitive(self.tw.prim_load_palette,
+ export_me=False,
+ arg_descs=[ArgSlot(TYPE_STRING)]))
palette.add_block('addturtle',
style='basic-style-1arg',
@@ -729,52 +787,56 @@ module found in the Journal'))
string_or_number=True,
help_string=_('chooses which turtle to command'))
self.tw.lc.def_prim('addturtle', 1,
- lambda self, x:
- self.tw.turtles.set_turtle(x))
+ Primitive(self.tw.lc.prim_turtle,
+ arg_descs=[ArgSlot(TYPE_STRING)]))
- primitive_dictionary['turtlex'] = self._prim_turtle_x
palette.add_block('turtlex',
style='number-style-1arg',
label=_('turtle x'),
prim_name='turtlex',
default=['Yertle'],
help_string=_('Returns x coordinate of turtle'))
- self.tw.lc.def_prim('turtlex', 1,
- lambda self, t: primitive_dictionary['turtlex'](t))
+ self.tw.lc.def_prim(
+ 'turtlex', 1,
+ Primitive(self.tw.turtles.get_turtle_x,
+ arg_descs=[ArgSlot(TYPE_OBJECT)],
+ return_type=TYPE_BOX))
- primitive_dictionary['turtley'] = self._prim_turtle_y
palette.add_block('turtley',
style='number-style-1arg',
label=_('turtle y'),
prim_name='turtley',
default=['Yertle'],
help_string=_('Returns y coordinate of turtle'))
- self.tw.lc.def_prim('turtley', 1,
- lambda self, t: primitive_dictionary['turtley'](t))
+ self.tw.lc.def_prim(
+ 'turtley', 1,
+ Primitive(self.tw.turtles.get_turtle_y,
+ arg_descs=[ArgSlot(TYPE_OBJECT)],
+ return_type=TYPE_BOX))
- primitive_dictionary['activeturtle'] = self._prim_active_turtle
palette.add_block('activeturtle',
style='box-style',
- #TRANS: pop removes a new item from the program stack
label=_('active turtle'),
prim_name='activeturtle',
value_block=True,
help_string=_('the name of the active turtle'))
- self.tw.lc.def_prim('activeturtle', 0,
- lambda self:
- primitive_dictionary['activeturtle']())
+ self.tw.lc.def_prim(
+ 'activeturtle', 0,
+ Primitive(Turtle.get_name,
+ return_type=TYPE_BOX))
- primitive_dictionary['turtleh'] = self._prim_turtle_h
palette.add_block('turtleh',
style='number-style-1arg',
label=_('turtle heading'),
prim_name='turtleh',
default=['Yertle'],
help_string=_('Returns heading of turtle'))
- self.tw.lc.def_prim('turtleh', 1,
- lambda self, t: primitive_dictionary['turtleh'](t))
+ self.tw.lc.def_prim(
+ 'turtleh', 1,
+ Primitive(self.tw.turtles.get_turtle_heading,
+ arg_descs=[ArgSlot(TYPE_OBJECT)],
+ return_type=TYPE_BOX))
- primitive_dictionary['skin'] = self._prim_reskin
palette.add_block('skin',
hidden=True,
colors=["#FF0000", "#A00000"],
@@ -783,8 +845,8 @@ module found in the Journal'))
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))
+ Primitive(self.tw.lc.reskin,
+ arg_descs=[ArgSlot(TYPE_OBJECT)]))
# macro
palette.add_block('reskin',
@@ -801,39 +863,40 @@ module found in the Journal'))
help_string=_('top of a collapsed stack'))
def _portfolio_palette(self):
- debug_output('creating %s palette' % _('portfolio'),
- self.tw.running_sugar)
+
palette = make_palette('portfolio',
colors=["#0606FF", "#0606A0"],
help_string=_('Palette of presentation \
templates'),
- position=9)
+ position=9,
+ translation=_('portfolio'))
- primitive_dictionary['hideblocks'] = self._prim_hideblocks
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: primitive_dictionary['hideblocks']())
+ self.tw.lc.def_prim(
+ 'hideblocks', 0,
+ Primitive(self._prim_hideblocks, export_me=False))
- primitive_dictionary['showblocks'] = self._prim_showblocks
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: primitive_dictionary['showblocks']())
+ self.tw.lc.def_prim(
+ 'showblocks', 0,
+ Primitive(self._prim_showblocks, export_me=False))
palette.add_block('fullscreen',
style='basic-style-extended-vertical',
label=_('Fullscreen').lower(),
prim_name='fullscreen',
help_string=_('hides the Sugar toolbars'))
- self.tw.lc.def_prim('fullscreen', 0,
- lambda self: self.tw.set_fullscreen())
+ self.tw.lc.def_prim(
+ 'fullscreen', 0,
+ Primitive(self.tw.set_fullscreen, export_me=False))
primitive_dictionary['bulletlist'] = self._prim_list
palette.add_block('list',
@@ -898,7 +961,10 @@ Journal objects'))
prim_name='lpos',
logo_command='lpos',
help_string=_('xcor of left of screen'))
- self.tw.lc.def_prim('lpos', 0, lambda self: CONSTANTS['leftpos'])
+ self.tw.lc.def_prim(
+ 'lpos', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('leftpos')]))
palette.add_block('bottompos',
style='box-style',
@@ -906,7 +972,10 @@ Journal objects'))
prim_name='bpos',
logo_command='bpos',
help_string=_('ycor of bottom of screen'))
- self.tw.lc.def_prim('bpos', 0, lambda self: CONSTANTS['bottompos'])
+ self.tw.lc.def_prim(
+ 'bpos', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('bottompos')]))
palette.add_block('width',
style='box-style',
@@ -914,7 +983,10 @@ Journal objects'))
prim_name='hres',
logo_command='width',
help_string=_('the canvas width'))
- self.tw.lc.def_prim('hres', 0, lambda self: CONSTANTS['width'])
+ self.tw.lc.def_prim(
+ 'hres', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('width')]))
palette.add_block('rightpos',
style='box-style',
@@ -922,7 +994,10 @@ Journal objects'))
prim_name='rpos',
logo_command='rpos',
help_string=_('xcor of right of screen'))
- self.tw.lc.def_prim('rpos', 0, lambda self: CONSTANTS['rightpos'])
+ self.tw.lc.def_prim(
+ 'rpos', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('rightpos')]))
palette.add_block('toppos',
style='box-style',
@@ -930,7 +1005,10 @@ Journal objects'))
prim_name='tpos',
logo_command='tpos',
help_string=_('ycor of top of screen'))
- self.tw.lc.def_prim('tpos', 0, lambda self: CONSTANTS['toppos'])
+ self.tw.lc.def_prim(
+ 'tpos', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('toppos')]))
palette.add_block('height',
style='box-style',
@@ -938,7 +1016,10 @@ Journal objects'))
prim_name='vres',
logo_command='height',
help_string=_('the canvas height'))
- self.tw.lc.def_prim('vres', 0, lambda self: CONSTANTS['height'])
+ self.tw.lc.def_prim(
+ 'vres', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('height')]))
palette.add_block('titlex',
hidden=True,
@@ -947,7 +1028,10 @@ Journal objects'))
label=_('title x'),
logo_command='titlex',
prim_name='titlex')
- self.tw.lc.def_prim('titlex', 0, lambda self: CONSTANTS['titlex'])
+ self.tw.lc.def_prim(
+ 'titlex', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('titlex')]))
palette.add_block('titley',
hidden=True,
@@ -956,7 +1040,10 @@ Journal objects'))
label=_('title y'),
logo_command='titley',
prim_name='titley')
- self.tw.lc.def_prim('titley', 0, lambda self: CONSTANTS['titley'])
+ self.tw.lc.def_prim(
+ 'titley', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('titley')]))
palette.add_block('leftx',
hidden=True,
@@ -965,7 +1052,10 @@ Journal objects'))
label=_('left x'),
prim_name='leftx',
logo_command='leftx')
- self.tw.lc.def_prim('leftx', 0, lambda self: CONSTANTS['leftx'])
+ self.tw.lc.def_prim(
+ 'leftx', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('leftx')]))
palette.add_block('topy',
hidden=True,
@@ -974,7 +1064,10 @@ Journal objects'))
label=_('top y'),
prim_name='topy',
logo_command='topy')
- self.tw.lc.def_prim('topy', 0, lambda self: CONSTANTS['topy'])
+ self.tw.lc.def_prim(
+ 'topy', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('topy')]))
palette.add_block('rightx',
hidden=True,
@@ -983,7 +1076,10 @@ Journal objects'))
label=_('right x'),
prim_name='rightx',
logo_command='rightx')
- self.tw.lc.def_prim('rightx', 0, lambda self: CONSTANTS['rightx'])
+ self.tw.lc.def_prim(
+ 'rightx', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('rightx')]))
palette.add_block('bottomy',
hidden=True,
@@ -992,7 +1088,10 @@ Journal objects'))
label=_('bottom y'),
prim_name='bottomy',
logo_command='bottomy')
- self.tw.lc.def_prim('bottomy', 0, lambda self: CONSTANTS['bottomy'])
+ self.tw.lc.def_prim(
+ 'bottomy', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('bottomy')]))
def _myblocks_palette(self):
''' User-defined macros are saved as a json-encoded file;
@@ -1002,12 +1101,11 @@ Journal objects'))
os.path.exists(self.tw.macros_path):
files = glob.glob(os.path.join(self.tw.macros_path, '*.tb'))
if len(files) > 0:
- debug_output('creating %s palette' % _('my blocks'),
- self.tw.running_sugar)
palette = make_palette(
- 'my blocks',
+ 'myblocks',
colors=["#FFFF00", "#A0A000"],
- help_string=_('Palette of user-defined operators'))
+ help_string=_('Palette of user-defined operators'),
+ translation=_('my blocks'))
for tafile in files:
data = data_from_file(tafile)
@@ -1020,360 +1118,36 @@ Journal objects'))
# Block primitives
- def _prim_emptyheap(self):
- """ Empty FILO """
- self.tw.lc.heap = []
-
- def _prim_keyboard(self):
- """ Return last character typed """
- return self.tw.lc.keyboard
-
- def _prim_kbinput(self):
- """ Query keyboard """
- DICT = {
- 'Left': 1,
- 'KP_Left': 1,
- 'Up': 2,
- 'KP_Up': 2,
- 'Right': 3,
- 'KP_Right': 3,
- 'Down': 4,
- 'KP_Down': 4,
- 'BackSpace': 8,
- 'Tab': 9,
- 'Return': 13,
- 'Escape': 27,
- 'space': 32,
- ' ': 32,
- 'exclam': 33,
- 'quotedbl': 34,
- 'numbersign': 35,
- 'dollar': 36,
- 'percent': 37,
- 'ampersand': 38,
- 'apostrophe': 39,
- 'parenleft': 40,
- 'parenright': 41,
- 'asterisk': 42,
- 'plus': 43,
- 'comma': 44,
- 'minus': 45,
- 'period': 46,
- 'slash': 47,
- 'colon': 58,
- 'semicolon': 59,
- 'less': 60,
- 'equal': 61,
- 'greater': 62,
- 'question': 63,
- 'at': 64,
- 'underscore': 95,
- 'bracketleft': 91,
- 'backslash': 92,
- 'bracketright': 93,
- 'asciicircum': 94,
- 'grave': 96,
- 'braceleft': 123,
- 'bar': 124,
- 'braceright': 125,
- 'asciitilde': 126,
- 'Delete': 127,
- }
- REVERSE_DICT = {
- 1: _('left'),
- 2: _('up'),
- 3: _('right'),
- 4: _('down'),
- 8: _('backspace'),
- 9: _('tab'),
- # TRANS: enter is the name of the enter (or return) key
- 13: _('enter'),
- 27: 'esc',
- # TRANS: space is the name of the space key
- 32: _('space'),
- 127: _('delete')
- }
-
- if len(self.tw.keypress) == 1:
- self.tw.lc.keyboard = ord(self.tw.keypress[0])
- elif self.tw.keypress in DICT:
- self.tw.lc.keyboard = DICT[self.tw.keypress]
- else:
- self.tw.lc.keyboard = 0
+ def after_keypress(self):
if self.tw.lc.update_values:
- if self.tw.keypress in DICT:
- if DICT[self.tw.keypress] in REVERSE_DICT:
+ if self.tw.keypress in KEY_DICT:
+ if KEY_DICT[self.tw.keypress] in REVERSE_KEY_DICT:
self.tw.lc.update_label_value(
- 'keyboard', REVERSE_DICT[DICT[self.tw.keypress]])
+ 'keyboard', REVERSE_KEY_DICT[
+ KEY_DICT[self.tw.keypress]])
else:
- self.tw.lc.update_label_value('keyboard',
- chr(DICT[self.tw.keypress]))
- elif self.tw.lc.keyboard > 0:
+ self.tw.lc.update_label_value(
+ 'keyboard', chr(KEY_DICT[self.tw.keypress]))
+ elif self.tw.keyboard > 0:
self.tw.lc.update_label_value('keyboard',
- chr(self.tw.lc.keyboard))
+ chr(self.tw.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 """
- for i, v in enumerate(x):
- if type(v) == int: # Pass float values to Python block
- x[i] = float(v)
- 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_is_heap_empty(self):
- """ is FILO empty? """
- if len(self.tw.lc.heap) == 0:
- return 1
- else:
- return 0
-
- def _prim_is_heap_empty_bool(self):
- """ is FILO empty? """
- if len(self.tw.lc.heap) == 0:
- return True
- else:
- return False
-
- def _prim_pop(self):
- """ Pop value off of FILO """
- if len(self.tw.lc.heap) == 0:
- raise logoerror("#emptyheap")
- else:
- if self.tw.lc.update_values:
- 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) == list:
- self.tw.showlabel('print', n)
- elif type(n) == str or type(n) == unicode:
- if n in COLORDICT:
- if COLORDICT[n][0] is None:
- self.tw.showlabel('print', '%s %d, %s %d' %
- (_('shade'), COLORDICT[n][1],
- _('gray'), COLORDICT[n][2]))
- else:
- self.tw.showlabel('print', '%s %d, %s %d, %s %d' %
- (_('color'), COLORDICT[n][0],
- _('shade'), COLORDICT[n][1],
- _('gray'), COLORDICT[n][2]))
- elif n[0:6] == 'media_' and \
- n[6:].lower not in media_blocks_dictionary:
- try:
- if self.tw.running_sugar:
- from sugar.datastore import datastore
- try:
- dsobject = datastore.get(n[6:])
- except:
- debug_output("Couldn't open %s" % (n[6:]),
- self.tw.running_sugar)
- self.tw.showlabel('print', dsobject.metadata['title'])
- dsobject.destroy()
- else:
- self.tw.showlabel('print', n[6:])
- except IOError:
- self.tw.showlabel('print', n)
+ def after_pop(self, *ignored_args):
+ if self.tw.lc.update_values:
+ if not self.tw.lc.heap:
+ self.tw.lc.update_label_value('pop')
else:
- self.tw.showlabel('print', n)
- elif type(n) == int:
- self.tw.showlabel('print', n)
- else:
- self.tw.showlabel(
- 'print',
- 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('print', str(self.tw.lc.heap)[0:79] + '…')
- else:
- self.tw.showlabel('print', str(self.tw.lc.heap))
-
- def _prim_load_heap(self, path):
- """ Load FILO from file """
- if type(path) == float:
- path = ''
- if self.tw.running_sugar:
- # Choose a datastore object and push data to heap (Sugar only)
- chooser_dialog(self.tw.parent, path,
- self.tw.lc.push_file_data_to_heap)
- else:
- if not os.path.exists(path):
- path, tw.load_save_folder = get_load_name(
- '.*', self.tw.load_save_folder)
- if path is None:
- return
-
- data = data_from_file(path)
- if data is not None:
- for val in data:
- self.tw.lc.heap.append(val)
-
- if len(self.tw.lc.heap) > 0:
- self.tw.lc.update_label_value('pop', self.tw.lc.heap[-1])
-
- def _prim_save_heap(self, path):
- """ save FILO to file """
- # TODO: add GNOME save
-
- if self.tw.running_sugar:
- from sugar import profile
- from sugar.datastore import datastore
- from sugar.activity import activity
-
- # Save JSON-encoded heap to temporary file
- heap_file = os.path.join(get_path(activity, 'instance'),
- str(path) + '.txt')
- data_to_file(self.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(path)
- dsobject.metadata['icon-color'] = profile.get_color().to_string()
- dsobject.metadata['mime_type'] = 'text/plain'
- dsobject.set_file_path(heap_file)
- datastore.write(dsobject)
- dsobject.destroy()
- else:
- heap_file = path
- data_to_file(self.tw.lc.heap, heap_file)
+ self.tw.lc.update_label_value('pop', self.tw.lc.heap[-1])
- def _prim_push(self, val):
- """ Push value onto FILO """
- self.tw.lc.heap.append(val)
+ def after_push(self, *ignored_args):
if self.tw.lc.update_values:
- 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.turtles.get_active_turtle().get_pixel()
- self.tw.lc.heap.append(b)
- self.tw.lc.heap.append(g)
- self.tw.lc.heap.append(r)
-
- def _prim_active_turtle(self):
- return(self.tw.turtles.get_active_turtle().get_name())
-
- 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?
- from sugar.datastore import datastore
- 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 is 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.turtles.get_active_turtle().set_shapes([pixbuf])
- pen_state = self.tw.turtles.get_active_turtle().get_pen_state()
- if pen_state:
- self.tw.turtles.get_active_turtle().set_pen_state(False)
- self.tw.turtles.get_active_turtle().forward(0)
- if pen_state:
- self.tw.turtles.get_active_turtle().set_pen_state(True)
-
- if self.tw.sharing():
- if self.tw.running_sugar:
- tmp_path = get_path(self.tw.activity, 'instance')
+ if not self.tw.lc.heap:
+ self.tw.lc.update_label_value('pop')
else:
- tmp_path = '/tmp'
- tmp_file = os.path.join(get_path(self.tw.activity, 'instance'),
- 'tmpfile.png')
- pixbuf.save(tmp_file, 'png', {'quality': '100'})
- data = image_to_base64(tmp_file, tmp_path)
- height = pixbuf.get_height()
- width = pixbuf.get_width()
- event = 'R|%s' % (data_to_string([self.tw.nick,
- [round_int(width),
- round_int(height),
- data]]))
- gobject.idle_add(self.tw.send_event, event)
- os.remove(tmp_file)
-
- 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.save_as_image(name, svg=True)
-
- def _prim_speak(self, text):
+ self.tw.lc.update_label_value('pop', self.tw.lc.heap[-1])
+
+ def prim_speak(self, text):
""" Speak text """
if type(text) == float and int(text) == text:
text = int(text)
@@ -1393,7 +1167,7 @@ Journal objects'))
language_option, text]))
self.tw.send_event(event)
- def _prim_sinewave(self, pitch, amplitude, duration):
+ def prim_sinewave(self, pitch, amplitude, duration):
""" Create a Csound score to play a sine wave. """
self.orchlines = []
self.scorelines = []
@@ -1467,139 +1241,34 @@ Journal objects'))
csd.write("\n</CsoundSynthesizer>")
csd.close()
- def _prim_mouse_x(self):
- """ Return mouse x coordinate """
- mousex = int(self.tw.mouse_x - (self.tw.canvas.width / 2))
+ def after_mouse_x(self):
+ """ Show mouse x coordinate """
if self.tw.lc.update_values:
- self.tw.lc.update_label_value('mousex', mousex)
- return mousex
+ self.tw.lc.update_label_value('mousex', self.tw.get_mouse_x())
- def _prim_mouse_y(self):
- """ Return mouse y coordinate """
- mousey = int((self.tw.canvas.height / 2) - self.tw.mouse_y)
+ def after_mouse_y(self):
+ """ Show mouse y coordinate """
if self.tw.lc.update_values:
- self.tw.lc.update_label_value('mousey', mousey)
- return mousey
+ self.tw.lc.update_label_value('mousey', self.tw.get_mouse_y())
- def _prim_mouse_button(self):
- """ Return 1 if mouse button is pressed """
- if self.tw.mouse_flag == 1:
- return 1
- else:
- return 0
-
- def _prim_mouse_button_bool(self):
- """ Return True if mouse button is pressed """
- if self.tw.mouse_flag == 1:
- return True
- else:
- return False
-
- def _prim_see(self):
- """ Read r, g, b from the canvas and return a corresponding palette
- color """
- r, g, b, a = self.tw.turtles.get_active_turtle().get_pixel()
- color_index = self.tw.canvas.get_color_index(r, g, b)
+ def after_see(self):
+ """ Show color under turtle """
if self.tw.lc.update_values:
- self.tw.lc.update_label_value('see', color_index)
- return color_index
+ self.tw.lc.update_label_value(
+ 'see',
+ self.tw.turtles.get_active_turtle().get_color_index())
- def _prim_setscale(self, scale):
- """ Set the scale used by the show block """
- self.tw.lc.scale = scale
- if self.tw.lc.update_values:
- 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.pixbuf = None # Camera writes directly to pixbuf
- 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?
- from sugar.datastore import datastore
- 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.pixbuf is not None:
- self.tw.lc.insert_image(center=center, pixbuf=True)
- elif self.tw.lc.filepath is 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=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.turtles.get_active_turtle().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.turtles.get_active_turtle().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.turtles.get_active_turtle().get_xy()[0] /
- self.tw.coord_scale)
- y = (self.tw.turtles.get_active_turtle().get_xy()[1] /
- self.tw.coord_scale)
- for s in sarray:
- self.tw.turtles.get_active_turtle().set_xy(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)
+ def _prim_list(self, blklist):
+ """ Expandable list block """
+ self.tw.lc.showlist(blklist)
+ self.tw.lc.ireturn()
+ yield True
+
+ def after_time(self, elapsed_time):
+ """ Update the label of the 'time' block after computing the new
+ value. """
if self.tw.lc.update_values:
self.tw.lc.update_label_value('time', elapsed_time)
- return elapsed_time
def _prim_hideblocks(self):
""" hide blocks and show showblocks button """
@@ -1619,137 +1288,8 @@ Journal objects'))
self.tw.activity.stop_turtle_button.set_icon("stopiton")
self.tw.activity.stop_turtle_button.set_tooltip(_('Stop turtle'))
- def _prim_chr(self, x):
- """ Chr conversion """
- try:
- return chr(int(x))
- except ValueError:
- self.tw.lc.stop_logo()
- raise logoerror("#notanumber")
-
- def _prim_int(self, x):
- """ Int conversion """
- try:
- return int(x)
- except ValueError:
- self.tw.lc.stop_logo()
- raise logoerror("#notanumber")
-
- def _prim_turtle_x(self, t):
- """ Return x coordinate of turtle t """
- return self.tw.turtles.get_turtle_x(t)
-
- def _prim_turtle_y(self, t):
- """ Return y coordinate of turtle t """
- return self.tw.turtles.get_turtle_y(t)
-
- def _prim_turtle_h(self, t):
- """ Return heading of turtle t """
- return self.tw.turtles.get_turtle_heading(t)
-
- def _prim_clamp(self, blklist):
- """ Run clamp blklist """
- self.tw.lc.icall(self.tw.lc.evline, blklist[:])
- yield True
- self.tw.lc.procstop = False
- self.tw.lc.ireturn()
- yield True
-
- def _prim_load_block(self, blkname):
- ''' Load a block on to the canvas '''
- # Place the block at the active turtle (x, y) and move the turtle
- # into position to place the next block in the stack.
- # TODO: Add expandable argument
- pos = self.tw.turtles.get_active_turtle().get_xy()
- if isinstance(blkname, list):
- name = blkname[0]
- if len(blkname) > 1:
- value = blkname[1:]
- dy = int(self._find_block(name, pos[0], pos[1], value))
- else:
- dy = int(self._find_block(name, pos[0], pos[1]))
- else:
- name = blkname
- if name == 'delete':
- for blk in self.tw.just_blocks():
- if blk.status == 'load block':
- blk.type = 'trash'
- blk.spr.hide()
- dy = 0
- else:
- dy = int(self._find_block(name, pos[0], pos[1]))
-
- # Reposition turtle to end of flow
- pos = self.tw.turtles.get_active_turtle().get_xy()
- pos[1] -= dy
- self.tw.turtles.get_active_turtle().move_turtle(pos)
-
- def _make_block(self, name, x, y, defaults):
- if defaults is None:
- self.tw._new_block(name, x, y, defaults)
- else:
- for i, v in enumerate(defaults):
- if type(v) == float and int(v) == v:
- defaults[i] = int(v)
- self.tw._new_block(name, x, y, defaults)
-
- # Find the block we just created and attach it to a stack.
- self.tw.drag_group = None
- spr = self.tw.sprite_list.find_sprite((x, y))
- if spr is not None:
- blk = self.tw.block_list.spr_to_block(spr)
- if blk is not None:
- self.tw.drag_group = find_group(blk)
- for b in self.tw.drag_group:
- b.status = 'load block'
- self.tw._snap_to_dock()
-
- # Disassociate new block from mouse.
- self.tw.drag_group = None
- return blk.docks[-1][3]
-
- def _find_block(self, 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. """
- x, y = self.tw.turtles.turtle_to_screen_coordinates((x, y))
- for name in block_names:
- # Translate label name into block/prim name.
- if blkname in block_names[name]: # block label is an array
- # print 'found a match', blkname, name, block_names[name]
- if name in content_blocks or \
- (name in block_primitives and
- block_primitives[name] == name):
- # print '_make_block', blkname, name
- return self._make_block(name, x, y, defaults)
- elif blkname in block_names:
- # print '_make_block', blkname
- return self._make_block(blkname, x, y, defaults)
- for name in special_names:
- # Translate label name into block/prim name.
- if blkname in special_names[name]:
- return self._make_block(name, x, y, defaults)
- # Check for a macro
- if blkname in MACROS:
- self.tw.new_macro(blkname, x, y)
- return 0 # Fix me: calculate flow position
- # Block not found
- raise logoerror("#syntaxerror")
- return -1
-
- def _prim_load_palette(self, arg):
- ''' Select a palette '''
- if type(arg) in [int, float]:
- if int(arg) < 0 or int(arg) > len(palette_names):
- raise logoerror("#syntaxerror")
- else:
- self.tw.show_toolbar_palette(int(arg))
- else:
- if type(arg) == unicode:
- arg = arg.encode('utf-8')
- if arg in palette_names or arg in palette_i18n_names:
- self.tw.show_toolbar_palette(palette_name_to_index(arg))
- else:
- raise logoerror("#syntaxerror")
+ def after_set(self, name, value=None):
+ ''' Update the associated value blocks '''
+ if value is not None:
+ if self.tw.lc.update_values:
+ self.tw.lc.update_label_value(name, value)
diff --git a/pyexported/__init__.py b/pyexported/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pyexported/__init__.py
diff --git a/pyexported/window_setup.py b/pyexported/window_setup.py
new file mode 100644
index 0000000..537022a
--- /dev/null
+++ b/pyexported/window_setup.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+
+import cairo
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+
+from gettext import gettext as _
+
+import os
+import sys
+
+from TurtleArt.tablock import Media
+from TurtleArt.taconstants import CONSTANTS
+from TurtleArt.tatype import *
+from TurtleArt.tawindow import TurtleArtWindow
+
+
+# search sys.path for a dir containing TurtleArt/tawindow.py
+# path to the toplevel directory of the TA installation
+_TA_INSTALLATION_PATH = None
+for path in sys.path:
+ try:
+ entries = os.listdir(path)
+ except OSError:
+ continue
+ if "TurtleArt" in entries:
+ new_path = os.path.join(path, "TurtleArt")
+ try:
+ new_entries = os.listdir(new_path)
+ except OSError:
+ continue
+ if "tawindow.py" in new_entries:
+ _TA_INSTALLATION_PATH = path
+ break
+# if the TA installation path was not found, notify the user and refuse to run
+if _TA_INSTALLATION_PATH is None:
+ print _("The path to the TurtleArt installation must be listed in the "
+ "environment variable PYTHONPATH.")
+ exit(1)
+
+_PLUGIN_SUBPATH = 'plugins'
+_MACROS_SUBPATH = 'macros'
+
+
+
+class DummyTurtleMain(object):
+ """Keep the main objects for running a dummy TA window in one place.
+ (Try not to have to inherit from turtleblocks.TurtleMain.)
+ """
+
+ def __init__(self, win, name="exported project"):
+ """Create a scrolled window to contain the turtle canvas.
+ win -- a GTK toplevel window
+ """
+ self.win = win
+ self.set_title = self.win.set_title
+
+ # setup a scrolled container for the canvas
+ self.vbox = gtk.VBox(False, 0)
+ self.vbox.show()
+ self.sw = gtk.ScrolledWindow()
+ self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.sw.show()
+ self.canvas = gtk.DrawingArea()
+ width = gtk.gdk.screen_width() * 2
+ height = gtk.gdk.screen_height() * 2
+ self.canvas.set_size_request(width, height)
+ self.sw.add_with_viewport(self.canvas)
+ self.canvas.show()
+ self.vbox.pack_end(self.sw, True, True)
+ self.win.add(self.vbox)
+ self.win.show_all()
+
+ # exported code is always in interactive mode
+ interactive = True
+
+ # copied from turtleblocks.TurtleMain._build_window()
+ if interactive:
+ gdk_win = self.canvas.get_window()
+ cr = gdk_win.cairo_create()
+ surface = cr.get_target()
+ else:
+ img_surface = cairo.ImageSurface(cairo.FORMAT_RGB24,
+ 1024, 768)
+ cr = cairo.Context(img_surface)
+ surface = cr.get_target()
+ self.turtle_canvas = surface.create_similar(
+ cairo.CONTENT_COLOR, max(1024, gtk.gdk.screen_width() * 2),
+ max(768, gtk.gdk.screen_height() * 2))
+
+
+
+ # instantiate an instance of a dummy sub-class that supports only
+ # the stuff TurtleGraphics needs
+ # TODO don't hardcode running_sugar
+ self.tw = TurtleArtWindow(self.canvas, _TA_INSTALLATION_PATH,
+ turtle_canvas=self.turtle_canvas,
+ parent=self, running_sugar=False,
+ running_turtleart=False)
+
+ self.name = name
+
+
+ def _quit_ta(self, widget=None, e=None):
+ """Quit all plugins and the main window. No need to prompt the user
+ to save their work, since they cannot change anything.
+ """
+ for plugin in self.tw.turtleart_plugins:
+ if hasattr(plugin, 'quit'):
+ plugin.quit()
+ gtk.main_quit()
+ exit()
+
+
+def get_tw():
+ """ Create a GTK window and instantiate a DummyTurtleMain instance. Return
+ the TurtleArtWindow object that holds the turtles and the canvas.
+ """
+ # copied from turtleblocks.TurtleMain._setup_gtk()
+
+ win = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ gui = DummyTurtleMain(win=win, name=sys.argv[0])
+ # TODO re-enable this code (after giving gui the right attributes)
+ # win.set_default_size(gui.width, gui.height)
+ # win.move(gui.x, gui.y)
+ win.maximize()
+ win.set_title(str(gui.name))
+ # if os.path.exists(os.path.join(gui._execdirname, gui._ICON_SUBPATH)):
+ # win.set_icon_from_file(os.path.join(gui._execdirname,
+ # gui._ICON_SUBPATH))
+ win.show()
+ win.connect('delete_event', gui._quit_ta)
+
+ return gui.tw
+
+
diff --git a/pysamples/brain.py b/pysamples/brain.py
index 33ee320..3510e03 100644
--- a/pysamples/brain.py
+++ b/pysamples/brain.py
@@ -21,7 +21,7 @@
# <http://www.gnu.org/licenses/>.
-def myblock(tw, text):
+def myblock(tw, args):
''' Dialog with AIML library: Usage: Load this code into a Python
Block. Pass text as an argument and the robot's response will
be pushed to the stack. Use a Pop Block to pop the response
@@ -104,6 +104,7 @@ Close other activities and try once more.'))
return kernel
+ text = args[0]
if not hasattr(tw, 'aiml_kernel'):
tw.aiml_kernel = brain_load(tw, get_default_voice())
response_text = brain_respond(tw.aiml_kernel, text)
diff --git a/pysamples/csound.py b/pysamples/csound.py
index a935674..ec1b014 100644
--- a/pysamples/csound.py
+++ b/pysamples/csound.py
@@ -17,16 +17,24 @@ def myblock(tw, sound):
import os
dirs = [os.path.join(
- os.environ['HOME'],
- 'Activities/TamTamMini.activity/common/Resources/Sounds/')]
+ os.environ['HOME'],
+ 'Activities/TamTamMini.activity/common/Resources/Sounds/'),
+ os.path.join(
+ os.environ['HOME'],
+ 'Activities/TamTamJam.activity/common/Resources/Sounds/'),
+ os.path.join(
+ os.environ['HOME'],
+ 'Activities/TamTamEdit.activity/common/Resources/Sounds/')]
orchlines = []
scorelines = []
instrlist = []
def finddir():
+ print dirs
for d in dirs:
if os.path.isdir(d):
return d
+ return '.'
def playSine(pitch=1000, amplitude=5000, duration=1, starttime=0,
pitch_envelope='default', amplitude_envelope='default'):
@@ -128,18 +136,17 @@ def myblock(tw, sound):
csd.write("\n</CsoundSynthesizer>")
csd.close()
- if type(sound) == float:
- playSine(pitch=float(sound))
- elif type(sound) == list: # Create a score by computing a sinewave.
- if len(sound) == 1:
+ if len(sound) == 1:
+ if isinstance(sound[0], float) or isinstance(sound[0], int):
playSine(pitch=float(sound[0]))
- elif len(sound) == 2:
+ else: # Create a score from a prerecorded Wave file.
+ playWave(sound[0])
+ else:
+ if len(sound) == 2:
playSine(pitch=float(sound[0]), amplitude=float(sound[1]))
else:
playSine(pitch=float(sound[0]), amplitude=float(sound[1]),
duration=float(sound[2]))
- else: # Create a score from a prerecorded Wave file.
- playWave(sound)
if tw.running_sugar:
path = os.path.join(get_path(tw.activity, 'instance'), 'tmp.csd')
else:
diff --git a/pysamples/dotted_line.py b/pysamples/dotted_line.py
index febd409..098e0a4 100644
--- a/pysamples/dotted_line.py
+++ b/pysamples/dotted_line.py
@@ -1,4 +1,4 @@
-#Copyright (c) 2009-11, Walter Bender
+#Copyright (c) 2009-13, Walter Bender
# 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
@@ -52,43 +52,57 @@
# clearscreen(self) tw.canvas.clearscreen()
# Note: Clears the screen and resets all turtle and
# pen attributes to default values
-# setpen(self, flag) tw.canvas.setpen(True)
+#
+#
+# Class Turtles -- useful properties and methods (from within
+# tamyblock.py, tw.turtles is the class instance)
+#
+# self.set_turtle(name) tw.turltes.set_turtle(1)
+# Note: Set the current turtle to turtle '1'
+# self.get_active_turtle() tw.turltes.get_active_turtle()
+# Note: Returns active turtle (See Class Turtle below)
+#
+# Class Turtle -- useful properties and methods (from within
+# tamyblock.py, tw.turtles.get_active_turtle() is the class instance)
+#
+# set_pen_state(self, flag)
+# tw.turtles.get_active_turtle().set_pen_state(True)
# Note: True will set the pen "down", enabling drawing;
# False will set the pen "up"
-# forward(self, n) tw.canvas.forward(100)
+# forward(self, n) tw.turtles.get_active_turtle().forward(100)
# Note: Move the turtle forward 100 units
-# arc(self, a, r) tw.canvas.arc(120, 50)
+# arc(self, a, r) tw.turtles.get_active_turtle().arc(120, 50)
# Note: Move the turtle along an arc of 120 degrees
# (clockwise) and radius of 50 units
-# setheading(self, a) tw.canvas.setheading(180)
+# set_heading(self, a) tw.turtles.get_active_turtle().set_heading(180)
# Note: Set the turtle heading to 180
# (towards the bottom of the screen)
-# self.heading tw.canvas.heading
+# self.get_heading() tw.turtles.get_active_turtle().get_heading()
# Note: The current heading
-# setpensize(self, n) tw.canvas.setpensize(25)
+# set_pen_size(self, n) tw.turtles.get_active_turtle().set_pen_size(25)
# Note: Set the turtle pensize to 25 units
-# self.pensize tw.canvas.pensize
-# Note: The current pensize
-# setcolor(self, c) tw.canvas.color(70)
+# self.get_pen_size() tw.turtles.get_active_turtle().get_pen_size()
+# Note: The current pen size
+# self.set_color(self, c) tw.turtles.get_active_turtle().set_color(70)
# Note: Set the pen color to 70 (blue)
-# self.color tw.canvas.color
+# self.color() tw.turtles.get_active_turtle().get_color()
# Note: The current pen color
-# setshade(self, s) tw.canvas.shade(50)
+# self.set_shade(self, s) tw.turtles.get_active_turtle().set_shade(50)
# Note: Set the pen shade to 50
-# self.shade tw.canvas.shade
+# self.get_shade() tw.turtles.get_active_turtle().get_shade()
# Note: The current pen shade
-# fillscreen(self, c, s) tw.canvas.fillscreen(70, 90)
+# fillscreen(self, c, s)
+# tw.turtles.get_active_turtle().fillscreen(70, 90)
# Note: Fill the screen with color 70, shade 90 (light blue)
-# setxy(self, x, y) tw.canvas.setxy(100,100)
+# self.set_xy(self, x, y)
+# tw.turtles.get_active_turtle().set_xy(100,100)
# Note: Move the turtle to position (100, 100)
-# self.xcor tw.canvas.xcor
+# self.get_xy tw.turtles.get_active_turtle().get_xy()[0]
# Note: The current x coordinate of the turtle
# (scaled to current units)
-# self.ycor tw.canvas.ycor
+# self.get_xy tw.turtles.get_active_turtle().get_xy()[1]
# Note: The current y coordinate of the turtle
# (scaled to current units)
-# self.set_turtle(name) tw.canvas.set_turtle(1)
-# Note: Set the current turtle to turtle '1'
#
#
# Other useful Python functions
@@ -110,24 +124,25 @@
# of the numeric argument block docked to the Python block.
-def myblock(tw, line_length):
+def myblock(tw, args):
''' Draw a dotted line of length line_length. '''
try: # make sure line_length is a number
- line_length = float(line_length)
+ line_length = float(args[0])
except ValueError:
return
- if tw.canvas.pendown:
+ if tw.turtles.get_active_turtle().get_pen_state():
dist = 0
- 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)
+ pen_size = tw.turtles.get_active_turtle().get_pen_size()
+ while dist + pen_size < line_length: # repeat drawing dots
+ tw.turtles.get_active_turtle().set_pen_state(True)
+ tw.turtles.get_active_turtle().forward(1)
+ tw.turtles.get_active_turtle().set_pen_state(False)
+ tw.turtles.get_active_turtle().forward(pen_size * 2 - 1)
+ dist += pen_size * 2
# make sure we have moved exactly line_length
- tw.canvas.forward(line_length - dist)
- tw.canvas.setpen(True)
+ tw.turtles.get_active_turtle().forward(line_length - dist)
+ tw.turtles.get_active_turtle().set_pen_state(True)
else:
- tw.canvas.forward(line_length)
+ tw.turtles.get_active_turtle().forward(line_length)
return
diff --git a/pysamples/forward_push.py b/pysamples/forward_push.py
index b98c726..80fe818 100644
--- a/pysamples/forward_push.py
+++ b/pysamples/forward_push.py
@@ -1,13 +1,16 @@
-#Copyright (c) 2012, Walter Bender
+#Copyright (c) 2012-2013, Walter Bender
# Usage: Import this code into a Python (user-definable) block; when
-# this code is run, the turtle will draw a line of the length of the
-# numeric argument block docked to the Python block. But before
-# drawing the line, it pushes the rgb values of the destination to the
-# FILO.
+# this code is run, a new block will be added to the Turtle Palette.
+# This block will be named 'name', the value of the argument block
+# docked to the Python block.
+#
+# The new block will cause the turtle to draw a line of the
+# length. But before drawing the line, it pushes the rgb values of the
+# destination to the FILO.
-def myblock(tw, name):
+def myblock(tw, args):
''' '''
def _prim_forward_push(tw, line_length):
@@ -15,16 +18,16 @@ def myblock(tw, name):
line_length = float(line_length)
except ValueError:
return
- penstatus = tw.canvas.pendown
- tw.canvas.setpen(False)
- tw.canvas.forward(line_length)
- r, g, b, a = tw.canvas.get_pixel()
+ penstatus = tw.turtles.get_active_turtle().get_pen_status()
+ tw.turtles.get_active_turtle().set_pen_state(False)
+ tw.turtles.get_active_turtle().forward(line_length)
+ r, g, b, a = tw.turtles.get_active_turtle().get_pixel()
tw.lc.heap.append(b)
tw.lc.heap.append(g)
tw.lc.heap.append(r)
- tw.canvas.forward(-line_length)
- tw.canvas.setpen(penstatus)
- tw.canvas.forward(line_length)
+ tw.turtles.get_active_turtle().forward(-line_length)
+ tw.turtles.get_active_turtle().set_pen_state(penstatus)
+ tw.turtles.get_active_turtle().forward(line_length)
return
from TurtleArt.tapalette import make_palette, palette_name_to_index
@@ -39,7 +42,7 @@ def myblock(tw, name):
# Create a new block prototype.
palette.add_block('forwardpush',
style='basic-style-1arg',
- label=name,
+ label=args[0],
default=100,
prim_name='forwardpush',
help_string=_('push destination rgb value to heap'))
diff --git a/pysamples/grecord.py b/pysamples/grecord.py
index a28b82c..486fdb7 100644
--- a/pysamples/grecord.py
+++ b/pysamples/grecord.py
@@ -10,7 +10,7 @@
# Sugar Journal.
-def myblock(tw, arg):
+def myblock(tw, args):
''' Record and playback a sound (Sugar only) '''
import os
import gst
@@ -204,12 +204,9 @@ def myblock(tw, arg):
# Sometime we need to parse multiple arguments, e.g., save, savename
save_name = '%s_%s' % (tw.activity.name, _('sound'))
- if isinstance(arg, list):
- cmd = arg[0].lower()
- if len(arg) > 1:
- save_name = str(arg[1])
- else:
- cmd = arg.lower()
+ cmd = args[0].lower()
+ if len(args) > 1:
+ save_name = str(arg[1])
if cmd == 'start' or cmd == _('start').lower():
tw.grecord.start_recording_audio()
diff --git a/pysamples/serial.py b/pysamples/serial.py
index 84772ab..d15011c 100644
--- a/pysamples/serial.py
+++ b/pysamples/serial.py
@@ -9,13 +9,13 @@
# (3) use a Pop Block to retrieve any strings input from serial device.
-def myblock(tw, x): # x is the string to transmit
+def myblock(tw, args): # x is the string to transmit
import serial # you may need to install this library
# serial device on USB, 9600 baud
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
- ser.write(str(x)) # send string x
+ ser.write(str(args[0])) # send string x
st = ser.read(1000) # read up to 1000 bytes
tw.lc.heap.append(st) # append to heap
ser.close()
diff --git a/pysamples/sinewave.py b/pysamples/sinewave.py
index 4f14c4c..a060f34 100644
--- a/pysamples/sinewave.py
+++ b/pysamples/sinewave.py
@@ -8,8 +8,9 @@
# the speaker at the specified frequency.
-def myblock(tw, frequency):
+def myblock(tw, args):
''' Plays a sound at frequency frequency '''
import os
+ frequency = args[0]
os.system('speaker-test -t sine -l 1 -f %d' % (int(frequency)))
diff --git a/pysamples/speak.py b/pysamples/speak.py
index 30762a9..13215e8 100644
--- a/pysamples/speak.py
+++ b/pysamples/speak.py
@@ -28,7 +28,7 @@ def myblock(tw, arg):
import os
pitch = None
- if type(arg) == type([]):
+ if len(arg) > 1:
text = arg[0]
if len(arg) > 1:
pitch = int(arg[1])
@@ -37,7 +37,7 @@ def myblock(tw, arg):
elif pitch < 0:
pitch = 0
else:
- text = arg
+ text = arg[0]
# Turtle Art numbers are passed as float,
# but they may be integer values.
diff --git a/pysamples/uturn.py b/pysamples/uturn.py
index cdeb96d..60683a0 100644
--- a/pysamples/uturn.py
+++ b/pysamples/uturn.py
@@ -1,4 +1,4 @@
-#Copyright (c) 2011, Walter Bender
+ #Copyright (c) 2011-2013, Walter Bender
# This procedure is invoked when the user-definable block on the
# "extras" palette is selected.
@@ -8,11 +8,22 @@
# can use the u-turn block as you would any other block.
-def myblock(tw, arg):
+def myblock(tw, args):
''' Add a uturn block to the 'turtle' palette '''
+ # def_prim takes 3 arguments: the primitive name, the number of
+ # arguments -- 0 in this case -- and the function to call -- in this
+ # case, we define the _prim_uturn function to set heading += 180.
+ def _prim_uturn(tw):
+ value = tw.turtles.get_active_turtle().get_heading() + 180
+ tw.turtles.get_active_turtle().set_heading(value)
+ # We also update the label on the heading block to indicate
+ # the current heading value
+ if tw.lc.update_values:
+ tw.lc.update_label_value('heading', value)
+
from TurtleArt.tapalette import make_palette, palette_name_to_index
- from TurtleArt.talogo import primitive_dictionary
+ from TurtleArt.taprimitive import Primitive, ConstantArg
from gettext import gettext as _
# Choose a palette for the new block.
@@ -23,12 +34,10 @@ def myblock(tw, arg):
style='basic-style-extended-vertical',
label=_('uturn'),
prim_name='uturn',
- help_string=_('make a uturn'))
+ help_string=_('turns the turtle 180 degrees'))
# 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))
+ tw.lc.def_prim('uturn', 0, Primitive(_prim_uturn, arg_descs=[ConstantArg(tw)]))
# Regenerate the palette, which will now include the new block.
tw.show_toolbar_palette(palette_name_to_index('turtle'),
diff --git a/samples/card-18.tb b/samples/card-18.tb
new file mode 100644
index 0000000..951a3fb
--- /dev/null
+++ b/samples/card-18.tb
@@ -0,0 +1,38 @@
+[[0, ["start", 2.0], 180, 180, [null, 33]],
+[1, ["repeat", 42], 758, 276, [10, 2, 3, 8]],
+[2, ["number", 3.0], 817, 276, [1, null]],
+[3, ["arc", 0], 776, 318, [1, 4, 5, 6]],
+[4, ["number", 90], 834, 318, [3, null]],
+[5, ["number", 50.0], 834, 360, [3, null]],
+[6, "left", 776, 402, [3, 7, null]],
+[7, ["number", 90], 834, 402, [6, null]],
+[8, "left", 758, 462, [1, 9, null]],
+[9, ["number", 180.0], 816, 462, [8, null]],
+[10, ["repeat", 93], 740, 234, [16, 11, 1, null]],
+[11, ["number", 2.0], 799, 234, [10, null]],
+[12, ["repeat", 21], 440, 234, [20, 13, 18, null]],
+[13, ["number", 12.0], 499, 234, [12, null]],
+[14, "right", 458, 318, [18, 15, null]],
+[15, ["number", 30.0], 516, 318, [14, null]],
+[16, "hat", 740, 180, [null, 17, 10]],
+[17, ["string", "action"], 798, 192, [16, null]],
+[18, "stack", 458, 276, [12, 19, 14]],
+[19, ["string", "action"], 516, 276, [18, null]],
+[20, "hat", 440, 180, [null, 21, 12]],
+[21, ["string", "bumps"], 498, 192, [20, null]],
+[22, "stack", 180, 436, [27, 23, 29]],
+[23, ["string", "bumps"], 238, 436, [22, null]],
+[24, "stack", 180, 562, [28, 25, null]],
+[25, ["string", "bumps"], 238, 562, [24, null]],
+[26, "red", 257, 520, [28, null]],
+[27, "setcolor", 180, 394, [31, 37, 22]],
+[28, "setcolor", 180, 520, [29, 26, 24]],
+[29, "setpensize", 180, 478, [22, 30, 28]],
+[30, ["number", 5], 282, 478, [29, null]],
+[31, "setpensize", 180, 352, [33, 32, 27]],
+[32, ["number", 20.0], 282, 352, [31, null]],
+[33, "fillscreen2", 180, 226, [0, 34, 35, 36, 31]],
+[34, ["number", 60], 262, 226, [33, null]],
+[35, ["number", 80], 262, 268, [33, null]],
+[36, ["number", 100], 262, 310, [33, null]],
+[37, "yellow", 257, 394, [27, null]]]
diff --git a/samples/game-snake.tb b/samples/game-snake.tb
new file mode 100644
index 0000000..aa157c3
--- /dev/null
+++ b/samples/game-snake.tb
@@ -0,0 +1,60 @@
+[[0, ["start", 2.0], 183, 131, [null, 56]],
+[1, "hat", 740, 420, [null, 2, 13]],
+[2, ["string", "left"], 798, 432, [1, null]],
+[3, "hat", 740, 180, [null, 4, 12]],
+[4, ["string", "right"], 798, 192, [3, null]],
+[5, "stack", 498, 281, [11, 6, 7]],
+[6, ["string", "left"], 556, 281, [5, null]],
+[7, "stack", 498, 323, [5, 8, 9]],
+[8, ["string", "right"], 556, 323, [7, null]],
+[9, "forward", 498, 365, [7, 10, 51]],
+[10, ["number", 10], 569, 365, [9, null]],
+[11, "kbinput", 498, 239, [33, 5]],
+[12, ["vspace", 0], 740, 234, [3, 14]],
+[13, ["vspace", 0], 740, 474, [1, 15]],
+[14, ["if", 0], 740, 276, [12, 16, 20, null]],
+[15, ["if", 0], 740, 516, [13, 17, 22, null]],
+[16, ["equal2", 0], 796, 242, [14, 45, 18, null]],
+[17, ["equal2", 0], 796, 482, [15, 46, 19, null]],
+[18, "keyboard", 852, 284, [16, null]],
+[19, "keyboard", 852, 524, [17, null]],
+[20, "right", 758, 342, [14, 21, null]],
+[21, ["number", 10], 816, 342, [20, null]],
+[22, "left", 758, 582, [15, 23, null]],
+[23, ["number", 10], 816, 582, [22, null]],
+[24, "wait", 201, 537, [35, 25, null]],
+[25, ["number", 0.1], 259, 537, [24, null]],
+[26, "forward", 1020, 276, [30, 27, 39]],
+[27, ["number", 5], 1091, 276, [26, null]],
+[28, "back", 1020, 402, [39, 29, 31]],
+[29, ["number", 5], 1078, 402, [28, null]],
+[30, "penup", 1020, 234, [49, 26]],
+[31, "pendown", 1020, 444, [28, null]],
+[32, "showblocks", 183, 597, [47, null]],
+[33, "hat", 498, 185, [null, 34, 11]],
+[34, ["string", "action"], 556, 197, [33, null]],
+[35, "stack", 201, 495, [47, 36, 24]],
+[36, ["string", "action"], 259, 495, [35, null]],
+[37, "setpensize", 183, 303, [57, 38, 54]],
+[38, ["number", 8], 285, 303, [37, null]],
+[39, ["storein", 0], 1020, 318, [26, 40, 41, 28]],
+[40, ["string", "color"], 1088, 318, [39, null]],
+[41, "see", 1088, 360, [39, null]],
+[42, ["equal2", 0], 239, 395, [47, 43, 53, null]],
+[43, "box", 295, 395, [42, 44, null]],
+[44, ["string", "color"], 350, 395, [43, null]],
+[45, ["number", 3], 852, 242, [16, null]],
+[46, ["number", 1], 852, 482, [17, null]],
+[47, ["until", 21], 183, 429, [48, 42, 35, 32]],
+[48, ["vspace", 0], 183, 387, [54, 47]],
+[49, "hat", 1020, 180, [null, 50, 30]],
+[50, ["string", "look ahead"], 1078, 192, [49, null]],
+[51, "stack", 498, 407, [9, 52, null]],
+[52, ["string", "look ahead"], 556, 407, [51, null]],
+[53, "red", 295, 437, [42, null]],
+[54, "setcolor", 183, 345, [37, 55, 48]],
+[55, "red", 260, 345, [54, null]],
+[56, "clean", 183, 177, [0, 57]],
+[57, ["storein", 0], 183, 219, [56, 58, 59, 37]],
+[58, ["string", "color"], 251, 219, [57, null]],
+[59, "blue", 251, 261, [57, null]]]
diff --git a/samples/media-music-keyboard.tb b/samples/media-music-keyboard.tb
index a525d33..6ac5eae 100644
--- a/samples/media-music-keyboard.tb
+++ b/samples/media-music-keyboard.tb
@@ -1,4 +1,4 @@
-[[0, ["start", 2.0], 160, 200, [null, 110]],
+[[0, ["start", 2.0], 160, 200, [null, 130]],
[1, ["until", 63], 600, 254, [38, 22, 25, 28]],
[2, ["forever", 230], 160, 330, [112, 40, null]],
[3, "sinewave", 178, 698, [20, 36, 10, 4, 11]],
@@ -108,7 +108,7 @@
[107, "ycor", 929, 1565, [101, null]],
[108, "hat", 1096, 195, [null, 109, 114]],
[109, ["string", "select notes"], 1154, 207, [108, null]],
-[110, "stack", 160, 246, [0, 111, 112]],
+[110, "stack", 160, 246, [130, 111, 112]],
[111, ["string", "select notes"], 218, 246, [110, null]],
[112, "stack", 160, 288, [110, 113, 2]],
[113, ["string", "draw keyboard"], 218, 288, [112, null]],
@@ -127,4 +127,5 @@
[126, "push", 1096, 501, [124, 127, 128]],
[127, ["number", 26.0], 1154, 501, [126, null]],
[128, "push", 1096, 543, [126, 129, null]],
-[129, ["number", 24], 1154, 543, [128, null]]]
+[129, ["number", 24], 1154, 543, [128, null]],
+[130, "clean", 853, 1149, [0, 110]]] \ No newline at end of file
diff --git a/samples/thumbnails/card-18.png b/samples/thumbnails/card-18.png
new file mode 100644
index 0000000..9d32c25
--- /dev/null
+++ b/samples/thumbnails/card-18.png
Binary files differ
diff --git a/samples/thumbnails/game-snake.png b/samples/thumbnails/game-snake.png
new file mode 100644
index 0000000..da57530
--- /dev/null
+++ b/samples/thumbnails/game-snake.png
Binary files differ
diff --git a/samples/thumbnails/sensors-follow-path.png b/samples/thumbnails/sensors-follow-path.png
new file mode 100644
index 0000000..a4916a9
--- /dev/null
+++ b/samples/thumbnails/sensors-follow-path.png
Binary files differ
diff --git a/turtleblocks.py b/turtleblocks.py
index 94c0fb7..604d609 100755
--- a/turtleblocks.py
+++ b/turtleblocks.py
@@ -57,6 +57,8 @@ from TurtleArt.tautils import (data_from_string, get_save_name)
from TurtleArt.tapalette import default_values
from TurtleArt.tawindow import TurtleArtWindow
from TurtleArt.taexportlogo import save_logo
+from TurtleArt.taexportpython import save_python
+from TurtleArt.taprimitive import PyExportError
from util.menubuilder import MenuBuilder
@@ -405,6 +407,8 @@ return %s(self)" % (p, P, P)
self._do_save_picture_cb)
MenuBuilder.make_menu_item(menu, _('Save as Logo'),
self._do_save_logo_cb)
+ MenuBuilder.make_menu_item(menu, _('Save as Python'),
+ self._do_save_python_cb)
MenuBuilder.make_menu_item(menu, _('Quit'), self._quit_ta)
activity_menu = MenuBuilder.make_sub_menu(menu, _('File'))
@@ -547,6 +551,7 @@ Would you like to save before quitting?'))
if len(logocode) == 0:
return
save_type = '.lg'
+ self.tw.load_save_folder = self._get_execution_dir()
filename, self.tw.load_save_folder = get_save_name(
save_type, self.tw.load_save_folder, 'logosession')
if isinstance(filename, unicode):
@@ -556,6 +561,36 @@ Would you like to save before quitting?'))
f.write(logocode)
f.close()
+ def _do_save_python_cb(self, widget):
+ ''' Callback for saving the project as Python code. '''
+ # catch PyExportError and display a user-friendly message instead
+ try:
+ pythoncode = save_python(self.tw)
+ except PyExportError as pyee:
+ if pyee.block is not None:
+ pyee.block.highlight()
+ self.tw.showlabel('status', str(pyee))
+ print pyee
+ return
+ if not pythoncode:
+ return
+ # use name of TA project if it has been saved already
+ default_name = self.tw.save_file_name
+ if default_name is None:
+ default_name = _("myproject")
+ elif default_name.endswith(".ta") or default_name.endswith(".tb"):
+ default_name = default_name[:-3]
+ save_type = '.py'
+ self.tw.load_save_folder = self._get_execution_dir()
+ filename, self.tw.load_save_folder = get_save_name(
+ save_type, self.tw.load_save_folder, default_name)
+ if isinstance(filename, unicode):
+ filename = filename.encode('utf-8')
+ if filename is not None:
+ f = file(filename, 'w')
+ f.write(pythoncode)
+ f.close()
+
def _do_resize_cb(self, widget, factor):
''' Callback to resize blocks. '''
if factor == -1:
@@ -846,6 +881,10 @@ Would you like to save before quitting?'))
self._selected_sample = image_path
self._sample_window.hide()
+ self.win.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+ gobject.idle_add(self._sample_loader)
+
+ def _sample_loader(self):
# Convert from thumbnail path to sample path
basename = os.path.basename(self._selected_sample)[:-4]
for suffix in ['.ta', '.tb']:
@@ -856,6 +895,7 @@ Would you like to save before quitting?'))
break
self.tw.load_save_folder = os.path.join(self._get_execution_dir(),
'samples')
+ self.win.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
def _fill_samples_list(self, store):
'''
diff --git a/util/ast_extensions.py b/util/ast_extensions.py
new file mode 100644
index 0000000..d46bdb0
--- /dev/null
+++ b/util/ast_extensions.py
@@ -0,0 +1,69 @@
+#Copyright (c) 2013 Marion Zepf
+
+#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.
+
+
+""" Extend the `ast` module to include comments """
+
+import ast
+
+
+class ExtraCode(ast.stmt):
+ """Adds extra content to a primitive needed in Python code, e.g.,
+ changes to the turtle (e.g., prim_turtle) require the addition of
+ turtle = turtles.get_active_turtle()
+ Extends the Python abstract grammar by the following: stmt
+ = ExtraContent(string text) | ... """
+
+ _fields = ('text')
+
+ def __init__(self, text="", lineno=1, col_offset=0):
+ """ text -- the textual content of the comment, i.e. everything
+ directly following the hashtag until the next newline """
+ self.text = text
+ self.lineno = lineno
+ self.col_offset = col_offset
+
+
+class Comment(ast.stmt):
+ """ An inline comment, starting with a hashtag (#).
+ Extends the Python abstract grammar by the following:
+ stmt = Comment(string text) | ... """
+
+ _fields = ('text')
+
+ def __init__(self, text="", lineno=1, col_offset=0):
+ """ text -- the textual content of the comment, i.e. everything
+ directly following the hashtag until the next newline """
+ self.text = text
+ self.lineno = lineno
+ self.col_offset = col_offset
+
+
+class LambdaWithStrBody(ast.Lambda):
+ """ Lambda AST whose body is a simple string (not ast.Str).
+ Extends the Python abstract grammar by the following:
+ expr = LambdaWithStrBody(string body_str, expr* args) | ... """
+
+ def __init__(self, body_str="", args=[], lineno=1, col_offset=0):
+ self.body_str = body_str
+ self.args = args
+ self.lineno = lineno
+ self.col_offset = col_offset
+
diff --git a/util/codegen.py b/util/codegen.py
new file mode 100644
index 0000000..46184e7
--- /dev/null
+++ b/util/codegen.py
@@ -0,0 +1,597 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2008, Armin Ronacher
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+"""
+ codegen
+ ~~~~~~~
+
+ Extension to ast that allow ast -> python code generation.
+
+ :copyright: Copyright 2008 by Armin Ronacher.
+ :license: BSD.
+
+ Modified by Marion Zepf.
+"""
+from ast import *
+from ast_extensions import Comment, ExtraCode
+
+
+def to_source(node, indent_with=' ' * 4, add_line_information=False):
+ """This function can convert a node tree back into python sourcecode.
+ This is useful for debugging purposes, especially if you're dealing with
+ custom asts not generated by python itself.
+
+ It could be that the sourcecode is evaluable when the AST itself is not
+ compilable / evaluable. The reason for this is that the AST contains some
+ more data than regular sourcecode does, which is dropped during
+ conversion.
+
+ Each level of indentation is replaced with `indent_with`. Per default this
+ parameter is equal to four spaces as suggested by PEP 8, but it might be
+ adjusted to match the application's styleguide.
+
+ If `add_line_information` is set to `True` comments for the line numbers
+ of the nodes are added to the output. This can be used to spot wrong line
+ number information of statement nodes.
+ """
+ generator = SourceGenerator(indent_with, add_line_information)
+ generator.visit(node)
+ return ''.join(generator.result)
+
+
+class SourceGenerator(NodeVisitor):
+ """This visitor is able to transform a well formed syntax tree into python
+ sourcecode. For more details have a look at the docstring of the
+ `node_to_source` function.
+ """
+
+ UNARYOP_SYMBOLS = {Invert: "~", Not: "not", UAdd: "+", USub: "-"}
+ # TODO use parentheses around expressions only where necessary
+ BINOP_SYMBOLS = {Add: "+", Sub: "-", Mult: "*", Div: "/", Mod: "%",
+ LShift: "<<", RShift:">>", BitOr: "|", BitXor: "^",
+ BitAnd: "&", FloorDiv: "//", Pow: "**"}
+ BOOLOP_SYMBOLS = {And: "and", Or: "or"}
+ CMPOP_SYMBOLS = {Eq: "==", NotEq: "!=", Lt: "<", LtE: "<=", Gt: ">",
+ GtE: ">=", Is: "is", IsNot: "is not", In: "in",
+ NotIn: "not in"}
+
+ def __init__(self, indent_with, add_line_information=False):
+ self.result = []
+ self.indent_with = indent_with
+ self.add_line_information = add_line_information
+ self.indentation = 0
+ self.new_lines = 0
+
+ def write(self, x):
+ if self.new_lines:
+ if self.result:
+ self.result.append('\n' * self.new_lines)
+ self.result.append(self.indent_with * self.indentation)
+ self.new_lines = 0
+ self.result.append(x)
+
+ def newline(self, node=None, extra=0):
+ self.new_lines = max(self.new_lines, 1 + extra)
+ if node is not None and self.add_line_information:
+ self.write('# line: %s' % node.lineno)
+ self.new_lines = 1
+
+ def body(self, statements, do_indent=True):
+ if do_indent:
+ self.indentation += 1
+ for stmt in statements:
+ self.newline()
+ self.visit(stmt)
+ if do_indent:
+ self.indentation -= 1
+
+ def body_or_else(self, node):
+ self.body(node.body)
+ if node.orelse:
+ self.newline()
+ self.write('else:')
+ self.body(node.orelse)
+
+ def signature(self, node):
+ want_comma = []
+ def write_comma():
+ if want_comma:
+ self.write(', ')
+ else:
+ want_comma.append(True)
+
+ padding = [None] * (len(node.args) - len(node.defaults))
+ for arg, default in zip(node.args, padding + node.defaults):
+ write_comma()
+ self.visit(arg)
+ if default is not None:
+ self.write('=')
+ self.visit(default)
+ if node.vararg is not None:
+ write_comma()
+ self.write('*' + node.vararg)
+ if node.kwarg is not None:
+ write_comma()
+ self.write('**' + node.kwarg)
+
+ def decorators(self, node):
+ for decorator in node.decorator_list:
+ self.newline(decorator)
+ self.write('@')
+ self.visit(decorator)
+
+ # Statements
+
+ def visit_Assign(self, node):
+ self.newline(node)
+ for idx, target in enumerate(node.targets):
+ if idx:
+ self.write(', ')
+ self.visit(target)
+ self.write(' = ')
+ self.visit(node.value)
+
+ def visit_AugAssign(self, node):
+ self.newline(node)
+ self.visit(node.target)
+ self.write(self.BINOP_SYMBOLS[node.op] + '=')
+ self.visit(node.value)
+
+ def visit_ImportFrom(self, node):
+ self.newline(node)
+ self.write('from %s%s import ' % ('.' * node.level, node.module))
+ for idx, item in enumerate(node.names):
+ if idx:
+ self.write(', ')
+ self.visit(item)
+
+ def visit_Import(self, node):
+ self.newline(node)
+ for item in node.names:
+ self.write('import ')
+ self.visit(item)
+
+ def visit_Expr(self, node):
+ self.newline(node)
+ self.generic_visit(node)
+
+ def visit_Module(self, node):
+ self.body(node.body, do_indent=False)
+
+ def visit_FunctionDef(self, node):
+ self.newline(extra=1)
+ self.decorators(node)
+ self.newline(node)
+ self.write('def %s(' % node.name)
+ self.signature(node.args)
+ self.write('):')
+ self.body(node.body)
+
+ def visit_ClassDef(self, node):
+ have_args = []
+ def paren_or_comma():
+ if have_args:
+ self.write(', ')
+ else:
+ have_args.append(True)
+ self.write('(')
+
+ self.newline(extra=2)
+ self.decorators(node)
+ self.newline(node)
+ self.write('class %s' % node.name)
+ for base in node.bases:
+ paren_or_comma()
+ self.visit(base)
+ # XXX: the if here is used to keep this module compatible
+ # with python 2.6.
+ if hasattr(node, 'keywords'):
+ for keyword in node.keywords:
+ paren_or_comma()
+ self.write(keyword.arg + '=')
+ self.visit(keyword.value)
+ if node.starargs is not None:
+ paren_or_comma()
+ self.write('*')
+ self.visit(node.starargs)
+ if node.kwargs is not None:
+ paren_or_comma()
+ self.write('**')
+ self.visit(node.kwargs)
+ self.write(have_args and '):' or ':')
+ self.body(node.body)
+
+ def visit_If(self, node):
+ self.newline(node)
+ self.write('if ')
+ self.visit(node.test)
+ self.write(':')
+ self.body(node.body)
+ while True:
+ else_ = node.orelse
+ if len(else_) == 1 and isinstance(else_[0], If):
+ node = else_[0]
+ self.newline()
+ self.write('elif ')
+ self.visit(node.test)
+ self.write(':')
+ self.body(node.body)
+ elif else_:
+ self.newline()
+ self.write('else:')
+ self.body(else_)
+ break
+ else:
+ break
+
+ def visit_For(self, node):
+ self.newline(node)
+ self.write('for ')
+ self.visit(node.target)
+ self.write(' in ')
+ self.visit(node.iter)
+ self.write(':')
+ self.body_or_else(node)
+
+ def visit_While(self, node):
+ self.newline(node)
+ self.write('while ')
+ self.visit(node.test)
+ self.write(':')
+ self.body_or_else(node)
+
+ def visit_With(self, node):
+ self.newline(node)
+ self.write('with ')
+ self.visit(node.context_expr)
+ if node.optional_vars is not None:
+ self.write(' as ')
+ self.visit(node.optional_vars)
+ self.write(':')
+ self.body(node.body)
+
+ def visit_Pass(self, node):
+ self.newline(node)
+ self.write('pass')
+
+ def visit_Print(self, node):
+ # XXX: python 2.6 only
+ self.newline(node)
+ self.write('print ')
+ want_comma = False
+ if node.dest is not None:
+ self.write(' >> ')
+ self.visit(node.dest)
+ want_comma = True
+ for value in node.values:
+ if want_comma:
+ self.write(', ')
+ self.visit(value)
+ want_comma = True
+ if not node.nl:
+ self.write(',')
+
+ def visit_Delete(self, node):
+ self.newline(node)
+ self.write('del ')
+ for idx, target in enumerate(node):
+ if idx:
+ self.write(', ')
+ self.visit(target)
+
+ def visit_TryExcept(self, node):
+ self.newline(node)
+ self.write('try:')
+ self.body(node.body)
+ for handler in node.handlers:
+ self.visit(handler)
+
+ def visit_TryFinally(self, node):
+ self.newline(node)
+ self.write('try:')
+ self.body(node.body)
+ self.newline(node)
+ self.write('finally:')
+ self.body(node.finalbody)
+
+ def visit_Global(self, node):
+ self.newline(node)
+ self.write('global ' + ', '.join(node.names))
+
+ def visit_Nonlocal(self, node):
+ self.newline(node)
+ self.write('nonlocal ' + ', '.join(node.names))
+
+ def visit_Return(self, node):
+ self.newline(node)
+ self.write('return')
+ if hasattr(node, "value") and node.value is not None:
+ self.write(' ')
+ self.visit(node.value)
+
+ def visit_Break(self, node):
+ self.newline(node)
+ self.write('break')
+
+ def visit_Continue(self, node):
+ self.newline(node)
+ self.write('continue')
+
+ def visit_Raise(self, node):
+ # XXX: Python 2.6 / 3.0 compatibility
+ self.newline(node)
+ self.write('raise')
+ if hasattr(node, 'exc') and node.exc is not None:
+ self.write(' ')
+ self.visit(node.exc)
+ if node.cause is not None:
+ self.write(' from ')
+ self.visit(node.cause)
+ elif hasattr(node, 'type') and node.type is not None:
+ self.visit(node.type)
+ if node.inst is not None:
+ self.write(', ')
+ self.visit(node.inst)
+ if node.tback is not None:
+ self.write(', ')
+ self.visit(node.tback)
+
+ def visit_Comment(self, node):
+ self.newline(node)
+ self.write('#' + str(node.text))
+
+ def visit_ExtraCode(self, node):
+ self.newline(node)
+ self.write(str(node.text))
+
+ # Expressions
+
+ def visit_Attribute(self, node):
+ self.visit(node.value)
+ self.write('.' + node.attr)
+
+ def visit_Call(self, node):
+ want_comma = []
+ def write_comma():
+ if want_comma:
+ self.write(', ')
+ else:
+ want_comma.append(True)
+
+ self.visit(node.func)
+ self.write('(')
+ for arg in node.args:
+ write_comma()
+ self.visit(arg)
+ for keyword in node.keywords:
+ write_comma()
+ self.write(keyword.arg + '=')
+ self.visit(keyword.value)
+ if node.starargs is not None:
+ write_comma()
+ self.write('*')
+ self.visit(node.starargs)
+ if node.kwargs is not None:
+ write_comma()
+ self.write('**')
+ self.visit(node.kwargs)
+ self.write(')')
+ visit_TypedCall = visit_Call
+
+ def visit_Name(self, node):
+ self.write(node.id)
+ visit_TypedName = visit_Name
+
+ def visit_Str(self, node):
+ self.write(repr(node.s))
+
+ def visit_Bytes(self, node):
+ self.write(repr(node.s))
+
+ def visit_Num(self, node):
+ self.write(repr(node.n))
+
+ def visit_Tuple(self, node):
+ self.write('(')
+ idx = -1
+ for idx, item in enumerate(node.elts):
+ if idx:
+ self.write(', ')
+ self.visit(item)
+ self.write(idx and ')' or ',)')
+
+ def sequence_visit(left, right):
+ def visit(self, node):
+ self.write(left)
+ for idx, item in enumerate(node.elts):
+ if idx:
+ self.write(', ')
+ self.visit(item)
+ self.write(right)
+ return visit
+
+ visit_List = sequence_visit('[', ']')
+ visit_Set = sequence_visit('{', '}')
+ del sequence_visit
+
+ def visit_Dict(self, node):
+ self.write('{')
+ for idx, (key, value) in enumerate(zip(node.keys, node.values)):
+ if idx:
+ self.write(', ')
+ self.visit(key)
+ self.write(': ')
+ self.visit(value)
+ self.write('}')
+
+ def visit_BinOp(self, node):
+ self.visit(node.left)
+ self.write(' %s ' % self.BINOP_SYMBOLS[node.op])
+ self.visit(node.right)
+
+ def visit_BoolOp(self, node):
+ self.write('(')
+ for idx, value in enumerate(node.values):
+ if idx:
+ self.write(' %s ' % self.BOOLOP_SYMBOLS[node.op])
+ self.visit(value)
+ self.write(')')
+
+ def visit_Compare(self, node):
+ self.write('(')
+ self.visit(node.left)
+ for op, right in zip(node.ops, node.comparators):
+ self.write(' %s ' % self.CMPOP_SYMBOLS[op])
+ self.visit(right)
+ self.write(')')
+
+ def visit_UnaryOp(self, node):
+ self.write('(')
+ op = self.UNARYOP_SYMBOLS[node.op]
+ self.write(op)
+ if op == 'not':
+ self.write(' ')
+ self.visit(node.operand)
+ self.write(')')
+
+ def visit_Subscript(self, node):
+ self.visit(node.value)
+ self.write('[')
+ self.visit(node.slice)
+ self.write(']')
+ visit_TypedSubscript = visit_Subscript
+
+ def visit_Index(self, node):
+ self.visit(node.value)
+
+ def visit_Slice(self, node):
+ if node.lower is not None:
+ self.visit(node.lower)
+ self.write(':')
+ if node.upper is not None:
+ self.visit(node.upper)
+ if node.step is not None:
+ self.write(':')
+ if not (isinstance(node.step, Name) and node.step.id == 'None'):
+ self.visit(node.step)
+
+ def visit_ExtSlice(self, node):
+ for idx, item in node.dims:
+ if idx:
+ self.write(', ')
+ self.visit(item)
+
+ def visit_Yield(self, node):
+ self.write('yield ')
+ self.visit(node.value)
+
+ def visit_Lambda(self, node):
+ self.write('(lambda ')
+ self.signature(node.args)
+ self.write(': ')
+ self.visit(node.body)
+ self.write(')')
+
+ def visit_LambdaWithStrBody(self, node):
+ self.write('(lambda ')
+ for idx, arg in enumerate(node.args):
+ if idx:
+ self.write(', ')
+ self.visit(arg)
+ self.write(': ')
+ self.write(node.body_str)
+ self.write(')')
+
+ def visit_Ellipsis(self, node):
+ self.write('Ellipsis')
+
+ def generator_visit(left, right):
+ def visit(self, node):
+ self.write(left)
+ self.visit(node.elt)
+ for comprehension in node.generators:
+ self.visit(comprehension)
+ self.write(right)
+ return visit
+
+ visit_ListComp = generator_visit('[', ']')
+ visit_GeneratorExp = generator_visit('(', ')')
+ visit_SetComp = generator_visit('{', '}')
+ del generator_visit
+
+ def visit_DictComp(self, node):
+ self.write('{')
+ self.visit(node.key)
+ self.write(': ')
+ self.visit(node.value)
+ for comprehension in node.generators:
+ self.visit(comprehension)
+ self.write('}')
+
+ def visit_IfExp(self, node):
+ self.visit(node.body)
+ self.write(' if ')
+ self.visit(node.test)
+ self.write(' else ')
+ self.visit(node.orelse)
+
+ def visit_Starred(self, node):
+ self.write('*')
+ self.visit(node.value)
+
+ def visit_Repr(self, node):
+ # XXX: python 2.6 only
+ self.write('`')
+ self.visit(node.value)
+ self.write('`')
+
+ # Helper Nodes
+
+ def visit_alias(self, node):
+ self.write(node.name)
+ if node.asname is not None:
+ self.write(' as ' + node.asname)
+
+ def visit_comprehension(self, node):
+ self.write(' for ')
+ self.visit(node.target)
+ self.write(' in ')
+ self.visit(node.iter)
+ if node.ifs:
+ for if_ in node.ifs:
+ self.write(' if ')
+ self.visit(if_)
+
+ def visit_excepthandler(self, node):
+ self.newline(node)
+ self.write('except')
+ if node.type is not None:
+ self.write(' ')
+ self.visit(node.type)
+ if node.name is not None:
+ self.write(' as ')
+ self.visit(node.name)
+ self.write(':')
+ self.body(node.body)