Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWalter Bender <walter@sugarlabs.org>2013-12-18 19:21:39 (GMT)
committer Walter Bender <walter@sugarlabs.org>2013-12-18 19:21:39 (GMT)
commit460727e4e87f06e44d84c56b386a3e54fc1eff16 (patch)
tree4bfdf176c4fa3e599839640012fa86f631e1cd75
parent51ef2849405531b6c1ea53a12cd8f022a4c0cbf2 (diff)
sync to 195
-rw-r--r--.gitignore2
-rw-r--r--TurtleArt/sprites.py9
-rw-r--r--TurtleArt/tabasics.py1063
-rw-r--r--TurtleArt/tablock.py138
-rw-r--r--TurtleArt/tacanvas.py26
-rw-r--r--TurtleArt/taconstants.py224
-rw-r--r--TurtleArt/taexportpython.py287
-rw-r--r--TurtleArt/tajail.py28
-rw-r--r--TurtleArt/talogo.py688
-rw-r--r--TurtleArt/tapalette.py9
-rw-r--r--TurtleArt/taplugin.py117
-rw-r--r--TurtleArt/taprimitive.py1174
-rwxr-xr-xTurtleArt/tasprite_factory.py76
-rw-r--r--TurtleArt/taturtle.py120
-rw-r--r--TurtleArt/tatype.py441
-rw-r--r--TurtleArt/tautils.py41
-rw-r--r--TurtleArt/tawindow.py507
-rw-r--r--TurtleArtActivity.py501
-rw-r--r--icons/load.svg137
-rw-r--r--icons/python-saveoff.svg111
-rw-r--r--icons/python-saveon.svg111
-rw-r--r--icons/save.svg137
-rw-r--r--images/Cartesian.svg406
-rw-r--r--images/dupstack.svg69
-rw-r--r--images/noconnection.svg122
-rw-r--r--plugins/accelerometer/accelerometer.py100
-rw-r--r--plugins/accelerometer/icons/extrasoff.svg75
-rw-r--r--plugins/accelerometer/icons/extrason.svg158
-rw-r--r--plugins/accelerometer/icons/sensoroff.svg79
-rw-r--r--plugins/accelerometer/icons/sensoron.svg63
-rw-r--r--plugins/audio_sensors/audio_sensors.py483
-rw-r--r--plugins/audio_sensors/audiograb.py673
-rw-r--r--plugins/audio_sensors/icons/extrasoff.svg75
-rw-r--r--plugins/audio_sensors/icons/extrason.svg158
-rw-r--r--plugins/audio_sensors/icons/sensoroff.svg79
-rw-r--r--plugins/audio_sensors/icons/sensoron.svg63
-rw-r--r--plugins/audio_sensors/ringbuffer.py108
-rw-r--r--plugins/camera_sensor/camera_sensor.py332
-rw-r--r--plugins/camera_sensor/glive.py638
-rw-r--r--plugins/camera_sensor/icons/sensoroff.svg79
-rw-r--r--plugins/camera_sensor/icons/sensoron.svg63
-rw-r--r--plugins/camera_sensor/images/camera1off.svg66
-rw-r--r--plugins/camera_sensor/images/camera1small.svg68
-rw-r--r--plugins/camera_sensor/images/cameraoff.svg15
-rw-r--r--plugins/camera_sensor/images/camerasmall.svg51
-rw-r--r--plugins/camera_sensor/instance.py22
-rw-r--r--plugins/camera_sensor/tacamera.py69
-rw-r--r--plugins/camera_sensor/utils.py63
-rw-r--r--plugins/camera_sensor/v4l2.py1914
-rw-r--r--plugins/light_sensor/icons/extrasoff.svg75
-rw-r--r--plugins/light_sensor/icons/extrason.svg158
-rw-r--r--plugins/light_sensor/icons/sensoroff.svg79
-rw-r--r--plugins/light_sensor/icons/sensoron.svg63
-rw-r--r--plugins/light_sensor/light_sensor.py105
-rw-r--r--plugins/rfid/device.py61
-rw-r--r--plugins/rfid/icons/extrasoff.svg75
-rw-r--r--plugins/rfid/icons/extrason.svg158
-rw-r--r--plugins/rfid/icons/sensoroff.svg79
-rw-r--r--plugins/rfid/icons/sensoron.svg63
-rw-r--r--plugins/rfid/rfid.py167
-rw-r--r--plugins/rfid/rfidrweusb.py200
-rw-r--r--plugins/rfid/rfidutils.py127
-rw-r--r--plugins/rfid/serial/__init__.py25
-rw-r--r--plugins/rfid/serial/serialposix.py492
-rw-r--r--plugins/rfid/serial/serialutil.py400
-rw-r--r--plugins/rfid/tis2000.py252
-rw-r--r--plugins/rfid/utils.py98
-rw-r--r--plugins/turtle_blocks_extras/turtle_blocks_extras.py1190
-rw-r--r--pyexported/__init__.py0
-rw-r--r--pyexported/window_setup.py138
-rw-r--r--util/ast_extensions.py69
-rw-r--r--util/codegen.py602
72 files changed, 14517 insertions, 2197 deletions
diff --git a/.gitignore b/.gitignore
index f3d74a9..37c737b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
*.pyc
*~
+locale
+dist
diff --git a/TurtleArt/sprites.py b/TurtleArt/sprites.py
index 7483c12..3f5d7df 100644
--- a/TurtleArt/sprites.py
+++ b/TurtleArt/sprites.py
@@ -106,7 +106,7 @@ class Sprites:
def length_of_list(self):
''' How many sprites are there? '''
- return(len(self.list))
+ return len(self.list)
def append_to_list(self, spr):
''' Append a new sprite to the end of the list. '''
@@ -122,9 +122,7 @@ class Sprites:
self.list.insert(i, spr)
def find_in_list(self, spr):
- if spr in self.list:
- return True
- return False
+ return (spr in self.list)
def remove_from_list(self, spr):
''' Remove a sprite from the list. '''
@@ -258,7 +256,8 @@ class Sprite:
if layer is not None:
self.layer = layer
for i in range(self._sprites.length_of_list()):
- if self.layer < self._sprites.get_sprite(i).layer:
+ spr = self._sprites.get_sprite(i)
+ if spr is not None and self.layer < spr.layer:
self._sprites.insert_in_list(self, i)
self.inval()
return
diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py
index 3d1eb10..a7dbd5b 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,20 @@ 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.canvas.clearscreen),
+ Primitive(self.tw.lc.reset_internals),
+ Primitive(self.tw.turtles.reset_turtles),
+ Primitive(self.tw.lc.active_turtle)
+ ])]))
- primitive_dictionary['right'] = self._prim_right
palette.add_block('left',
style='basic-style-1arg',
label=_('left'),
@@ -175,8 +211,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 +225,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 +238,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 +255,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 +270,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 +285,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 +300,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 +314,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 +334,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 +360,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 +374,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 +391,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 +407,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 +421,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 +435,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 +446,8 @@ 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, return_type=TYPE_NUMBER))
palette.add_block('gray',
style='box-style',
@@ -416,8 +456,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 +467,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 +477,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 +486,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 +499,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 +512,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 +520,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 +531,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 +629,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 +643,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 +662,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 +677,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 +690,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 +700,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 +727,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 +739,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 +752,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 +776,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 +786,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 +805,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 +824,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 +842,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 +855,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 +867,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,54 +887,74 @@ 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'),
prim_name='forever',
- default=[None, None],
+ default=[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'),
prim_name='repeat',
- default=[4, None, None],
+ default=[4, None],
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'), ''],
prim_name='if',
- default=[None, None, None],
+ default=[None, None],
special_name=_('if then'),
logo_command='if',
help_string=_('if-then operator that uses boolean \
operators from Numbers palette'))
- self.tw.lc.def_prim('if', 2, primitive_dictionary['if'], True)
+ 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',
label=[_('if'), _('then'), _('else')],
prim_name='ifelse',
- default=[None, None, None, None],
+ default=[None, None, None],
logo_command='ifelse',
special_name=_('if then else'),
help_string=_('if-then-else operator that uses \
boolean operators from Numbers palette'))
- self.tw.lc.def_prim('ifelse', 3, primitive_dictionary['ifelse'], True)
+ 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 +971,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 +981,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 +1010,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 +1033,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 +1049,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 +1061,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 +1075,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 +1088,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 +1101,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 +1118,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 +1134,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 +1143,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 +1155,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 +1167,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 +1180,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 +1209,9 @@ variable'))
label=_('clear all'),
help_string=_('move all blocks to trash'))
- # Block primitives
-
- 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
+ # Callbacks to update labels after executing a block
- 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 +1225,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 +1237,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..81bdd2a 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 """
@@ -152,7 +183,7 @@ class Block:
trash -- block in the trash """
def __init__(self, block_list, sprite_list, name, x, y, type='block',
- values=[], scale=BLOCK_SCALE[0],
+ values=None, scale=BLOCK_SCALE[0],
colors=['#A0A0A0', '#808080']):
self.block_list = block_list
@@ -178,7 +209,7 @@ class Block:
self._visible = True
self.unknown = False # Block is of unknown style
- self.block_methods = {
+ self._block_methods = {
'basic-style': self._make_basic_style,
'blank-style': self._make_blank_style,
'basic-style-head': self._make_basic_style_head,
@@ -210,6 +241,7 @@ class Block:
'clamp-style-collapsed': self._make_clamp_style_collapsed,
'clamp-style-1arg': self._make_clamp_style_1arg,
'clamp-style-boolean': self._make_clamp_style_boolean,
+ 'clamp-style-until': self._make_clamp_style_until,
'clamp-style-else': self._make_clamp_style_else,
'flow-style-tail': self._make_flow_style_tail,
'portfolio-style-2x2': self._make_portfolio_style_2x2,
@@ -224,8 +256,9 @@ class Block:
self.font_size[i] *= self.scale * \
self.block_list.font_scale_factor
- for v in (values):
- self.values.append(v)
+ if values is not None:
+ for v in (values):
+ self.values.append(v)
# If there is already a block with the same name, reuse it
copy_block = None
@@ -241,6 +274,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 +317,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 +607,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 +638,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)
@@ -604,7 +681,8 @@ class Block:
y = self.docks[1][3] - int(int(self.font_size[0] * 1.3))
self.spr.set_label_attributes(int(self.font_size[0] + 0.5),
True, 'right', y_pos=y, i=0)
- elif self.name in block_styles['clamp-style-boolean']:
+ elif self.name in block_styles['clamp-style-boolean'] or \
+ self.name in block_styles['clamp-style-until']:
y = self.docks[1][3] - int(int(self.font_size[0] * 1.3))
self.spr.set_label_attributes(int(self.font_size[0] + 0.5),
True, 'right', y_pos=y, i=0)
@@ -634,19 +712,18 @@ class Block:
self._right = 0
self._bottom = 0
self.svg.set_stroke_width(STANDARD_STROKE_WIDTH)
- self.svg.clear_docks()
if isinstance(self.name, unicode):
self.name = self.name.encode('utf-8')
for k in block_styles.keys():
if self.name in block_styles[k]:
- if isinstance(self.block_methods[k], list):
- self.block_methods[k][0](svg, self.block_methods[k][1],
- self.block_methods[k][2])
+ if isinstance(self._block_methods[k], list):
+ self._block_methods[k][0](svg, self._block_methods[k][1],
+ self._block_methods[k][2])
else:
- self.block_methods[k](svg)
+ self._block_methods[k](svg)
return
error_output('ERROR: block type not found %s' % (self.name))
- self.block_methods['blank-style'](svg)
+ self._block_methods['blank-style'](svg)
self.unknown = True
def _set_colors(self, svg):
@@ -973,7 +1050,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], ']']]
@@ -1011,6 +1088,25 @@ class Block:
['flow', False, self.svg.docks[4][0],
self.svg.docks[4][1], ']']]
+ def _make_clamp_style_until(self, svg, extend_x=0, extend_y=4):
+ self.svg.expand(self.dx + self.ex + extend_x, self.ey + extend_y,
+ 0, self.ey2)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_boolean(True)
+ self.svg.second_clamp(False)
+ self._make_block_graphics(svg, self.svg.clamp_until)
+ # Dock positions are flipped
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['bool', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1], '['],
+ # Skip bottom of clamp
+ ['flow', False, self.svg.docks[4][0],
+ self.svg.docks[4][1], ']']]
+
def _make_clamp_style_else(self, svg, extend_x=0, extend_y=4):
self.svg.expand(self.dx + self.ex + extend_x, self.ey + extend_y,
self.dx + self.ex + extend_x, self.ey2 + extend_y)
@@ -1119,6 +1215,7 @@ class Block:
def _make_block_graphics(self, svg, function, arg=None):
self._set_colors(svg)
self.svg.set_gradient(True, GRADIENT_COLOR)
+ self.svg.clear_docks()
if arg is None:
pixbuf = svg_str_to_pixbuf(function())
else:
@@ -1128,6 +1225,7 @@ class Block:
self.shapes[0] = _pixbuf_to_cairo_surface(pixbuf,
self.width, self.height)
self.svg.set_gradient(False)
+ self.svg.clear_docks()
if arg is None:
pixbuf = svg_str_to_pixbuf(function())
else:
diff --git a/TurtleArt/tacanvas.py b/TurtleArt/tacanvas.py
index 89b8ed1..f37a3a4 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
@@ -305,10 +305,14 @@ class TurtleGraphics:
''' Draw text '''
def _draw_text(cr, label, x, y, size, width, scale, heading, rgb):
+ import textwrap
+ final_scale = int(size * scale) * pango.SCALE
+ label = str(label)
+ label = '\n'.join(textwrap.wrap(label, int(width / scale)))
cc = pangocairo.CairoContext(cr)
pl = cc.create_layout()
fd = pango.FontDescription('Sans')
- fd.set_size(int(size * scale) * pango.SCALE)
+ fd.set_size(final_scale)
pl.set_font_description(fd)
if isinstance(label, (str, unicode)):
pl.set_text(label.replace('\0', ' '))
diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py
index 835209e..3e92956 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 = []
@@ -134,7 +287,7 @@ OVERLAY_SHAPES = ['Cartesian', 'Cartesian_labeled', 'polar', 'metric']
STATUS_SHAPES = ['status', 'info', 'nostack', 'dupstack', 'noinput',
'emptyheap', 'emptybox', 'nomedia', 'nocode', 'overflowerror',
'negroot', 'syntaxerror', 'nofile', 'nojournal', 'zerodivide',
- 'notanumber', 'incompatible', 'help', 'print']
+ 'notanumber', 'incompatible', 'help', 'print', 'noconnection']
# Emulate Sugar toolbar when running from outside of Sugar
TOOLBAR_SHAPES = ['hideshowoff', 'eraseron', 'run-fastoff',
@@ -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,11 @@ 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]]],
+ 'loadheapfromjournal':
+ [[0, 'loadheap', 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..60bf0c1
--- /dev/null
+++ b/TurtleArt/taexportpython.py
@@ -0,0 +1,287 @@
+#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
+# -*- coding: utf-8 -*-
+
+_INSTALL_PATH = '/usr/share/sugar/activities/TurtleArt.activity'
+_ALTERNATIVE_INSTALL_PATH = \
+ '/usr/local/share/sugar/activities/TurtleArt.activity'
+
+import os, sys
+paths = []
+paths.append('../%s.activity')
+paths.append(os.path.expanduser('~') + '/Activities/%s.activity')
+paths.append('/usr/share/sugar/activities/%s.activity')
+paths.append('/usr/local/share/sugar/activities/%s.activity')
+
+flag = False
+for path in paths:
+ for activity in ['TurtleBlocks', 'TurtleBots']:
+ p = path % activity
+ if os.path.exists(p):
+ flag = True
+ sys.path.insert(0, p)
+
+if not flag:
+ print 'This code require the Turtle Blocks/Bots 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..ec6e7e7 100644
--- a/TurtleArt/talogo.py
+++ b/TurtleArt/talogo.py
@@ -22,10 +22,15 @@
#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
+import urllib2
+import tempfile
try:
from sugar.graphics import style
@@ -33,10 +38,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 +58,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 +90,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 +117,7 @@ class HiddenBlock:
self.connections = []
self.docks = []
+
# Utility functions
@@ -142,7 +168,6 @@ class LogoCode:
self.hidden_turtle = None
- self.keyboard = 0
self.trace = 0
self.update_values = False
self.gplay = None
@@ -187,6 +212,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 +267,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 +301,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 +310,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 +353,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 +410,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 +441,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 +464,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 +476,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 +488,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 +498,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 +553,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 +563,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 +663,282 @@ 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.tw.canvas.clearscreen()
+ self.tw.turtles.reset_turtles()
+ self.reset_internals()
+
+ 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 active_turtle(self):
+ ''' NOP used to add get_active_turtle to Python export '''
+ # turtle = self.tw.turtles.get_turtle()
+ pass
+
+ 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, obj):
+ """ Load FILO from file """
+ if self.tw.running_sugar:
+ # Is the object a dsobject?
+ if isinstance(obj, Media) and obj.value:
+ from sugar.datastore import datastore
+ try:
+ dsobject = datastore.get(obj.value)
+ except:
+ debug_output("Couldn't find dsobject %s" %
+ (obj.value), self.tw.running_sugar)
+ if dsobject is not None:
+ self.push_file_data_to_heap(dsobject)
+ # Or is it a path?
+ elif os.path.exists(obj):
+ self.push_file_data_to_heap(None, path=obj)
+ else:
+ # Finally try choosing a datastore object
+ chooser_dialog(self.tw.parent, obj,
+ self.push_file_data_to_heap)
+ else:
+ # If you cannot find the file, open a chooser.
+ if not os.path.exists(obj):
+ obj, self.tw.load_save_folder = get_load_name(
+ '.*', self.tw.load_save_folder)
+ if obj is not None:
+ self.push_file_data_to_heap(None, path=obj)
+
+ def save_heap(self, obj):
+ """ 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'),
+ 'heap.txt')
+ data_to_file(self.heap, heap_file)
+
+ # Write to an existing or new dsobject
+ if isinstance(obj, Media) and obj.value:
+ dsobject = datastore.get(obj.value)
+ else:
+ dsobject = datastore.create()
+ dsobject.metadata['title'] = str(obj)
+ 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 = obj
+ 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,9 +1015,184 @@ class LogoCode:
for blk in drag_group:
blk.spr.move_relative((dx, 0))
- def push_file_data_to_heap(self, dsobject):
+ 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 obj.value is not None and os_path_exists(obj.value):
+ 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 get_from_url(self, url):
+ """ Get contents of URL as text or tempfile to image """
+ if "://" not in url: # no protocol
+ url = "http://" + url # assume HTTP
+ try:
+ req = urllib2.urlopen(url)
+ except urllib2.HTTPError, e:
+ debug_output("Couldn't open %s: %s" % (url, e),
+ self.tw.running_sugar)
+ raise logoerror(url + ' [%d]' % (e.code))
+ except urllib2.URLError, e:
+ if hasattr(e, 'code'):
+ debug_output("Couldn't open %s: %s" % (url, e),
+ self.tw.running_sugar)
+ raise logoerror(url + ' [%d]' % (e.code))
+ else: # elif hasattr(e, 'reason'):
+ debug_output("Couldn't reach server: %s" % (e),
+ self.tw.running_sugar)
+ raise logoerror('#noconnection')
+
+ if req.info().getheader("Content-Type")[0:5] == "image":
+ # it can't be deleted immediately, or else we won't ever access it
+ tmp = tempfile.NamedTemporaryFile(delete=False)
+ tmp.write(req.read()) # prepare for writing
+ tmp.flush() # actually write it
+ obj = Media('media', value=tmp.name)
+ return obj
+ else:
+ return req.read()
+
+ 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, path=None):
""" push contents of a data store object (assuming json encoding) """
- data = data_from_file(dsobject.file_path)
+ if dsobject:
+ data = data_from_file(dsobject.file_path)
+ elif path is not None:
+ data = data_from_file(path)
+ else:
+ data = None
+ debug_output("No file to open", self.tw.running_sugar)
if data is not None:
for val in data:
self.heap.append(val)
@@ -861,7 +1365,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 +1415,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 +1484,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..2cf568a 100644
--- a/TurtleArt/tapalette.py
+++ b/TurtleArt/tapalette.py
@@ -71,6 +71,7 @@ block_styles = {'basic-style': [],
'clamp-style-collapsed': [],
'clamp-style-1arg': [],
'clamp-style-boolean': [],
+ 'clamp-style-until': [],
'clamp-style-else': [],
'portfolio-style-2x2': [],
'portfolio-style-1x1': [],
@@ -90,7 +91,6 @@ except ImportError:
HELP_PALETTE = False
from taconstants import (EXPANDABLE_STYLE, EXPANDABLE_FLOW)
-from tautils import debug_output
from gettext import gettext as _
@@ -177,7 +177,7 @@ class Palette():
logo_command=None, hidden=False, colors=None,
string_or_number=False):
""" Add a new block to the palette """
- block = Block(block_name)
+ block = _ProtoBlock(block_name)
block.set_style(style)
if label is not None:
block.set_label(label)
@@ -224,7 +224,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)
@@ -252,7 +252,7 @@ def define_logo_function(key, value):
logo_functions[key] = value
-class Block():
+class _ProtoBlock():
""" a class for defining new block primitives """
def __init__(self, name):
@@ -290,6 +290,7 @@ class Block():
'clamp-style-collapsible',
'clamp-style-1arg',
'clamp-style-boolean',
+ 'clamp-style-until',
'clamp-style-else']:
EXPANDABLE_FLOW.append(self._name)
diff --git a/TurtleArt/taplugin.py b/TurtleArt/taplugin.py
new file mode 100644
index 0000000..4cf2563
--- /dev/null
+++ b/TurtleArt/taplugin.py
@@ -0,0 +1,117 @@
+#Copyright (c) 2013 Walter Bender
+#Copyright (c) 2013 Daniel Francis
+
+#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 ConfigParser
+from gettext import gettext as _
+import os
+import shutil
+import subprocess
+from TurtleArt.tapalette import (palette_names, help_strings)
+
+
+def cancel_plugin_install(self, tmp_dir):
+ ''' If we cancel, just cleanup '''
+ shutil.rmtree(tmp_dir)
+
+
+def complete_plugin_install(self, tmp_dir, tmp_path, plugin_path,
+ plugin_name, file_info):
+ ''' We complete the installation directly or from ConfirmationAlert '''
+ status = subprocess.call(['cp', '-r', tmp_path, plugin_path + '/'])
+ if status == 0:
+ # Save the plugin.info file in the plugin directory
+ subprocess.call(['cp', os.path.join(tmp_dir, 'plugin.info'),
+ os.path.join(plugin_path, plugin_name) + '/'])
+ if self.has_toolbarbox:
+ palette_name_list = []
+ if file_info.has_option('Plugin', 'palette'):
+ palette_name_list = file_info.get(
+ 'Plugin', 'palette').split(',')
+ create_palette = []
+ for palette_name in palette_name_list:
+ if not palette_name.strip() in palette_names:
+ create_palette.append(True)
+ else:
+ create_palette.append(False)
+ self.tw.init_plugin(plugin_name)
+ self.tw.turtleart_plugins[-1].setup()
+ self.tw.load_media_shapes()
+ for i, palette_name in enumerate(palette_name_list):
+ if create_palette[i]:
+ j = len(self.palette_buttons)
+ self.palette_buttons.append(
+ self._radio_button_factory(
+ palette_name.strip() + 'off',
+ self._palette_toolbar,
+ self.do_palette_buttons_cb,
+ j - 1,
+ help_strings[palette_name.strip()],
+ self.palette_buttons[0]))
+ self._overflow_buttons.append(
+ self._add_button(
+ palette_name.strip() + 'off',
+ None,
+ self.do_palette_buttons_cb,
+ None,
+ arg=j - 1))
+ self._overflow_box.pack_start(
+ self._overflow_buttons[j - 1])
+ self.tw.palettes.insert(j - 1, [])
+ self.tw.palette_sprs.insert(j - 1, [None, None])
+ else:
+ # We need to change the index associated with the
+ # Trash Palette Button.
+ j = len(palette_names)
+ pidx = palette_names.index(palette_name.strip())
+ self.palette_buttons[pidx].connect(
+ 'clicked', self.do_palette_buttons_cb, j - 1)
+ self._overflow_buttons[pidx].connect(
+ 'clicked', self.do_palette_buttons_cb, j - 1)
+ self._setup_palette_toolbar()
+ else:
+ self.tw.showlabel('status', label=_('Please restart Turtle Art \
+in order to use the plugin.'))
+ else:
+ self.tw.showlabel('status', label=_('Plugin could not be installed.'))
+ status = subprocess.call(['rm', '-r', tmp_path])
+ shutil.rmtree(tmp_dir)
+
+
+def load_a_plugin(self, tmp_dir):
+ ''' Load a plugin from the Journal and initialize it '''
+ plugin_path = os.path.join(tmp_dir, 'plugin.info')
+ file_info = ConfigParser.ConfigParser()
+ if len(file_info.read(plugin_path)) == 0:
+ self.tw.showlabel('status',
+ label=_('Plugin could not be installed.'))
+ elif not file_info.has_option('Plugin', 'name'):
+ self.tw.showlabel(
+ 'status', label=_('Plugin could not be installed.'))
+ else:
+ plugin_name = file_info.get('Plugin', 'name')
+ tmp_path = os.path.join(tmp_dir, plugin_name)
+ plugin_path = os.path.join(self.bundle_path, 'plugins')
+ if os.path.exists(os.path.join(plugin_path, plugin_name)):
+ self._reload_plugin_alert(tmp_dir, tmp_path, plugin_path,
+ plugin_name, file_info)
+ else:
+ complete_plugin_install(self, tmp_dir, tmp_path, plugin_path,
+ plugin_name, file_info)
diff --git a/TurtleArt/taprimitive.py b/TurtleArt/taprimitive.py
new file mode 100644
index 0000000..197a8d8
--- /dev/null
+++ b/TurtleArt/taprimitive.py
@@ -0,0 +1,1174 @@
+#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=[])
+ # Until always executes its body once.
+ if controller == Primitive.controller_until:
+ loop_list = []
+ for arg_ast in new_arg_asts[1]:
+ loop_list.append(arg_ast)
+ loop_list.append(loop_ast)
+ return loop_list
+ else:
+ 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)]
+
+ elif self == LogoCode.active_turtle:
+ text = 'turtle = turtles.get_active_turtle()'
+ return 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/tasprite_factory.py b/TurtleArt/tasprite_factory.py
index 2bd8993..8360dbb 100755
--- a/TurtleArt/tasprite_factory.py
+++ b/TurtleArt/tasprite_factory.py
@@ -609,6 +609,51 @@ stroke-width="3.5" fill="%s" stroke="none" />\n' % (self._stroke)
svg += self.footer()
return self.header() + svg
+ def clamp_until(self):
+ ''' Until block is like clamp but docks are flipped '''
+ self.reset_min_max()
+ x = self._stroke_width / 2.0
+ y = self._stroke_width / 2.0 + self._radius
+ self.margins[0] = int((x + self._stroke_width + 0.5) * self._scale)
+ self.margins[1] = int((self._stroke_width + 0.5) * self._scale)
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, y)
+ svg += self._corner(1, -1)
+ svg += self._do_slot()
+ svg += self._rline_to(self._radius + self._stroke_width, 0)
+ svg += self._rline_to(self._expand_x, 0)
+ xx = self._x
+ svg += self._corner(1, 1, skip=True)
+ svg += self._corner(-1, 1, skip=True)
+ svg += self.line_to(xx, self._y)
+ svg += self._rline_to(-self._expand_x, 0)
+ svg += self._do_tab()
+ svg += self._inverse_corner(-1, 1, 90, 0, 0)
+ svg += self._rline_to(0, self._expand_y)
+ svg += self._inverse_corner(1, 1, 90, 0, 0)
+ svg += self._do_slot()
+ svg += self._rline_to(self._radius, 0)
+ if self._innie[0] is True:
+ svg += self._do_innie()
+ else:
+ self.margins[2] = \
+ int((self._x - self._stroke_width + 0.5) * self._scale)
+ svg += self._rline_to(0, self._radius + self._expand_y2)
+ if self._bool is True:
+ svg += self._do_boolean()
+ svg += self._corner(-1, 1)
+ svg += self._rline_to(-self._radius - self._stroke_width, 0)
+ svg += self._do_tab()
+ svg += self._corner(-1, -1)
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ if self._collapsible:
+ svg += self._hide_dot()
+ svg += self.footer()
+ return self.header() + svg
+
def status_block(self, graphic=None):
''' Generate a status block '''
self.reset_min_max()
@@ -950,7 +995,8 @@ stroke-width="3.5" fill="%s" stroke="none" />\n' % (self._stroke)
0)
return svg_str
- def _corner(self, sign_x, sign_y, a=90, l=0, s=1, start=True, end=True):
+ def _corner(self, sign_x, sign_y, a=90, l=0, s=1, start=True, end=True,
+ skip=False):
svg_str = ""
if sign_x == 1 and sign_y == -1: # Upper-left corner
self._hide_x = self._x + self._radius + self._stroke_width
@@ -970,7 +1016,7 @@ stroke-width="3.5" fill="%s" stroke="none" />\n' % (self._stroke)
if start:
if sign_x * sign_y == 1:
svg_str += self._rline_to(sign_x * r2, 0)
- else:
+ elif not skip:
svg_str += self._rline_to(0, sign_y * r2)
x = self._x + sign_x * r2
y = self._y + sign_y * r2
@@ -978,7 +1024,7 @@ stroke-width="3.5" fill="%s" stroke="none" />\n' % (self._stroke)
if end:
if sign_x * sign_y == 1:
svg_str += self._rline_to(0, sign_y * r2)
- else:
+ elif not skip:
svg_str += self._rline_to(sign_x * r2, 0)
return svg_str
@@ -1237,6 +1283,7 @@ def close_file(f):
def generator(datapath):
+ '''
svg = SVG()
f = open_file(datapath, "turtle.svg")
svg.set_scale(2)
@@ -1471,29 +1518,42 @@ def generator(datapath):
close_file(f)
svg = SVG()
- f = open_file(datapath, "clampb.svg")
+ f = open_file(datapath, "clampe.svg")
svg.set_scale(2)
svg.expand(30, 0, 0, 0)
svg.set_slot(True)
svg.set_tab(True)
svg.set_boolean(True)
- svg.second_clamp(False)
+ svg.second_clamp(True)
svg_str = svg.clamp()
f.write(svg_str)
close_file(f)
+ '''
svg = SVG()
- f = open_file(datapath, "clampe.svg")
+ f = open_file(datapath, "clampb.svg")
svg.set_scale(2)
- svg.expand(30, 0, 0, 0)
+ svg.expand(0, 30, 0, 0)
svg.set_slot(True)
svg.set_tab(True)
svg.set_boolean(True)
- svg.second_clamp(True)
+ svg.second_clamp(False)
svg_str = svg.clamp()
f.write(svg_str)
close_file(f)
+ svg = SVG()
+ f = open_file(datapath, "clampu.svg")
+ svg.set_scale(2)
+ svg.expand(0, 30, 0, 0)
+ svg.set_slot(True)
+ svg.set_tab(True)
+ svg.set_boolean(True)
+ svg.second_clamp(False)
+ svg_str = svg.clamp_until()
+ f.write(svg_str)
+ close_file(f)
+
def main():
return 0
diff --git a/TurtleArt/taturtle.py b/TurtleArt/taturtle.py
index ac72bdb..11336d7 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)
@@ -691,7 +653,6 @@ class Turtle:
def draw_pixbuf(self, pixbuf, a, b, x, y, w, h, path, share=True):
''' Draw a pixbuf '''
-
self._turtles.turtle_window.canvas.draw_pixbuf(
pixbuf, a, b, x, y, w, h, self._heading)
@@ -736,6 +697,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..0fcfc3c
--- /dev/null
+++ b/TurtleArt/tatype.py
@@ -0,0 +1,441 @@
+#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..ae5fb97 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,9 @@ 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()
+ self.activity.update_palette_from_metadata()
def _set_screen_dpi(self):
dpi = get_screen_dpi()
@@ -379,10 +402,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 +437,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 +786,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 +1504,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 +1532,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 +1603,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 +1628,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))
@@ -1598,10 +1640,7 @@ before making changes to your program'))
elif blk.name == 'restore':
self.restore_latest_from_trash()
elif blk.name == 'empty':
- if self.running_sugar:
- self.activity.empty_trash_alert()
- else:
- self.empty_trash()
+ self.empty_trash()
elif blk.name == 'trashall':
for b in self.just_blocks():
if b.type != 'trash':
@@ -1623,9 +1662,20 @@ before making changes to your program'))
name = blk.name
# You can only have one instance of some blocks
if blk.name in ['start', 'hat1', 'hat2']:
- if len(self.block_list.get_similar_blocks(
- 'block', blk.name)) > 0:
+ blk_list = self.block_list.get_similar_blocks(
+ 'block', blk.name)
+ if len(blk_list) > 0:
self.showlabel('dupstack')
+ if blk.name == 'start':
+ # Recenter the screen and move the start
+ # stack to the center of the screen
+ if self.running_sugar:
+ self.activity.recenter()
+ dx = 200 - blk_list[0].spr.get_xy()[0]
+ dy = 200 - blk_list[0].spr.get_xy()[1]
+ drag_group = find_group(blk_list[0])
+ for dblk in drag_group:
+ dblk.spr.move_relative((dx, dy))
return True
# We need to check to see if there is already a
# similarly default named stack
@@ -1797,7 +1847,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:
@@ -1854,17 +1904,21 @@ before making changes to your program'))
elif spr.name == _('shift'):
self._shift_toolbar_palette(self.selected_palette)
else:
- self.orientation = 1 - self.orientation
- self.palette_button[self.orientation].set_layer(TAB_LAYER)
- self.palette_button[1 - self.orientation].hide()
- self.palette_sprs[self.selected_palette][
- 1 - self.orientation].hide()
- self._layout_palette(self.selected_palette)
- self.show_palette(self.selected_palette)
+ self.set_orientation(1 - self.orientation)
elif spr.type == 'toolbar':
self._select_toolbar_button(spr)
return False
+ def set_orientation(self, orientation):
+ self.orientation = orientation
+ self.palette_button[self.orientation].set_layer(TAB_LAYER)
+ self.palette_button[1 - self.orientation].hide()
+ spr = self.palette_sprs[self.selected_palette][1 - self.orientation]
+ if spr is not None:
+ spr.hide()
+ self._layout_palette(self.selected_palette)
+ self.show_palette(self.selected_palette)
+
def _update_action_names(self, name):
''' change the label on action blocks of the same name '''
if isinstance(name, (float, int)):
@@ -2133,7 +2187,7 @@ before making changes to your program'))
for gblk in group:
if gblk.name == 'sandwichclampcollapsed':
restore_clamp(gblk)
- self.resize_parent_clamps(gblk)
+ self._resize_parent_clamps(gblk)
for gblk in group:
gblk.rescale(self.block_scale)
@@ -2157,10 +2211,27 @@ before making changes to your program'))
def empty_trash(self):
''' Permanently remove all blocks presently in the trash can. '''
+ title = _('empty trash')
+ msg = _('Do you really want to empty the trash?')
+ if self.running_sugar:
+ self.activity.empty_trash_alert(title, msg)
+ else:
+ dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_WARNING,
+ gtk.BUTTONS_OK_CANCEL, msg)
+ dialog.set_title(title)
+ res = dialog.run()
+ dialog.destroy()
+ if res == gtk.RESPONSE_OK:
+ self._empty_trash()
+
+ def _empty_trash(self):
+ remove_list = []
for blk in self.block_list.list:
if blk.type == 'trash':
- blk.type = 'deleted'
blk.spr.hide()
+ remove_list.append(blk)
+ for blk in remove_list:
+ self.block_list.list.remove(blk)
self.trash_stack = []
if 'trash' in palette_names:
self.show_toolbar_palette(palette_names.index('trash'),
@@ -2482,10 +2553,15 @@ before making changes to your program'))
if blk is None:
continue
if blk.name in EXPANDABLE_FLOW:
- if blk.name in block_styles['clamp-style-1arg'] or\
+ if blk.name in block_styles['clamp-style-1arg'] or \
blk.name in block_styles['clamp-style-boolean']:
if blk.connections[2] is not None:
self._resize_clamp(blk, blk.connections[2])
+ elif blk.name in block_styles['clamp-style-until']:
+ if blk.connections[2] is not None:
+ self._resize_clamp(blk, blk.connections[2])
+ if blk.connections[1] is not None:
+ self._resize_clamp(blk, blk.connections[1], dockn=1)
elif blk.name in block_styles['clamp-style']:
if blk.connections[1] is not None:
self._resize_clamp(blk, blk.connections[1])
@@ -3157,6 +3233,13 @@ before making changes to your program'))
if len(self.block_list.get_similar_blocks('block', 'forever')) > 0:
debug_output('WARNING: Projects with forever blocks \
may not terminate.', False)
+ else:
+ self._hide_text_entry()
+ self.parent.get_window().set_cursor(
+ gtk.gdk.Cursor(gtk.gdk.WATCH))
+ gobject.idle_add(self.__run_stack, blk)
+
+ def __run_stack(self, blk):
if self.status_spr is not None:
self.status_spr.hide()
self._autohide_shape = True
@@ -3166,15 +3249,19 @@ 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())
+ if self.interactive_mode:
+ self.parent.get_window().set_cursor(
+ gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
self.lc.run_blocks(code)
if self.interactive_mode:
gobject.idle_add(self.lc.doevalstep)
else:
while self.lc.doevalstep():
pass
+ self.running_blocks = False
def _snap_to_dock(self):
''' Snap a block (selected_block) to the dock of another block
@@ -3313,6 +3400,14 @@ before making changes to your program'))
if best_destination_dockn == 2:
self._resize_clamp(best_destination,
self.drag_group[0])
+ elif best_destination.name in \
+ block_styles['clamp-style-until']:
+ if best_destination_dockn == 2:
+ self._resize_clamp(best_destination,
+ self.drag_group[0])
+ elif best_destination_dockn == 1:
+ self._resize_clamp(best_destination,
+ self.drag_group[0], dockn=1)
elif best_destination.name in block_styles['clamp-style'] or \
best_destination.name in \
block_styles['clamp-style-collapsible']:
@@ -3410,16 +3505,19 @@ before making changes to your program'))
self._expand_expandable(blk2, blk, dy)
self._cascade_expandable(blk2)
elif c is not None and blk2.name in EXPANDABLE_FLOW:
- if blk2.name in block_styles['clamp-style-1arg'] or\
+ if blk2.name in block_styles['clamp-style-1arg'] or \
blk2.name in block_styles['clamp-style-boolean']:
if c == 2:
- self._resize_clamp(blk2, None, c)
+ self._resize_clamp(blk2, None, dockn=c)
+ elif blk2.name in block_styles['clamp-style-until']:
+ if c in [1, 2]:
+ self._resize_clamp(blk2, None, dockn=c)
elif blk2.name in block_styles['clamp-style'] or \
blk2.name in block_styles['clamp-style-collapsible']:
if c == 1:
self._resize_clamp(blk2, None)
elif blk2.name in block_styles['clamp-style-else']:
- if c == 2 or c == 3:
+ if c in [2, 3]:
self._resize_clamp(blk2, None, dockn=c)
while blk3 is not None and blk3.connections[dockn] is not None:
self._resize_clamp(blk3, blk3.connections[dockn], dockn=dockn)
@@ -3435,22 +3533,35 @@ before making changes to your program'))
y1 = blk.docks[-1][3]
if blk.name in block_styles['clamp-style-else'] and dockn == 3:
blk.reset_y2()
+ elif blk.name in block_styles['clamp-style-until'] and dockn == 1:
+ blk.reset_y2()
else:
blk.reset_y()
dy = 0
# Calculate height of drag group
- while gblk is not None:
- delta = int((gblk.docks[-1][3] - gblk.docks[0][3]) / gblk.scale)
- if delta == 0:
- dy += 21 # Fixme: don't hardcode size of stop action block
- else:
- dy += delta
- gblk = gblk.connections[-1]
- # Clamp has room for one 'standard' block by default
- if dy > 0:
- dy -= 21 # Fixme: don't hardcode
+ if blk.name in block_styles['clamp-style-until'] and dockn == 1:
+ if gblk is not None:
+ dy = int(gblk.spr.rect.height / gblk.scale)
+ # Room for part of one 'standard' boolean by default
+ if dy > 0:
+ dy -= 25 # Fixme: don't hardcode size of slot
+ if dy < 0:
+ dy = 0
+ else:
+ while gblk is not None:
+ delta = int((gblk.docks[-1][3] - gblk.docks[0][3]) / gblk.scale)
+ if delta == 0:
+ dy += 21 # Fixme: don't hardcode size of slot
+ else:
+ dy += delta
+ gblk = gblk.connections[-1]
+ # Clamp has room for one 'standard' block by default
+ if dy > 0:
+ dy -= 21 # Fixme: don't hardcode size of slot
if blk.name in block_styles['clamp-style-else'] and dockn == 3:
blk.expand_in_y2(dy)
+ elif blk.name in block_styles['clamp-style-until'] and dockn == 1:
+ blk.expand_in_y2(dy)
else:
blk.expand_in_y(dy)
y2 = blk.docks[-1][3]
@@ -3460,12 +3571,18 @@ before making changes to your program'))
drag_group = find_group(blk.connections[-1])
for gblk in drag_group:
gblk.spr.move_relative((0, y2-y1))
- # We may have to move the else clamp group down too.
+ # We may have to move the else clamp group up or down too.
if blk.name in block_styles['clamp-style-else'] and dockn == 2:
if blk.connections[3] is not None:
drag_group = find_group(blk.connections[3])
for gblk in drag_group:
gblk.spr.move_relative((0, y2 - y1))
+ # We may have to move the bool group up or down too.
+ if blk.name in block_styles['clamp-style-until']:
+ if blk.connections[1] is not None:
+ drag_group = find_group(blk.connections[1])
+ for gblk in drag_group:
+ gblk.spr.move_relative((0, y2 - y1))
def _expandable_flow_above(self, blk):
''' Is there an expandable flow block above this one? '''
@@ -3584,15 +3701,68 @@ 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
+ self.keypress = ''
+
+ 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 +3801,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:
@@ -3697,11 +3867,8 @@ before making changes to your program'))
self._snap_to_dock()
self.drag_group = None
- def _test_number(self):
- ''' Make sure a 'number' block contains a number. '''
+ def _hide_text_entry(self):
if hasattr(self, '_text_entry'):
- bounds = self._text_buffer.get_bounds()
- text = self._text_buffer.get_text(bounds[0], bounds[1])
if self._focus_out_id is not None:
self._text_entry.disconnect(self._focus_out_id)
self._focus_out_id = None
@@ -3709,6 +3876,13 @@ before making changes to your program'))
self._text_buffer.disconnect(self._insert_text_id)
self._insert_text_id = None
self._text_entry.hide()
+
+ def _test_number(self):
+ ''' Make sure a 'number' block contains a number. '''
+ if hasattr(self, '_text_entry'):
+ bounds = self._text_buffer.get_bounds()
+ text = self._text_buffer.get_text(bounds[0], bounds[1])
+ self._hide_text_entry()
else:
text = self.selected_blk.spr.labels[0]
self._number_check(text)
@@ -3755,12 +3929,9 @@ before making changes to your program'))
def _test_string(self):
if hasattr(self, '_text_entry'):
- if self._focus_out_id is not None:
- self._text_entry.disconnect(self._focus_out_id)
- self._focus_out_id = None
bounds = self._text_buffer.get_bounds()
text = self._text_buffer.get_text(bounds[0], bounds[1])
- self._text_entry.hide()
+ self._hide_text_entry()
else:
text = self.selected_blk.spr.labels[0]
self.selected_blk.spr.set_label(text.replace('\12', RETURN))
@@ -3908,8 +4079,6 @@ before making changes to your program'))
self.new_project()
self.process_data(data_from_file(ta_file))
self._loaded_project = ta_file
- # Always start on the Turtle palette
- self.show_toolbar_palette(palette_name_to_index('turtle'))
def load_file_from_chooser(self, create_new_project=True):
''' Load a project from file chooser '''
@@ -4349,6 +4518,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 +4594,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':
@@ -4384,13 +4610,20 @@ before making changes to your program'))
self.status_spr.move((PALETTE_WIDTH, self.height - 400))
else:
# Adjust vertical position based on scrolled window adjustment
+ offset_from_bottom = 60
if self.running_sugar:
+ if self.activity.toolbox.get_property("visible"):
+ if self.activity.toolbars_expanded():
+ offset_from_bottom += 110
+ else:
+ offset_from_bottom += 60
self.status_spr.move(
(0,
- self.height - 200 +
+ self.height - offset_from_bottom +
self.activity.sw.get_vadjustment().get_value()))
elif self.interactive_mode:
- self.status_spr.move((0, self.height - 100))
+ self.status_spr.move(
+ (0, self.activity.win.get_window().get_size()[1] - 80))
def calc_position(self, template):
''' Relative placement of portfolio objects (deprecated) '''
@@ -4620,7 +4853,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 +4861,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 +4880,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 +4888,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 +4907,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 +4916,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 +4972,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 2fc7283..3cbe1a5 100644
--- a/TurtleArtActivity.py
+++ b/TurtleArtActivity.py
@@ -28,7 +28,6 @@ import gtk
import cairo
import gobject
import dbus
-import glob
import logging
_logger = logging.getLogger('turtleart-activity')
@@ -42,14 +41,17 @@ except ImportError:
HAS_TOOLBARBOX = False
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.radiotoolbutton import RadioToolButton
-from sugar.graphics.alert import (ConfirmationAlert, NotifyAlert)
+from sugar.graphics.alert import (ConfirmationAlert, Alert)
from sugar.graphics import style
from sugar.graphics.objectchooser import ObjectChooser
-from sugar import mime
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
from sugar.datastore import datastore
from sugar import profile
+from sugar import mime
import os
+import glob
import tarfile
import subprocess
import ConfigParser
@@ -63,15 +65,19 @@ except ImportError:
from gettext import gettext as _
+from TurtleArt.taplugin import (load_a_plugin, cancel_plugin_install,
+ complete_plugin_install)
from TurtleArt.tapalette import (palette_names, help_strings, help_palettes,
- help_windows)
+ help_windows, default_values)
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)
@@ -80,6 +86,8 @@ if HAS_TOOLBARBOX:
class TurtleArtActivity(activity.Activity):
''' Activity subclass for Turtle Art '''
_HOVER_HELP = '/desktop/sugar/activities/turtleart/hoverhelp'
+ _ORIENTATION = '/desktop/sugar/activities/turtleart/orientation'
+ _COORDINATE_SCALE = '/desktop/sugar/activities/turtleart/coordinatescale'
def __init__(self, handle):
''' Set up the toolbars, canvas, sharing, etc. '''
@@ -90,8 +98,13 @@ class TurtleArtActivity(activity.Activity):
self.tw = None
self.init_complete = False
+
self._stop_help = False
+ self.bundle_path = activity.get_bundle_path()
+
+ self.error_list = []
+
self.palette_buttons = []
self._palette_names = []
self._overflow_buttons = []
@@ -107,18 +120,28 @@ class TurtleArtActivity(activity.Activity):
self._setup_toolbar()
_logger.debug('_setup_canvas')
- self._canvas = self._setup_canvas(self._setup_scrolled_window())
-
- # FIX ME: not sure how or why self.canvas gets overwritten
- # It is set to self.sw in _setup_canvas but None here.
- # We need self.canvas for generating the preview image
- self.canvas = self.sw
+ self._setup_canvas(self._setup_scrolled_window())
_logger.debug('_setup_palette_toolbar')
self._setup_palette_toolbar()
self._setup_extra_controls()
_logger.debug('_setup_sharing')
+ if self.shared_activity:
+ # We're joining
+ if not self.get_shared():
+ xocolors = XoColor(profile.get_color().to_string())
+ share_icon = Icon(icon_name='zoom-neighborhood',
+ xo_color=xocolors)
+ self._joined_alert = Alert()
+ self._joined_alert.props.icon = share_icon
+ self._joined_alert.props.title = _('Please wait')
+ self._joined_alert.props.msg = _('Starting connection...')
+ self.add_alert(self._joined_alert)
+
+ # Wait for joined signal
+ self.connect("joined", self._joined_cb)
+
self._setup_sharing()
# Activity count is the number of times this instance has been
@@ -137,6 +160,16 @@ class TurtleArtActivity(activity.Activity):
self.client = gconf.client_get_default()
if self.client.get_int(self._HOVER_HELP) == 1:
self._do_hover_help_toggle(None)
+ if not self.client.get_int(self._COORDINATE_SCALE) in [0, 1]:
+ self.tw.coord_scale = 1
+ self.do_rescale_cb(None)
+ else:
+ self.tw.coord_scale = 0
+ self.do_rescale_cb(None)
+
+ self._selected_sample = None
+ self._sample_window = None
+
self.init_complete = True
if not hasattr(self, '_offsets'):
@@ -150,6 +183,30 @@ class TurtleArtActivity(activity.Activity):
self._challenge_window = None
self._load_level()
+ def update_palette_from_metadata(self):
+ if HAS_GCONF:
+ # We have to wait to set the orientation for the palettes
+ # to be loaded.
+ self.client = gconf.client_get_default()
+ if self.client.get_int(self._ORIENTATION) == 1:
+ self.tw.set_orientation(1)
+
+ if 'palette' in self.metadata:
+ n = int(self.metadata['palette'])
+ if n == -1:
+ self.tw.hideshow_palette(False)
+ else:
+ # Try to set radio button to active
+ if n < len(self.palette_buttons):
+ self.palette_buttons[n].set_active(True)
+ else:
+ self.tw.show_palette(n=0)
+ if 'orientation' in self.metadata:
+ self.tw.set_orientation(int(self.metadata['orientation']))
+ else:
+ # Else start on the Turtle palette
+ self.tw.show_palette(n=0)
+
def check_buttons_for_fit(self):
''' Check to see which set of buttons to display '''
if not self.has_toolbarbox:
@@ -159,51 +216,96 @@ class TurtleArtActivity(activity.Activity):
# scrolling window
self._setup_palette_toolbar()
- if self.samples_button in self._toolbox.toolbar:
- self._toolbox.toolbar.remove(self.extras_separator)
- self._toolbox.toolbar.remove(self.samples_button)
- self._toolbox.toolbar.remove(self.stop_separator)
- self._toolbox.toolbar.remove(self.stop_button)
+ if self.samples_button in self.toolbox.toolbar:
+ self.toolbox.toolbar.remove(self.extras_separator)
+ self.toolbox.toolbar.remove(self.samples_button)
+ self.toolbox.toolbar.remove(self.stop_separator)
+ self.toolbox.toolbar.remove(self.stop_button)
self._view_toolbar.remove(self._coordinates_toolitem)
if gtk.gdk.screen_width() / 14 < style.GRID_CELL_SIZE:
self.samples_button2.show()
self.samples_label2.show()
- self._toolbox.toolbar.insert(self.stop_button, -1)
+ self.toolbox.toolbar.insert(self.stop_button, -1)
else:
self.samples_button2.hide()
self.samples_label2.hide()
- self._toolbox.toolbar.insert(self.extras_separator, -1)
+ self.toolbox.toolbar.insert(self.extras_separator, -1)
self.extras_separator.props.draw = True
self.extras_separator.show()
- self._toolbox.toolbar.insert(self.samples_button, -1)
+ self.toolbox.toolbar.insert(self.samples_button, -1)
self.samples_button.show()
- self._toolbox.toolbar.insert(self.stop_separator, -1)
+ self.toolbox.toolbar.insert(self.stop_separator, -1)
self.stop_separator.show()
- self._toolbox.toolbar.insert(self.stop_button, -1)
+ self.toolbox.toolbar.insert(self.stop_button, -1)
self._view_toolbar.insert(self._coordinates_toolitem, -1)
- self._toolbox.show_all()
+ self.toolbox.show_all()
# Activity toolbar callbacks
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()
- 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'))
+ 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(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. '''
@@ -230,7 +332,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()
@@ -257,13 +358,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()
@@ -276,7 +391,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
@@ -285,7 +402,7 @@ class TurtleArtActivity(activity.Activity):
if self.tw.palette:
self.tw.hideshow_palette(False)
self.do_hidepalette()
- if self.has_toolbarbox and self.tw.selected_palette is not None:
+ if not self.has_toolbarbox and self.tw.selected_palette is not None:
self.palette_buttons[self.tw.selected_palette].set_icon(
palette_names[self.tw.selected_palette] + 'off')
else:
@@ -324,7 +441,8 @@ class TurtleArtActivity(activity.Activity):
self.tw.no_help = False
self._hover_help_toggle.set_icon('help-off')
self._hover_help_toggle.set_tooltip(_('Turn off hover help'))
- self.client.set_int(self._HOVER_HELP, 0)
+ if HAS_GCONF:
+ self.client.set_int(self._HOVER_HELP, 0)
else:
self.tw.no_help = True
self.tw.last_label = None
@@ -332,7 +450,8 @@ class TurtleArtActivity(activity.Activity):
self.tw.status_spr.hide()
self._hover_help_toggle.set_icon('help-on')
self._hover_help_toggle.set_tooltip(_('Turn on hover help'))
- self.client.set_int(self._HOVER_HELP, 1)
+ if HAS_GCONF:
+ self.client.set_int(self._HOVER_HELP, 1)
# These methods are called both from toolbar buttons and blocks.
@@ -503,7 +622,7 @@ class TurtleArtActivity(activity.Activity):
if self.tw.block_scale in BLOCK_SCALE:
i = BLOCK_SCALE.index(self.tw.block_scale) + inc
else:
- i = BLOCK_SCALE[3] # 2.0
+ i = 3
if i < 0:
self.tw.block_scale = BLOCK_SCALE[0]
elif i == len(BLOCK_SCALE):
@@ -534,18 +653,30 @@ class TurtleArtActivity(activity.Activity):
self.tw.set_metric(True)
def do_rescale_cb(self, button):
- ''' Rescale coordinate system (100==height/2 or 100 pixels). '''
+ ''' Rescale coordinate system (20==height/2 or 100 pixels). '''
if self.tw.coord_scale == 1:
- self.tw.coord_scale = self.tw.height / 200
+ self.tw.coord_scale = self.tw.height / 40
self.rescale_button.set_icon('contract-coordinates')
self.rescale_button.set_tooltip(_('Rescale coordinates down'))
+ default_values['forward'] = [10]
+ default_values['back'] = [10]
+ default_values['arc'] = [90, 10]
+ default_values['setpensize'] = [1]
+ self.tw.turtles.get_active_turtle().set_pen_size(1)
else:
self.tw.coord_scale = 1
self.rescale_button.set_icon('expand-coordinates')
self.rescale_button.set_tooltip(_('Rescale coordinates up'))
- self.tw.eraser_button()
+ default_values['forward'] = [100]
+ default_values['back'] = [100]
+ default_values['arc'] = [90, 100]
+ default_values['setpensize'] = [5]
+ self.tw.turtles.get_active_turtle().set_pen_size(5)
+ if HAS_GCONF:
+ self.client.set_int(self._COORDINATE_SCALE, self.tw.coord_scale)
# Given the change in how overlays are handled (v123), there is no way
# to erase and then redraw the overlays.
+ self.tw.eraser_button()
def _do_help_cb(self, button):
if self._selected_challenge is None:
@@ -637,8 +768,10 @@ class TurtleArtActivity(activity.Activity):
def _setup_toolbar(self):
''' Setup toolbar according to Sugar version. '''
if self.has_toolbarbox:
+ self.max_participants = 4
+
self._setup_toolbar_help()
- self._toolbox = ToolbarBox()
+ self.toolbox = ToolbarBox()
self.activity_toolbar_button = ActivityToolbarButton(self)
@@ -659,33 +792,31 @@ class TurtleArtActivity(activity.Activity):
_('Help'),
self._do_help_cb,
None)
- # self._help_button = HelpButton(self)
self._make_load_save_buttons(self.activity_toolbar_button)
self.activity_toolbar_button.show()
- self._toolbox.toolbar.insert(self.activity_toolbar_button, -1)
+ self.toolbox.toolbar.insert(self.activity_toolbar_button, -1)
self.edit_toolbar_button.show()
- self._toolbox.toolbar.insert(self.edit_toolbar_button, -1)
+ self.toolbox.toolbar.insert(self.edit_toolbar_button, -1)
self.view_toolbar_button.show()
- self._toolbox.toolbar.insert(self.view_toolbar_button, -1)
+ self.toolbox.toolbar.insert(self.view_toolbar_button, -1)
self.palette_toolbar_button.show()
- self._toolbox.toolbar.insert(self.palette_toolbar_button, -1)
+ self.toolbox.toolbar.insert(self.palette_toolbar_button, -1)
- self.set_toolbar_box(self._toolbox)
- self.palette_toolbar_button.set_expanded(True)
+ self.set_toolbar_box(self.toolbox)
else:
- self._toolbox = activity.ActivityToolbox(self)
- self.set_toolbox(self._toolbox)
+ self.toolbox = activity.ActivityToolbox(self)
+ self.set_toolbox(self.toolbox)
self._project_toolbar = gtk.Toolbar()
- self._toolbox.add_toolbar(_('Project'), self._project_toolbar)
+ self.toolbox.add_toolbar(_('Project'), self._project_toolbar)
self._view_toolbar = gtk.Toolbar()
- self._toolbox.add_toolbar(_('View'), self._view_toolbar)
+ self.toolbox.add_toolbar(_('View'), self._view_toolbar)
edit_toolbar = gtk.Toolbar()
- self._toolbox.add_toolbar(_('Edit'), edit_toolbar)
+ self.toolbox.add_toolbar(_('Edit'), edit_toolbar)
journal_toolbar = gtk.Toolbar()
- self._toolbox.add_toolbar(_('Save/Load'), journal_toolbar)
+ self.toolbox.add_toolbar(_('Save/Load'), journal_toolbar)
self._make_palette_buttons(self._project_toolbar,
palette_button=True)
@@ -737,10 +868,14 @@ class TurtleArtActivity(activity.Activity):
edit_toolbar.show()
self._view_toolbar.show()
- self._toolbox.show()
+ self.toolbox.show()
- if not self.has_toolbarbox:
- self._toolbox.set_current_toolbar(1)
+ if self.has_toolbarbox:
+ self.edit_toolbar_button.set_expanded(True)
+ self.edit_toolbar_button.set_expanded(False)
+ self.palette_toolbar_button.set_expanded(True)
+ else:
+ self.toolbox.set_current_toolbar(1)
def _setup_extra_controls(self):
''' Add the rest of the buttons to the main toolbar '''
@@ -753,24 +888,24 @@ class TurtleArtActivity(activity.Activity):
self._make_project_buttons(self._project_toolbar)
return
- self._make_project_buttons(self._toolbox.toolbar)
+ self._make_project_buttons(self.toolbox.toolbar)
self.extras_separator = self._add_separator(
- self._toolbox.toolbar, expand=False, visible=True)
+ self.toolbox.toolbar, expand=False, visible=True)
self.samples_button = self._add_button(
'ta-open', _('Load challenges'), self._create_store,
- self._toolbox.toolbar)
+ self.toolbox.toolbar)
- self._toolbox.toolbar.insert(self._help_button, -1)
+ self.toolbox.toolbar.insert(self._help_button, -1)
self._help_button.show()
self.stop_separator = self._add_separator(
- self._toolbox.toolbar, expand=True, visible=False)
+ self.toolbox.toolbar, expand=True, visible=False)
self.stop_button = StopButton(self)
self.stop_button.props.accelerator = '<Ctrl>Q'
- self._toolbox.toolbar.insert(self.stop_button, -1)
+ self.toolbox.toolbar.insert(self.stop_button, -1)
self.stop_button.show()
def _setup_toolbar_help(self):
@@ -816,12 +951,11 @@ class TurtleArtActivity(activity.Activity):
help_palettes['activity-toolbar'].show()
add_paragraph(help_box, _('Share selected blocks'), icon='shareon')
- if gtk.gdk.screen_width() < 1024:
- add_paragraph(help_box, _('Save/Load'), icon='save-load')
- else:
- add_section(help_box, _('Save/Load'), icon='turtleoff')
+ add_paragraph(help_box, _('Save/Load'), icon='save-load')
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']
if activity.get_bundle_path()[0:len(home)] == home:
@@ -999,10 +1133,10 @@ class TurtleArtActivity(activity.Activity):
self._share_cb, toolbar)
if self.has_toolbarbox:
self._add_separator(toolbar, expand=False, visible=True)
- save_load_button = self._add_button(
- 'save-load', _('Save/Load'), self._save_load_palette_cb,
+ save_button = self._add_button(
+ 'save', _('Save'), self._save_load_palette_cb,
toolbar)
- self._palette = save_load_button.get_palette()
+ self._save_palette = save_button.get_palette()
button_box = gtk.VBox()
self.save_as_image, label = self._add_button_and_label(
'image-saveoff', _('Save as image'), self.do_save_as_image_cb,
@@ -1010,16 +1144,28 @@ 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)
-
- # When screen is in portrait mode, the buttons don't fit
- # on the main toolbar, so put them here.
+ 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)
+
+ load_button = self._add_button(
+ 'load', _('Load'), self._save_load_palette_cb,
+ toolbar)
+ button_box.show_all()
+ self._save_palette.set_content(button_box)
+
+ self._load_palette = load_button.get_palette()
+ button_box = gtk.VBox()
+ # When screen is in portrait mode, the buttons don't fit
+ # on the main toolbar, so put them here.
self.samples_button2, self.samples_label2 = \
self._add_button_and_label('ta-open',
- _('Load challenges'),
- self._create_store,
+ _('Load example'),
+ self.do_samples_cb,
None,
button_box)
@@ -1038,7 +1184,7 @@ class TurtleArtActivity(activity.Activity):
'pippy-openoff', _('Load Python block'),
self.do_load_python_cb, None, button_box)
button_box.show_all()
- self._palette.set_content(button_box)
+ self._load_palette.set_content(button_box)
else:
self.save_as_image = self._add_button(
'image-saveoff', _('Save as image'), self.do_save_as_image_cb,
@@ -1046,10 +1192,14 @@ 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(
- 'load-from-journal', _('Load project'),
+ 'load-from-journal', _('Add project'),
self.do_load_ta_project_cb, toolbar)
# Only enable plugin loading if installed in $HOME
if activity.get_bundle_path()[0:len(home)] == home:
@@ -1061,13 +1211,12 @@ class TurtleArtActivity(activity.Activity):
self.do_load_python_cb, toolbar)
def _save_load_palette_cb(self, button):
- if self._palette:
- if not self._palette.is_up():
- self._palette.popup(immediate=True,
- state=self._palette.SECONDARY)
+ palette = button.get_palette()
+ if palette:
+ if not palette.is_up():
+ palette.popup(immediate=True, state=palette.SECONDARY)
else:
- self._palette.popdown(immediate=True)
- return
+ palette.popdown(immediate=True)
def _make_palette_buttons(self, toolbar, palette_button=False):
''' Creates the palette and block buttons for both toolbar types'''
@@ -1208,6 +1357,12 @@ class TurtleArtActivity(activity.Activity):
self._collaboration = Collaboration(self.tw, self)
self._collaboration.setup()
+ def _joined_cb(self, widget):
+ if self._joined_alert is not None:
+ self.remove_alert(self._joined_alert)
+ self._joined_alert = None
+ self.set_canvas(self.fixed)
+
def send_xy(self):
''' Resync xy position (and orientation) of my turtle. '''
self._collaboration.send_my_xy()
@@ -1243,112 +1398,26 @@ class TurtleArtActivity(activity.Activity):
self._offsets[basename][2])
self.metadata['mime_type'] = MIMETYPE[0]
self.metadata['turtle blocks'] = ''.join(self.tw.used_block_list)
- self.metadata['public'] = data_to_string(['activity count',
- 'turtle blocks'])
- _logger.debug('Wrote to file: %s' % (file_path))
-
- def _load_a_plugin(self, tmp_dir):
- ''' Load a plugin from the Journal and initialize it '''
- plugin_path = os.path.join(tmp_dir, 'plugin.info')
- _logger.debug(plugin_path)
- file_info = ConfigParser.ConfigParser()
- if len(file_info.read(plugin_path)) == 0:
- _logger.debug('Required file plugin.info could not be found.')
- self.tw.showlabel('status',
- label=_('Plugin could not be installed.'))
- elif not file_info.has_option('Plugin', 'name'):
- _logger.debug('Required open name not found in \
-Plugin section of plugin.info file.')
- self.tw.showlabel(
- 'status', label=_('Plugin could not be installed.'))
- else:
- plugin_name = file_info.get('Plugin', 'name')
- _logger.debug('Plugin name: %s' % (plugin_name))
- tmp_path = os.path.join(tmp_dir, plugin_name)
- plugin_path = os.path.join(activity.get_bundle_path(), 'plugins')
- if os.path.exists(os.path.join(plugin_path, plugin_name)):
- self._reload_plugin_alert(tmp_dir, tmp_path, plugin_path,
- plugin_name, file_info)
- else:
- self._complete_plugin_install(tmp_dir, tmp_path, plugin_path,
- plugin_name, file_info)
-
- def _complete_plugin_install(self, tmp_dir, tmp_path, plugin_path,
- plugin_name, file_info):
- ''' We complete the installation directly or from ConfirmationAlert '''
- status = subprocess.call(['cp', '-r', tmp_path, plugin_path + '/'])
- if status == 0:
- # Save the plugin.info file in the plugin directory
- subprocess.call(['cp', os.path.join(tmp_dir, 'plugin.info'),
- os.path.join(plugin_path, plugin_name) + '/'])
- _logger.debug('Plugin installed successfully.')
- if self.has_toolbarbox:
- palette_name_list = []
- if file_info.has_option('Plugin', 'palette'):
- palette_name_list = file_info.get(
- 'Plugin', 'palette').split(',')
- create_palette = []
- for palette_name in palette_name_list:
- if not palette_name.strip() in palette_names:
- create_palette.append(True)
- else:
- create_palette.append(False)
- _logger.debug('Initializing plugin...')
- self.tw.init_plugin(plugin_name)
- self.tw.turtleart_plugins[-1].setup()
- self.tw.load_media_shapes()
- for i, palette_name in enumerate(palette_name_list):
- if create_palette[i]:
- _logger.debug('Creating plugin palette %s (%d)' %
- (palette_name.strip(), i))
- j = len(self.palette_buttons)
- self.palette_buttons.append(
- self._radio_button_factory(
- palette_name.strip() + 'off',
- self._palette_toolbar,
- self.do_palette_buttons_cb,
- j - 1,
- help_strings[palette_name.strip()],
- self.palette_buttons[0]))
- self._overflow_buttons.append(
- self._add_button(
- palette_name.strip() + 'off',
- None,
- self.do_palette_buttons_cb,
- None,
- arg=j - 1))
- self._overflow_box.pack_start(
- self._overflow_buttons[j - 1])
- self.tw.palettes.insert(j - 1, [])
- self.tw.palette_sprs.insert(j - 1, [None, None])
- else:
- _logger.debug('Palette already exists... \
-skipping insert')
- # We need to change the index associated with the
- # Trash Palette Button.
- j = len(palette_names)
- pidx = palette_names.index(palette_name.strip())
- self.palette_buttons[pidx].connect(
- 'clicked', self.do_palette_buttons_cb, j - 1)
- self._overflow_buttons[pidx].connect(
- 'clicked', self.do_palette_buttons_cb, j - 1)
- _logger.debug('reinitializing palette toolbar')
- self._setup_palette_toolbar()
- else:
- self.tw.showlabel('status',
- label=_('Please restart Turtle Art \
-in order to use the plugin.'))
+ # Deprecated
+ # self.metadata['public'] = data_to_string(['activity count',
+ # 'turtle blocks'])
+ if self.tw.palette:
+ self.metadata['palette'] = str(self.tw.selected_palette)
else:
- self.tw.showlabel(
- 'status', label=_('Plugin could not be installed.'))
- status = subprocess.call(['rm', '-r', tmp_path])
- if status != 0:
- _logger.debug('Problems cleaning up tmp_path.')
- shutil.rmtree(tmp_dir)
+ self.metadata['palette'] = '-1'
+ self.metadata['orientation'] = str(self.tw.orientation)
+ if HAS_GCONF:
+ self.client.set_int(self._ORIENTATION, self.tw.orientation)
+ 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 _cancel_plugin_install(self, tmp_dir):
- ''' If we cancel, just cleanup '''
- shutil.rmtree(tmp_dir)
def _reload_plugin_alert(self, tmp_dir, tmp_path, plugin_path, plugin_name,
file_info):
@@ -1363,8 +1432,8 @@ in order to use the plugin.'))
if response_id is gtk.RESPONSE_OK:
_logger.debug('continue to install')
self.remove_alert(alert)
- self._complete_plugin_install(tmp_dir, tmp_path, plugin_path,
- plugin_name, file_info)
+ complete_plugin_install(self, tmp_dir, tmp_path, plugin_path,
+ plugin_name, file_info)
elif response_id is gtk.RESPONSE_CANCEL:
_logger.debug('cancel install')
self.remove_alert(alert)
@@ -1424,7 +1493,7 @@ in order to use the plugin.'))
gobject.idle_add(self._project_loader, turtle_code)
else:
_logger.debug('load a plugin from %s' % (tmp_dir))
- self._load_a_plugin(tmp_dir)
+ load_a_plugin(self, tmp_dir)
self.restore_cursor()
except:
_logger.debug('Could not extract files from %s.' %
@@ -1557,6 +1626,25 @@ in order to use the plugin.'))
self._old_cursor = self.get_window().get_cursor()
self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND1))
+ def empty_trash_alert(self, title, msg):
+ ''' We get confirmation from the user before emptying the trash '''
+ alert = ConfirmationAlert()
+ alert.props.title = title
+ alert.props.msg = msg
+
+ def _empty_trash_alert_response_cb(alert, response_id, self):
+ if response_id is gtk.RESPONSE_OK:
+ _logger.debug('emptying the trash')
+ self.remove_alert(alert)
+ self.tw._empty_trash()
+ elif response_id is gtk.RESPONSE_CANCEL:
+ _logger.debug('cancel emptying the trash')
+ self.remove_alert(alert)
+
+ alert.connect('response', _empty_trash_alert_response_cb, self)
+ self.add_alert(alert)
+ alert.show()
+
def _add_label(self, string, toolbar, width=None):
''' Add a label to a toolbar. '''
label = gtk.Label(string)
@@ -1609,6 +1697,7 @@ in order to use the plugin.'))
return button
def _load_level(self, custom=False):
+ logging.error('LOAD LEVEL')
self.tw.canvas.clearscreen()
self._draw_cartoon()
if custom:
@@ -1673,19 +1762,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 hide_store(self, widget=None):
if self._challenge_window is not None:
self._challenge_box.hide()
@@ -1773,6 +1849,17 @@ in order to use the plugin.'))
filepath, 100, 100)
store.append([pixbuf, filepath])
+ def is_toolbar_expanded(self):
+ if self.palette_toolbar_button.is_expanded():
+ return True
+ elif self.edit_toolbar_button.is_expanded():
+ return True
+ elif self.view_toolbar_button.is_expanded():
+ return True
+ elif self.activity_toolbar_button.is_expanded():
+ return True
+ return False
+
def _scan_for_challenges(self):
file_list = list(glob.glob(os.path.join(activity.get_bundle_path(),
'samples', 'thumbnails',
diff --git a/icons/load.svg b/icons/load.svg
new file mode 100644
index 0000000..189948f
--- /dev/null
+++ b/icons/load.svg
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="49.517162"
+ height="50.998993"
+ id="svg2"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="save.svg">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="640"
+ inkscape:window-height="480"
+ id="namedview27"
+ showgrid="false"
+ inkscape:zoom="4.6275424"
+ inkscape:cx="24.758581"
+ inkscape:cy="25.499496"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg2" />
+ <defs
+ id="defs4" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(-146.6683,-689.71929)"
+ id="layer1">
+ <g
+ transform="translate(0,-1.4025637)"
+ id="g3406">
+ <g
+ transform="matrix(0.55205508,0,0,0.55205508,204.78435,703.45425)"
+ id="g4382">
+ <g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g>
+ <g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g>
+ <path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ id="line4326"
+ y2="23.725029"
+ y1="58.753029"
+ x2="-66.884659"
+ x1="-66.884659"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="translate(0.12541438,0)"
+ id="g3396">
+ <g
+ transform="matrix(1,0,0,-1,90.781727,733.42579)"
+ id="g4770">
+ <g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772" />
+ </g>
+ <g
+ transform="matrix(0,-1,-1,0,211.6059,774.10486)"
+ id="g4770-3">
+ <g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772-5">
+ <polyline
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ id="polyline4774-1" />
+ <path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776-7"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/icons/python-saveoff.svg b/icons/python-saveoff.svg
new file mode 100644
index 0000000..1064219
--- /dev/null
+++ b/icons/python-saveoff.svg
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 54.999998 55.000001"
+ id="Icon"
+ xml:space="preserve"
+ style="overflow:visible"><metadata
+ id="metadata26"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs24"><linearGradient
+ id="linearGradient3166-6"><stop
+ id="stop3168-5"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" /><stop
+ id="stop3170-6"
+ style="stop-color:#ff0000;stop-opacity:1"
+ offset="1" /></linearGradient><linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient3172-9"
+ xlink:href="#linearGradient3166-6"
+ gradientUnits="userSpaceOnUse" /><linearGradient
+ id="linearGradient3166"><stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" /><stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" /></linearGradient><linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" /></defs><g
+ transform="matrix(1.1181651,0,0,1.1181651,38.678832,-23.386068)"
+ id="g3348"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m -19.754174,46.744351 c 3.555069,0 8.83424,-1.56838 8.83424,-6.181226 0,-5.131219 -4.597011,-5.60538 -6.503378,-6.124857 -2.107038,-0.441556 -3.510542,-1.057744 -3.594847,-1.909356 -0.144269,-1.460061 0.687503,-2.028723 2.342736,-2.028723 0,0 3.937412,2.024856 7.282311,0.40895 0.942794,-0.454819 2.631273,-2.579702 2.631273,-4.04529 0,-1.466142 -5.450749,-3.160522 -7.104794,-3.160522 -1.655233,0 -3.062894,2.125988 -3.062894,2.125988 -3.309277,0 -6.619149,2.932283 -6.619149,5.864566 0,2.93173 3.166197,5.225166 6.950433,5.864565 1.759131,0.259187 3.230316,1.226851 2.896064,3.005231 -0.27132,1.444036 -1.778128,2.932283 -4.963917,2.932283 -2.524407,0 -7.896195,-0.121026 -8.75409,-2.254199 -0.551547,-1.373851 0.09974,-2.876467 0.927358,-2.876467 l -0.01603,-0.08842 c -0.843052,-0.08732 -3.293841,0.08842 -3.293841,3.020151 -5.94e-4,3.759026 5.428782,5.447327 12.048526,5.447327 z"
+ id="path2474"
+ style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
+ d="m -11.393706,30.909692 c -1.557272,-0.158607 -3.924943,-1.105272 -4.43315,-2.775335"
+ id="path2476"
+ style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><circle
+ cx="35.805"
+ cy="10.96"
+ r="1.676"
+ transform="matrix(0.59369893,0,0,0.5526353,-36.672813,19.93767)"
+ id="circle2478"
+ style="fill:none;stroke:#ffffff;stroke-width:3.42034841;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g
+ transform="translate(-1.2440512,-2.6168323)"
+ id="g3830"><g
+ transform="matrix(0.55205508,0,0,0.55205508,75.618464,18.235971)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ id="line4326"
+ y2="23.725029"
+ y1="58.753029"
+ x2="-66.884659"
+ x1="-66.884659"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g><g
+ transform="matrix(1,0,0,-1,-30.386573,49.171266)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ id="polyline4774"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg> \ No newline at end of file
diff --git a/icons/python-saveon.svg b/icons/python-saveon.svg
new file mode 100644
index 0000000..8871ae7
--- /dev/null
+++ b/icons/python-saveon.svg
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 54.999998 55.000001"
+ id="Icon"
+ xml:space="preserve"
+ style="overflow:visible"><metadata
+ id="metadata26"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs24"><linearGradient
+ id="linearGradient3166-6"><stop
+ id="stop3168-5"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" /><stop
+ id="stop3170-6"
+ style="stop-color:#ff0000;stop-opacity:1"
+ offset="1" /></linearGradient><linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient3172-9"
+ xlink:href="#linearGradient3166-6"
+ gradientUnits="userSpaceOnUse" /><linearGradient
+ id="linearGradient3166"><stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" /><stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" /></linearGradient><linearGradient
+ x1="0.94254935"
+ y1="-31.669659"
+ x2="104.37702"
+ y2="20.434471"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" /></defs><g
+ transform="matrix(1.1181651,0,0,1.1181651,38.678832,-23.386068)"
+ id="g3348"
+ style="fill:none;stroke:#00ff00;stroke-opacity:1"><path
+ d="m -19.754174,46.744351 c 3.555069,0 8.83424,-1.56838 8.83424,-6.181226 0,-5.131219 -4.597011,-5.60538 -6.503378,-6.124857 -2.107038,-0.441556 -3.510542,-1.057744 -3.594847,-1.909356 -0.144269,-1.460061 0.687503,-2.028723 2.342736,-2.028723 0,0 3.937412,2.024856 7.282311,0.40895 0.942794,-0.454819 2.631273,-2.579702 2.631273,-4.04529 0,-1.466142 -5.450749,-3.160522 -7.104794,-3.160522 -1.655233,0 -3.062894,2.125988 -3.062894,2.125988 -3.309277,0 -6.619149,2.932283 -6.619149,5.864566 0,2.93173 3.166197,5.225166 6.950433,5.864565 1.759131,0.259187 3.230316,1.226851 2.896064,3.005231 -0.27132,1.444036 -1.778128,2.932283 -4.963917,2.932283 -2.524407,0 -7.896195,-0.121026 -8.75409,-2.254199 -0.551547,-1.373851 0.09974,-2.876467 0.927358,-2.876467 l -0.01603,-0.08842 c -0.843052,-0.08732 -3.293841,0.08842 -3.293841,3.020151 -5.94e-4,3.759026 5.428782,5.447327 12.048526,5.447327 z"
+ id="path2474"
+ style="fill:none;stroke:#00ff00;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path
+ d="m -11.393706,30.909692 c -1.557272,-0.158607 -3.924943,-1.105272 -4.43315,-2.775335"
+ id="path2476"
+ style="fill:none;stroke:#00ff00;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><circle
+ cx="35.805"
+ cy="10.96"
+ r="1.676"
+ transform="matrix(0.59369893,0,0,0.5526353,-36.672813,19.93767)"
+ id="circle2478"
+ style="fill:none;stroke:#00ff00;stroke-width:3.42034841;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g
+ transform="translate(-1.2440512,-2.6168323)"
+ id="g3830"><g
+ transform="matrix(0.55205508,0,0,0.55205508,75.618464,18.235971)"
+ id="g4382"><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1"><path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line
+ id="line4326"
+ y2="23.725029"
+ y1="58.753029"
+ x2="-66.884659"
+ x1="-66.884659"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g><g
+ transform="matrix(1,0,0,-1,-30.386573,49.171266)"
+ id="g4770"><g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772"><polyline
+ id="polyline4774"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg> \ No newline at end of file
diff --git a/icons/save.svg b/icons/save.svg
new file mode 100644
index 0000000..1f1ad11
--- /dev/null
+++ b/icons/save.svg
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ width="49.517162"
+ height="50.998993"
+ id="svg2"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="save.svg">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="640"
+ inkscape:window-height="480"
+ id="namedview27"
+ showgrid="false"
+ inkscape:zoom="4.6275424"
+ inkscape:cx="24.758581"
+ inkscape:cy="25.499496"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg2" />
+ <defs
+ id="defs4" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(-146.6683,-689.71929)"
+ id="layer1">
+ <g
+ transform="translate(0,-1.4025637)"
+ id="g3406">
+ <g
+ transform="matrix(0.55205508,0,0,0.55205508,204.78435,703.45425)"
+ id="g4382">
+ <g
+ transform="translate(-80.093659,12.220029)"
+ id="g4308"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4310"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389"
+ id="path4312"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g>
+ <g
+ transform="translate(-80.093659,12.220029)"
+ id="g4314"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <g
+ id="g4316"
+ style="fill:none;stroke:#ffffff;stroke-opacity:1">
+ <path
+ d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z"
+ id="path4318"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ </g>
+ <path
+ d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543"
+ id="path4320"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543"
+ id="path4322"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544"
+ id="path4324"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <line
+ id="line4326"
+ y2="23.725029"
+ y1="58.753029"
+ x2="-66.884659"
+ x1="-66.884659"
+ style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="translate(0.12541438,0)"
+ id="g3396">
+ <g
+ transform="matrix(1,0,0,-1,90.781727,733.42579)"
+ id="g4770">
+ <g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772">
+ <polyline
+ transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round"
+ points="51.562,15.306 41.17,16.188 42.053,5.794"
+ id="polyline4774" />
+ <path
+ d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067"
+ id="path4776"
+ style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+ <g
+ transform="matrix(0,-1,-1,0,211.6059,774.10486)"
+ id="g4770-3">
+ <g
+ transform="translate(34.0803,-1006.42)"
+ id="g4772-5" />
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/images/Cartesian.svg b/images/Cartesian.svg
index 7a29e22..b34751f 100644
--- a/images/Cartesian.svg
+++ b/images/Cartesian.svg
@@ -1,211 +1,213 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
- version="1.0"
+ version="1.2"
width="1200"
height="900"
id="svg2">
- <defs
- id="defs4" />
- <g
- id="layer1">
- <path
- d="M 0,850 L 1200,850"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 0,750 L 1200,750"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 0,650 L 1200,650"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 0,550 L 1200,550"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 0,350 L 1200,350"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 0,250 L 1200,250"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 0,150 L 1200,150"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 0,50 L 1200,50"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 1100,0 L 1100,900"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 1000,0 L 1000,900"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 900,0 L 900,900"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 800,0 L 800,900"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 700,0 L 700,900"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 500,0 L 500,900"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 400,0 L 400,900"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 300,0 L 300,900"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 200,0 L 200,900"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 100,0 L 100,900"
- style="fill:none;stroke:#AAAAAA;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 590,850 L 610,850"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 590,750 L 610,750"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 590,650 L 610,650"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 590,550 L 610,550"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 0,450 L 1200,450"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 590,350 L 610,350"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 590,250 L 610,250"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 590,150 L 610,150"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 590,50 L 610,50"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 1100,440 L 1100,460"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 1000,440 L 1000,460"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 900,440 L 900,460"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 800,440 L 800,460"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 700,440 L 700,460"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 600,0 L 600,900"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 500,440 L 500,460"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 400,440 L 400,460"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 300,440 L 300,460"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 200,440 L 200,460"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
- <path
- d="M 100,440 L 100,460"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
-<!--
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
- x="95"
- y="480">–500</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
- x="195"
- y="480">–400</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
- x="295"
- y="480">–300</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
- x="395"
- y="480">–200</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
- x="495"
- y="480">–100</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
- x="650"
- y="480">x</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
- x="700"
- y="480">100</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
- x="800"
- y="480">200</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
- x="900"
- y="480">300</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
- x="1000"
- y="480">400</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#0000FF;fill-opacity:1;stroke:none;text-anchor:middle;text-align:center;font-family:Bitstream Vera Sans"><tspan
- x="1100"
- y="480">500</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
- x="590"
- y="58">400</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
- x="590"
- y="158">300</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
- x="590"
- y="258">200</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
- x="590"
- y="358">100</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
- x="590"
- y="408">y</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
- x="590"
- y="558">–100</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
- x="590"
- y="658">–200</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
- x="590"
- y="758">–300</tspan></text>
- <text
- style="font-size:20px;font-style:normal;font-weight:normal;fill:#00AA00;fill-opacity:1;stroke:none;text-anchor:end;text-align:end;font-family:Bitstream Vera Sans"><tspan
- x="590"
- y="858">–400</tspan></text>
--->
- </g>
+ <path
+ d="m 0,850 1200,0"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,750 1200,0"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,650 1200,0"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,550 1200,0"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,350 1200,0"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,250 1200,0"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,150 1200,0"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,50 1200,0"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 1100,0 0,900"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 1000,0 0,900"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 900,0 0,900"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 800,0 0,900"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 700,0 0,900"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 500,0 0,900"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 400,0 0,900"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 300,0 0,900"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 200,0 0,900"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 100,0 0,900"
+ style="fill:none;stroke:#aaaaaa;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,850 20,0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,750 20,0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,650 20,0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,550 20,0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 0,450 1200,0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,350 20,0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,250 20,0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,150 20,0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 590,50 20,0"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 1100,440 0,20"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 1000,440 0,20"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 900,440 0,20"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 800,440 0,20"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 700,440 0,20"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 600,0 0,900"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 500,440 0,20"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 400,440 0,20"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 300,440 0,20"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 200,440 0,20"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <path
+ d="m 100,440 0,20"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1" />
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="90"
+ y="447"
+ style="font-size:24px">-25</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="190"
+ y="447"
+ style="font-size:24px">-20</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="290"
+ y="447"
+ style="font-size:24px">-15</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="390"
+ y="447"
+ style="font-size:24px">-10</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="490"
+ y="447"
+ style="font-size:24px">-5</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="700"
+ y="447"
+ style="font-size:24px">5</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="800"
+ y="447"
+ style="font-size:24px">10</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="600"
+ y="447"
+ style="font-size:24px">0</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="900"
+ y="447"
+ style="font-size:24px">15</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="1000"
+ y="447"
+ style="font-size:24px">20</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="1100"
+ y="447"
+ style="font-size:24px">25</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="590"
+ y="747"
+ style="font-size:24px">-15</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="590"
+ y="647"
+ style="font-size:24px">-10</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="590"
+ y="547"
+ style="font-size:24px">-5</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="600"
+ y="347"
+ style="font-size:24px">5</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="600"
+ y="247"
+ style="font-size:24px">10</tspan></text>
+ <text
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="600"
+ y="147"
+ style="font-size:24px">15</tspan></text>
</svg>
diff --git a/images/dupstack.svg b/images/dupstack.svg
index 6a0f3fd..f3eacd9 100644
--- a/images/dupstack.svg
+++ b/images/dupstack.svg
@@ -47,21 +47,6 @@
style="font-size:12px">X</tspan>
</text>
</g>
- <path
- d="m 222.97818,18.999999 0,0 -26,15.000001 0,0 0,3 -13.50001,0 0,-3 c 0,0 -26,-15.000001 -26,-15.000001 0,0 33.00001,-17.9999992 33.00001,-17.9999992 0,0 32.5,17.9999992 32.5,17.9999992 z"
- id="path2490-6"
- style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:#c0a000;stroke-width:1.33340001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <text
- x="6.1187973"
- y="4.1250014"
- id="text2474"
- style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
- <tspan
- x="176.1188"
- y="32.125"
- id="tspan2476"
- style="font-size:48px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">x</tspan>
- </text>
<g
transform="translate(6,0)"
id="g2478">
@@ -91,8 +76,54 @@
id="tspan2488"
style="font-size:24px">!</tspan>
</text>
- <path
- d="m 148.5,19 0,0 -26,15 0,0 0,3 -13.5,0 0,-3 c 0,0 -26,-15 -26,-15 0,0 33,-18 33,-18 0,0 32.5,18 32.5,18 z"
- id="path2490"
- style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:#c0a000;stroke-width:1.33340001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(1.44,0,0,1.44,70,1)"
+ id="g3061"
+ style="fill:#ffe000;fill-opacity:1;stroke:#c0a000;stroke-opacity:1">
+ <path
+ d="m 0.5,14.5 0,-4 a 4,4 90 0 1 4,-4 l 4,0 5,-6 5,6 30,0 4,0 a 4,4 90 0 1 4,4 l 0,4 0,4 a 4,4 90 0 1 -4,4 l -4,0 -30,0 -1,0 0,2 -8,0 0,-2 -1,0 -4,0 a 4,4 90 0 1 -4,-4 l 0,-4 z"
+ id="path3063"
+ style="fill:#ffe000;fill-opacity:1;stroke:#c0a000;stroke-width:1;stroke-linecap:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(1.44,0,0,1.44,160,1)"
+ id="g3061-8"
+ style="fill:#ffe000;fill-opacity:1;stroke:#c0a000;stroke-opacity:1">
+ <path
+ d="m 0.5,14.5 0,-4 a 4,4 90 0 1 4,-4 l 4,0 5,-6 5,6 30,0 4,0 a 4,4 90 0 1 4,4 l 0,4 0,4 a 4,4 90 0 1 -4,4 l -4,0 -30,0 -1,0 0,2 -8,0 0,-2 -1,0 -4,0 a 4,4 90 0 1 -4,-4 l 0,-4 z"
+ id="path3063-7"
+ style="fill:#ffe000;fill-opacity:1;stroke:#c0a000;stroke-width:1;stroke-linecap:round;stroke-opacity:1" />
+ </g>
+ <text
+ x="-15.714066"
+ y="-38.59026"
+ transform="scale(1.0155331,-0.98470448)"
+ id="text2474"
+ style="font-size:13.9273119px;font-style:normal;font-weight:normal;line-height:125%;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="181.58951"
+ y="-6.0931983"
+ id="tspan2476"
+ style="font-size:55.70924759px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">x</tspan>
+ </text>
+ <text
+ x="103.1943"
+ y="29.748047"
+ id="text2999"
+ xml:space="preserve"
+ style="font-size:288px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="103.1943"
+ y="29.748047"
+ id="tspan3001"
+ style="font-size:24px">1</tspan></text>
+ <text
+ x="193.72749"
+ y="29.90625"
+ id="text2999-5"
+ xml:space="preserve"
+ style="font-size:288px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="193.72749"
+ y="29.90625"
+ id="tspan3001-1"
+ style="font-size:24px">2</tspan></text>
</svg>
diff --git a/images/noconnection.svg b/images/noconnection.svg
new file mode 100644
index 0000000..e1f54ed
--- /dev/null
+++ b/images/noconnection.svg
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="767"
+ height="38"
+ id="svg2467">
+ <metadata
+ id="metadata20">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs2493" />
+ <path
+ d="M 15,37.5 C 11.5,37.5 8,35 5.5,32.5 3,30 0.5,26.5 0.5,23 l 0,-8 c 0,-3.25 2.5,-8.5 5,-10.5 2.5,-2 6,-4 9.5,-4 l 736.5,0 c 2.5,0 7,1 10.5,4 3.5,2.75 4.5,7.5 4.5,10.5 l 0,8 c 0,3.5 -2,7 -4.5,9.5 -2.5,2.5 -6.5,5 -10.5,5 L 15,37.5 z"
+ id="path4"
+ style="fill:#ffd000;fill-opacity:1;fill-rule:evenodd;stroke:#e0a000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(656,65.625)"
+ id="g6">
+ <path
+ d="m 79.5,438.5 c 0,4.5 -3.75,8 -8.5,8 -4.5,0 -8.25,-3.5 -8.25,-8 0,-4.5 3.75,-8.25 8.25,-8.25 4.75,0 8.5,3.75 8.5,8.25 l 0,0 z"
+ transform="translate(24,-485)"
+ id="path8"
+ style="fill:#ff4040;fill-opacity:1;fill-rule:nonzero;stroke:#ff4040;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <text
+ id="text10"
+ style="font-size:12px;font-weight:bold;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="91"
+ y="-42"
+ id="tspan12"
+ style="font-size:12px">X</tspan>
+ </text>
+ </g>
+ <g
+ transform="translate(6,0)"
+ id="g2478">
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-4,20)"
+ id="path2480"
+ style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(1,17)"
+ id="path2482"
+ style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ <path
+ d="M 44,15 10.5,15 27.5,-14 44,15 z"
+ transform="translate(-1,18)"
+ id="path2484"
+ style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+ </g>
+ <text
+ x="6"
+ id="text2486"
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="27"
+ y="29"
+ id="tspan2488"
+ style="font-size:24px">!</tspan>
+ </text>
+ <g
+ transform="translate(-120,0)"
+ id="g3945">
+ <g
+ transform="translate(-40.622658,1.1962357)"
+ id="g3938">
+ <path
+ d="m 261.81132,16.490566 c 0,0 5.61534,-5.733989 13.20755,-5.660377 7.59221,0.07361 13.20755,5.81132 13.20755,5.81132"
+ id="path3884"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 265.46812,20.608986 c 0,0 4.06061,-4.251886 9.55075,-4.197302 5.49014,0.05458 9.55075,4.30923 9.55075,4.30923"
+ id="path3884-4"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 269.11765,24.054842 c 0,0 2.50898,-2.705885 5.90122,-2.671148 3.39225,0.03474 5.90122,2.742378 5.90122,2.742378"
+ id="path3884-4-5"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 276.23247,31.491378 a 1.9079307,2.4622495 0 0 1 -3.50568,0.16164"
+ transform="matrix(1.2819187,0,0,-0.77106983,-76.841701,51.868416)"
+ id="path3922"
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+ <path
+ d="m 272.41851,26.636292 2.37829,2.607019 2.70756,-2.377285"
+ id="path3924"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.06946218px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <g
+ transform="translate(-12.150943,0)"
+ id="g3878"
+ style="stroke:#ff0000;stroke-opacity:1">
+ <path
+ d="m 297.81132,21.283018 a 17.735849,17.018867 0 1 1 -35.4717,0 17.735849,17.018867 0 1 1 35.4717,0 z"
+ transform="matrix(-0.87649902,0,0,0.9134247,492.03303,-0.44043455)"
+ id="path3104"
+ style="fill:none;stroke:#ff0000;stroke-width:5.48631763;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="M 235.90565,7.7547168 257.18867,30.245283"
+ id="path3876"
+ style="fill:none;stroke:#ff0000;stroke-width:4.90899992;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ </g>
+</svg>
diff --git a/plugins/accelerometer/accelerometer.py b/plugins/accelerometer/accelerometer.py
new file mode 100644
index 0000000..b792f5e
--- /dev/null
+++ b/plugins/accelerometer/accelerometer.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+#Copyright (c) 2011 Walter Bender
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+
+from gettext import gettext as _
+
+from plugins.plugin import Plugin
+
+from TurtleArt.tapalette import make_palette
+from TurtleArt.tautils import debug_output
+from TurtleArt.taprimitive import Primitive
+
+import logging
+_logger = logging.getLogger('turtleart-activity accelerometer plugin')
+
+
+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
+ else:
+ self._status = False
+ self.running_sugar = self._parent.running_sugar
+
+ def setup(self):
+ # set up accelerometer specific blocks
+ palette = make_palette('extras',
+ colors=["#FF0000", "#A00000"],
+ help_string=_('Palette of extra options'),
+ position=8,
+ translation=_('extras'))
+ '''
+ palette = make_palette('sensor',
+ colors=["#FF6060", "#A06060"],
+ help_string=_('Palette of sensor blocks'),
+ position=6)
+ '''
+
+ if self._status:
+ palette.add_block('xyz',
+ hidden=True,
+ style='basic-style-extended-vertical',
+ label=_('acceleration'),
+ help_string=\
+ _('push acceleration in x, y, z to heap'),
+ prim_name='xyz')
+ else:
+ palette.add_block('xyz',
+ hidden=True,
+ style='basic-style-extended-vertical',
+ label=_('acceleration'),
+ help_string=\
+ _('push acceleration in x, y, z to heap'),
+ prim_name='xyz')
+
+ self._parent.lc.def_prim(
+ 'xyz', 0,
+ Primitive(self.prim_xyz))
+
+ def _status_report(self):
+ debug_output('Reporting accelerator status: %s' % (str(self._status)))
+ return self._status
+
+ # Block primitives used in talogo
+
+ def prim_xyz(self):
+ ''' push accelerometer xyz to stack '''
+ if not self._status:
+ self._parent.lc.heap.append(0)
+ self._parent.lc.heap.append(0)
+ self._parent.lc.heap.append(0)
+ else:
+ fh = open(ACCELEROMETER_DEVICE)
+ string = fh.read()
+ xyz = string[1:-2].split(',')
+ self._parent.lc.heap.append(float(xyz[2]) / 18)
+ self._parent.lc.heap.append(float(xyz[1]) / 18)
+ self._parent.lc.heap.append(float(xyz[0]) / 18)
+ fh.close()
diff --git a/plugins/accelerometer/icons/extrasoff.svg b/plugins/accelerometer/icons/extrasoff.svg
new file mode 100644
index 0000000..a956f03
--- /dev/null
+++ b/plugins/accelerometer/icons/extrasoff.svg
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <rect
+ width="42.345783"
+ height="42.345783"
+ x="6.3271084"
+ y="6.3271084"
+ id="rect3016"
+ style="fill:#282828;fill-opacity:1;stroke:#282828;stroke-width:2.6542182;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="g3">
+ <polygon
+ points="26.891,12.363 17.238,16.369 14.659,4.975 20.555,2.531 "
+ id="polygon5"
+ style="fill:#ffffff" />
+ <polygon
+ points="42.646,26.88 38.649,17.228 50.04,14.654 52.477,20.55 "
+ id="polygon7"
+ style="fill:#ffffff" />
+ <polygon
+ points="28.117,42.645 37.775,38.645 40.349,50.029 34.453,52.471 "
+ id="polygon9"
+ style="fill:#ffffff" />
+ <polygon
+ points="37.824,16.315 28.171,12.309 34.394,2.439 40.295,4.882 "
+ id="polygon11"
+ style="fill:#ffffff" />
+ <polygon
+ points="38.628,37.791 42.623,28.139 52.493,34.365 50.055,40.258 "
+ id="polygon13"
+ style="fill:#ffffff" />
+ <polygon
+ points="16.385,37.76 12.391,28.105 2.515,34.34 4.953,40.234 "
+ id="polygon15"
+ style="fill:#ffffff" />
+ <polygon
+ points="12.319,26.875 16.32,17.216 4.936,14.643 2.493,20.539 "
+ id="polygon17"
+ style="fill:#ffffff" />
+ <polygon
+ points="26.939,42.623 17.287,38.629 14.719,50.018 20.609,52.461 "
+ id="polygon19"
+ style="fill:#ffffff" />
+ <path
+ d="m 39.925,22.352 c 2.845,6.863 -0.412,14.728 -7.274,17.574 -6.867,2.85 -14.734,-0.418 -17.578,-7.281 -2.84,-6.862 0.418,-14.733 7.279,-17.572 6.862,-2.845 14.734,0.417 17.573,7.279 z"
+ id="path21"
+ style="fill:none;stroke:#ffffff;stroke-width:11.69439983" />
+ </g>
+</svg>
diff --git a/plugins/accelerometer/icons/extrason.svg b/plugins/accelerometer/icons/extrason.svg
new file mode 100644
index 0000000..7ee08bf
--- /dev/null
+++ b/plugins/accelerometer/icons/extrason.svg
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata20">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5">
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2431"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2428"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2557"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,9.2560985,9.9123239)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2561"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,8.962951,22.783517)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2461"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2463"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
+ </defs>
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <g
+ id="g3799">
+ <polygon
+ points="20.555,2.531 26.891,12.363 17.238,16.369 14.659,4.975 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon5"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="52.477,20.55 42.646,26.88 38.649,17.228 50.04,14.654 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon7"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="34.453,52.471 28.117,42.645 37.775,38.645 40.349,50.029 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon9"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="40.295,4.882 37.824,16.315 28.171,12.309 34.394,2.439 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon11"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="50.055,40.258 38.628,37.791 42.623,28.139 52.493,34.365 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon13"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="4.953,40.234 16.385,37.76 12.391,28.105 2.515,34.34 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon15"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="2.493,20.539 12.319,26.875 16.32,17.216 4.936,14.643 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon17"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="20.609,52.461 26.939,42.623 17.287,38.629 14.719,50.018 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon19"
+ style="fill:#ff0000;fill-opacity:1" />
+ <path
+ d="m 35.45648,24.236169 c 1.8208,4.389511 -0.26368,9.419891 -4.65536,11.240166 -4.39488,1.822833 -9.42976,-0.267349 -11.24992,-4.65686 -1.8176,-4.388871 0.26752,-9.423089 4.65856,-11.238887 4.39168,-1.819635 9.42976,0.26671 11.24672,4.655581 z"
+ id="path21"
+ style="fill:none;stroke:#ff0000;stroke-width:7.48202181;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/plugins/accelerometer/icons/sensoroff.svg b/plugins/accelerometer/icons/sensoroff.svg
new file mode 100644
index 0000000..0a16670
--- /dev/null
+++ b/plugins/accelerometer/icons/sensoroff.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata15"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs13" />
+<rect
+ width="42.763924"
+ height="42.763924"
+ x="6.1180382"
+ y="6.1180382"
+ id="rect2986"
+ style="fill:#282828;fill-opacity:1;stroke:#282828;stroke-width:2.23607516;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><g
+ transform="matrix(0.87078705,0,0,0.87078705,3.2821055,2.9298726)"
+ id="network-wired_1_"
+ style="display:block">
+ <g
+ id="network-wired">
+ <line
+ id="line3076"
+ y2="23.993"
+ y1="32.438999"
+ x2="16.966999"
+ x1="16.966999"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round" />
+ <line
+ id="line3078"
+ y2="28.215"
+ y1="28.215"
+ x2="34.938"
+ x1="29.636999"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round" />
+
+ <rect
+ width="9.7939997"
+ height="7.599"
+ x="42.157001"
+ y="24.312"
+ id="rect3080"
+ style="fill:#ffffff;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round" />
+ <path
+ d="m 16.967,23.993 c 0,-2.334 -1.892,-4.224 -4.224,-4.224 -2.332,0 -4.223,1.889 -4.223,4.224"
+ id="path3082"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 25.413,32.439 c 0,2.334 -1.891,4.224 -4.224,4.224 -2.332,0 -4.223,-1.89 -4.223,-4.224"
+ id="path3084"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 25.413,32.439 c 0,-2.332 1.893,-4.226 4.224,-4.226"
+ id="path3086"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 8.52,23.993 c 0,2.332 -1.892,4.222 -4.223,4.222"
+ id="path3088"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+
+ <rect
+ width="14.477"
+ height="11.35"
+ x="31.945"
+ y="22.438"
+ id="rect3090"
+ style="fill:#ffffff;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/plugins/accelerometer/icons/sensoron.svg b/plugins/accelerometer/icons/sensoron.svg
new file mode 100644
index 0000000..d756860
--- /dev/null
+++ b/plugins/accelerometer/icons/sensoron.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata15"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs13" /><rect
+ width="55"
+ height="55"
+ x="0"
+ y="0"
+ id="rect3269"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:nonzero;stroke:none" /><g
+ transform="translate(0.27777716,18.796296)"
+ id="g4054"><line
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-opacity:1"
+ x1="17.778971"
+ x2="17.778971"
+ y1="12.381037"
+ y2="5.0263696"
+ id="line3076" /><line
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-opacity:1"
+ x1="28.811842"
+ x2="33.427887"
+ y1="8.7028332"
+ y2="8.7028332"
+ id="line3078" /><rect
+ width="8.5284882"
+ height="6.6171107"
+ x="39.7141"
+ y="5.3041511"
+ id="rect3080"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:1.95927083;stroke-linecap:round;stroke-opacity:1" /><path
+ d="m 17.778971,5.0263697 c 0,-2.032417 -1.647529,-3.6782045 -3.678204,-3.6782045 -2.030675,0 -3.677334,1.6449167 -3.677334,3.6782045"
+ id="path3082"
+ style="fill:none;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 25.133639,12.381037 c 0,2.032417 -1.646658,3.678205 -3.678205,3.678205 -2.030675,0 -3.677333,-1.645788 -3.677333,-3.678205"
+ id="path3084"
+ style="fill:none;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 25.133639,12.381037 c 0,-2.030675 1.6484,-3.679946 3.678204,-3.679946"
+ id="path3086"
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 10.423433,5.0263697 c 0,2.0306754 -1.6475288,3.6764629 -3.6773334,3.6764629"
+ id="path3088"
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><rect
+ width="12.606384"
+ height="9.8834333"
+ x="30.821619"
+ y="3.6722956"
+ id="rect3090"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/plugins/audio_sensors/audio_sensors.py b/plugins/audio_sensors/audio_sensors.py
new file mode 100644
index 0000000..027fc67
--- /dev/null
+++ b/plugins/audio_sensors/audio_sensors.py
@@ -0,0 +1,483 @@
+#!/usr/bin/env python
+#Copyright (c) 2011, 2012 Walter Bender
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+try:
+ from numpy.fft import rfft
+ PITCH_AVAILABLE = True
+except:
+ PITCH_AVAILABLE = False
+
+from plugins.plugin import Plugin
+
+from plugins.audio_sensors.audiograb import (AudioGrab,
+ SENSOR_DC_NO_BIAS, SENSOR_DC_BIAS, SENSOR_AC_BIAS)
+
+from plugins.audio_sensors.ringbuffer import RingBuffer1d
+
+from TurtleArt.tapalette import make_palette
+from TurtleArt.taconstants import XO1, XO15, XO175, XO30, XO4
+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')
+
+
+def _avg(array, abs_value=False):
+ ''' Calc. the average value of an array '''
+ if len(array) == 0:
+ return 0
+ array_sum = 0
+ if abs_value:
+ for a in array:
+ array_sum += abs(a)
+ else:
+ for a in array:
+ array_sum += a
+ return float(array_sum) / len(array)
+
+
+class Audio_sensors(Plugin):
+
+ def __init__(self, parent):
+ 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
+ self.running_sugar = self._parent.running_sugar
+
+ 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('extras',
+ colors=["#FF0000", "#A00000"],
+ help_string=_('Palette of extra options'),
+ position=8,
+ translation=_('extras'))
+ '''
+ palette = make_palette('sensor',
+ colors=["#FF6060", "#A06060"],
+ help_string=_('Palette of sensor blocks'),
+ position=6)
+ '''
+ hidden = True
+ '''
+ if self._status:
+ 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,
+ 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,
+ Primitive(self.prim_volume,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'channel': ConstantArg(0)},
+ call_afterwards=self.after_volume))
+
+ hidden = True
+ '''
+ if PITCH_AVAILABLE and self._status:
+ 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 == 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
+ 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:
+ 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,
+ 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,
+ 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,
+ 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,
+ Primitive(self.prim_voltage,
+ return_type=TYPE_NUMBER,
+ kwarg_descs={'channel': ConstantArg(1)},
+ call_afterwards=self.after_voltage))
+
+ if self.hw in [XO175, XO30, XO4]:
+ self.PARAMETERS = {
+ SENSOR_AC_BIAS: (False, True, 80, True),
+ SENSOR_DC_NO_BIAS: (True, False, 80, False),
+ SENSOR_DC_BIAS: (True, True, 90, False)
+ }
+ elif self.hw == XO15:
+ self.PARAMETERS = {
+ SENSOR_AC_BIAS: (False, True, 80, True),
+ SENSOR_DC_NO_BIAS: (True, False, 80, False),
+ SENSOR_DC_BIAS: (True, True, 90, False)
+ }
+ elif self.hw == XO1:
+ self.PARAMETERS = {
+ SENSOR_AC_BIAS: (False, True, 40, True),
+ SENSOR_DC_NO_BIAS: (True, False, 0, False),
+ SENSOR_DC_BIAS: (True, True, 0, False)
+ }
+ else:
+ self.PARAMETERS = {
+ SENSOR_AC_BIAS: (None, True, 40, True),
+ SENSOR_DC_NO_BIAS: (True, False, 80, False),
+ SENSOR_DC_BIAS: (True, True, 90, False)
+ }
+
+ def start(self):
+ ''' 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(
+ 'block', ['volume', 'sound', 'pitch'])) > 0:
+ mode, bias, gain, boost = self.PARAMETERS[SENSOR_AC_BIAS]
+ elif len(self._parent.block_list.get_similar_blocks(
+ 'block', ['resistance', 'resistance2'])) > 0:
+ mode, bias, gain, boost = self.PARAMETERS[SENSOR_DC_BIAS]
+ elif len(self._parent.block_list.get_similar_blocks(
+ 'block', ['voltage', 'voltage2'])) > 0:
+ mode, bias, gain, boost = self.PARAMETERS[SENSOR_DC_NO_BIAS]
+ else:
+ return # No audio blocks in use.
+ self.audiograb = AudioGrab(self.new_buffer, self,
+ mode, bias, gain, boost)
+ self._channels = self.audiograb.channels
+ for i in range(self._channels):
+ self.ringbuffer.append(RingBuffer1d(self.max_samples,
+ dtype='int16'))
+ self.audiograb.start_grabbing()
+ self.audio_started = True
+
+ def new_buffer(self, buf, channel=0):
+ ''' Append a new buffer to the ringbuffer '''
+ self.ringbuffer[channel].append(buf)
+ return True
+
+ def stop(self):
+ ''' This gets called by the stop button '''
+ if self._status and self.audio_started:
+ self.audiograb.on_activity_quit() # reset all setting
+ self.audio_started = False
+
+ def goto_background(self):
+ ''' This gets called when your process is sent to the background '''
+ pass
+
+ def return_to_foreground(self):
+ ''' This gets called when your process returns from the background '''
+ pass
+
+ def quit(self):
+ ''' This gets called by the quit button '''
+ if self._status and self.audio_started:
+ self.audiograb.on_activity_quit()
+
+ def _status_report(self):
+ debug_output(
+ 'Reporting audio sensor status: %s' % (str(self._status)),
+ self._parent.running_sugar)
+ return self._status
+
+ # Block primitives
+
+ 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:
+ self._prim_sound(1)
+ return (self._sound[0] + self._sound[1]) / 2.0
+ else:
+ return self._sound[0]
+
+ def _prim_sound(self, channel):
+ ''' return raw mic in value '''
+ buf = self.ringbuffer[channel].read(None, self.input_step)
+ if len(buf) > 0:
+ self._sound[channel] = float(buf[0])
+ else:
+ 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_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:
+ self._prim_volume(1)
+ return (self._volume[0] + self._volume[1]) / 2.0
+ else:
+ return self._volume[0]
+
+ def _prim_volume(self, channel):
+ ''' return raw mic in value '''
+ buf = self.ringbuffer[channel].read(None, self.input_step)
+ if len(buf) > 0:
+ self._volume[channel] = float(_avg(buf, abs_value=True))
+ else:
+ 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=0):
+ if not self._status:
+ return 0
+ self._prim_pitch(0)
+ # Return average of both channels if sampling in stereo
+ if self._channels == 2:
+ self._prim_pitch(1)
+ return (self._pitch[0] + self._pitch[1]) / 2.0
+ else:
+ return self._pitch[0]
+
+ def _prim_pitch(self, channel):
+ ''' 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:
+ 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)
+ self._pitch[channel] = maxi * 48000 / (len(buf) * 2)
+ else:
+ 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=0):
+ if not self.hw in [XO1, XO15, XO175, XO30, XO4] or not self._status:
+ return 0
+ if self.hw in [XO1, XO4]:
+ self._prim_resistance(0)
+ return self._resistance[0]
+ elif self.hw == XO15:
+ self._prim_resistance(channel)
+ return self._resistance[channel]
+ # For XO175: channel assignment is seemingly random
+ # (#3675), one of them will be 0
+ else:
+ 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
+ # and http://bugs.sugarlabs.org/ticket/4649
+ avg_buf = float(_avg(buf))
+ if self.hw == XO1:
+ self._resistance[channel] = \
+ 2.718 ** ((avg_buf * 0.000045788) + 8.0531)
+ elif self.hw == XO15:
+ if avg_buf > 0:
+ self._resistance[channel] = (420000000 / avg_buf) - 13500
+ else:
+ 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:
+ 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:
+ self._resistance[channel] = \
+ (46000000. / (30514 - avg_buf)) - 1150
+ else:
+ self._resistance[channel] = 999999999
+ if self._resistance[channel] < 0:
+ self._resistance[channel] = 0
+ else:
+ self._resistance[channel] = 0
+
+ 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=0):
+ if not self.hw in [XO1, XO15, XO175, XO30, XO4] or not self._status:
+ return 0
+ if self.hw in [XO1, XO4]:
+ self._prim_voltage(0)
+ return self._voltage[0]
+ elif self.hw == XO15:
+ self._prim_voltage(channel)
+ return self._voltage[channel]
+ # FIXME: For XO175: channel assignment is seemingly random
+ # (#3675), one of them will be 0
+ else:
+ 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>
+ self._voltage[channel] = \
+ float(_avg(buf)) * self.voltage_gain + self.voltage_bias
+ else:
+ self._voltage[channel] = 0
+
+ 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/audio_sensors/audiograb.py b/plugins/audio_sensors/audiograb.py
new file mode 100644
index 0000000..228e4c2
--- /dev/null
+++ b/plugins/audio_sensors/audiograb.py
@@ -0,0 +1,673 @@
+#! /usr/bin/python
+#
+# Author: Arjun Sarwal arjun@laptop.org
+# Copyright (C) 2007, Arjun Sarwal
+# Copyright (C) 2009-12 Walter Bender
+# Copyright (C) 2009, Benjamin Berg, Sebastian Berg
+# Copyright (C) 2009, Sayamindu Dasgupta
+# Copyright (C) 2010, Sascha Silbe
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, write to the Free Software
+# Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
+
+import pygst
+import gst
+import gst.interfaces
+from numpy import fromstring
+import subprocess
+import traceback
+from string import find
+from threading import Timer
+
+from TurtleArt.taconstants import XO1, XO4
+from TurtleArt.tautils import debug_output
+
+# Initial device settings
+RATE = 48000
+MIC_BOOST = True
+DC_MODE_ENABLE = False
+CAPTURE_GAIN = 50
+BIAS = True
+
+# Setting on quit
+QUIT_MIC_BOOST = False
+QUIT_DC_MODE_ENABLE = False
+QUIT_CAPTURE_GAIN = 100
+QUIT_BIAS = True
+
+# Capture modes
+SENSOR_AC_NO_BIAS = 'external'
+SENSOR_AC_BIAS = 'sound'
+SENSOR_DC_NO_BIAS = 'voltage'
+SENSOR_DC_BIAS = 'resistance'
+
+
+class AudioGrab():
+ """ The interface between measure and the audio device """
+
+ def __init__(self, callable1, parent,
+ mode=None, bias=None, gain=None, boost=None):
+ """ Initialize the class: callable1 is a data buffer;
+ parent is the parent class"""
+
+ self.callable1 = callable1
+ self.parent = parent
+ self.sensor = None
+
+ self.temp_buffer = [0]
+
+ self.rate = RATE
+ # Force XO1 and XO4 to use just 1 channel
+ if self.parent.hw in [XO1, XO4]:
+ self.channels = 1
+ else:
+ self.channels = None
+
+ self._dc_control = None
+ self._mic_bias_control = None
+ self._capture_control = None
+ self._mic_boost_control = None
+ self._labels_available = True # Query controls for device names
+
+ self._query_mixer()
+ # If Channels was not found in the Capture controller, guess.
+ if self.channels is None:
+ debug_output('Guessing there are 2 channels',
+ self.parent.running_sugar)
+ self.channels = 2
+
+ # Set mixer to known state
+ self.set_dc_mode(DC_MODE_ENABLE)
+ self.set_bias(BIAS)
+ self.set_capture_gain(CAPTURE_GAIN)
+ self.set_mic_boost(MIC_BOOST)
+
+ self.master = self.get_master()
+ self.dc_mode = self.get_dc_mode()
+ self.bias = self.get_bias()
+ self.capture_gain = self.get_capture_gain()
+ self.mic_boost = self.get_mic_boost()
+
+ # Set mixer to desired state
+ self._set_sensor_type(mode, bias, gain, boost)
+ self.dc_mode = self.get_dc_mode()
+ self.bias = self.get_bias()
+ self.capture_gain = self.get_capture_gain()
+ self.mic_boost = self.get_mic_boost()
+
+ # Set up gstreamer pipeline
+ self._pad_count = 0
+ self.pads = []
+ self.queue = []
+ self.fakesink = []
+ self.pipeline = gst.Pipeline('pipeline')
+ self.alsasrc = gst.element_factory_make('alsasrc', 'alsa-source')
+ self.pipeline.add(self.alsasrc)
+ self.caps1 = gst.element_factory_make('capsfilter', 'caps1')
+ self.pipeline.add(self.caps1)
+ caps_str = 'audio/x-raw-int,rate=%d,channels=%d,depth=16' % (
+ RATE, self.channels)
+ self.caps1.set_property('caps', gst.caps_from_string(caps_str))
+ if self.channels == 1:
+ self.fakesink.append(gst.element_factory_make('fakesink', 'fsink'))
+ self.pipeline.add(self.fakesink[0])
+ self.fakesink[0].connect('handoff', self.on_buffer, 0)
+ self.fakesink[0].set_property('signal-handoffs', True)
+ gst.element_link_many(self.alsasrc, self.caps1, self.fakesink[0])
+ else:
+ if not hasattr(self, 'splitter'):
+ self.splitter = gst.element_factory_make('deinterleave')
+ self.pipeline.add(self.splitter)
+ self.splitter.set_properties('keep-positions=true', 'name=d')
+ self.splitter.connect('pad-added', self._splitter_pad_added)
+ gst.element_link_many(self.alsasrc, self.caps1, self.splitter)
+ for i in range(self.channels):
+ self.queue.append(gst.element_factory_make('queue'))
+ self.pipeline.add(self.queue[i])
+ self.fakesink.append(gst.element_factory_make('fakesink'))
+ self.pipeline.add(self.fakesink[i])
+ self.fakesink[i].connect('handoff', self.on_buffer, i)
+ self.fakesink[i].set_property('signal-handoffs', True)
+
+ self.dont_queue_the_buffer = False
+
+ # Timer for interval sampling and switch to indicate when to capture
+ self.capture_timer = None
+ self.capture_interval_sample = False
+
+ def _query_mixer(self):
+ self._mixer = gst.element_factory_make('alsamixer')
+ rc = self._mixer.set_state(gst.STATE_PAUSED)
+ assert rc == gst.STATE_CHANGE_SUCCESS
+
+ # Query the available controls
+ tracks_list = self._mixer.list_tracks()
+ if hasattr(tracks_list[0].props, 'untranslated_label'):
+ self._capture_control = self._find_control(['capture', 'axi'])
+ self._dc_control = self._find_control(['dc mode'])
+ self._mic_bias_control = self._find_control(['mic bias',
+ 'dc input bias',
+ 'v_refout'])
+ self._mic_boost_control = self._find_control(['mic boost',
+ 'mic1 boost',
+ 'mic boost (+20db)',
+ 'internal mic boost',
+ 'analog mic boost'])
+ self._mic_gain_control = self._find_control(['mic'])
+ self._master_control = self._find_control(['master'])
+ else: # Use hardwired values
+ self._labels_available = False
+
+ def _unlink_sink_queues(self):
+ ''' Build the sink pipelines '''
+
+ # If there were existing pipelines, unlink them
+ for i in range(self._pad_count):
+ try:
+ self.splitter.unlink(self.queue[i])
+ self.queue[i].unlink(self.fakesink[i])
+ except:
+ traceback.print_exc()
+
+ # Build the new pipelines
+ self._pad_count = 0
+ self.pads = []
+
+ def _splitter_pad_added(self, element, pad):
+ ''' Seems to be the case that ring is right channel 0,
+ tip is left channel 1'''
+ '''
+ debug_output('splitter pad %d added' % (self._pad_count),
+ self.parent.running_sugar)
+ '''
+ self.pads.append(pad)
+ if self._pad_count < self.channels:
+ pad.link(self.queue[self._pad_count].get_pad('sink'))
+ self.queue[self._pad_count].get_pad('src').link(
+ self.fakesink[self._pad_count].get_pad('sink'))
+ self._pad_count += 1
+ else:
+ debug_output('ignoring channels > %d' % (self.channels),
+ self.parent.running_sugar)
+
+ def set_handoff_signal(self, handoff_state):
+ '''Sets whether the handoff signal would generate an interrupt
+ or not'''
+ for i in range(len(self.fakesink)):
+ self.fakesink[i].set_property('signal-handoffs', handoff_state)
+
+ def _new_buffer(self, buf, channel):
+ ''' Use a new buffer '''
+ if not self.dont_queue_the_buffer:
+ self.temp_buffer = buf
+ self.callable1(buf, channel=channel)
+ else:
+ pass
+
+ def on_buffer(self, element, data_buffer, pad, channel):
+ '''The function that is called whenever new data is available
+ This is the signal handler for the handoff signal'''
+ temp_buffer = fromstring(data_buffer, 'int16')
+ if not self.dont_queue_the_buffer:
+ self._new_buffer(temp_buffer, channel=channel)
+ return False
+
+ def start_sound_device(self):
+ '''Start or Restart grabbing data from the audio capture'''
+ gst.event_new_flush_start()
+ self.pipeline.set_state(gst.STATE_PLAYING)
+
+ def stop_sound_device(self):
+ '''Stop grabbing data from capture device'''
+ gst.event_new_flush_stop()
+ self.pipeline.set_state(gst.STATE_NULL)
+
+ def sample_now(self):
+ ''' Log the current sample now. This method is called from the
+ capture_timer object when the interval expires. '''
+ self.capture_interval_sample = True
+ self.make_timer()
+
+ def set_buffer_interval_logging(self, interval=0):
+ '''Sets the number of buffers after which a buffer needs to be
+ emitted'''
+ self.buffer_interval_logging = interval
+
+ def set_sampling_rate(self, sr):
+ '''Sets the sampling rate of the capture device
+ Sampling rate must be given as an integer for example 16000 for
+ setting 16Khz sampling rate
+ The sampling rate would be set in the device to the nearest available'''
+ self.pause_grabbing()
+ caps_str = 'audio/x-raw-int,rate=%d,channels=%d,depth=16' % (
+ sr, self.channels)
+ self.caps1.set_property('caps', gst.caps_from_string(caps_str))
+ self.resume_grabbing()
+
+ def get_sampling_rate(self):
+ '''Gets the sampling rate of the capture device'''
+ return int(self.caps1.get_property('caps')[0]['rate'])
+
+ def set_callable1(self, callable1):
+ '''Sets the callable to the drawing function for giving the
+ data at the end of idle-add'''
+ self.callable1 = callable1
+
+ def start_grabbing(self):
+ '''Called right at the start of the Activity'''
+ self.start_sound_device()
+ self.set_handoff_signal(True)
+
+ def pause_grabbing(self):
+ '''When Activity goes into background'''
+ self.save_state()
+ self.stop_sound_device()
+
+ def resume_grabbing(self):
+ '''When Activity becomes active after going to background'''
+ self.start_sound_device()
+ self.resume_state()
+ self.set_handoff_signal(True)
+
+ def stop_grabbing(self):
+ '''Not used ???'''
+ self.stop_sound_device()
+ self.set_handoff_signal(False)
+
+ def _find_control(self, prefixes):
+ '''Try to find a mixer control matching one of the prefixes.
+
+ The control with the best match (smallest difference in length
+ between label and prefix) will be returned. If no match is found,
+ None is returned.
+ '''
+ def best_prefix(label, prefixes):
+ matches =\
+ [len(label) - len(p) for p in prefixes if label.startswith(p)]
+ if not matches:
+ return None
+
+ matches.sort()
+ return matches[0]
+
+ controls = []
+ for track in self._mixer.list_tracks():
+ label = track.props.untranslated_label.lower()
+ diff = best_prefix(label, prefixes)
+ if diff is not None:
+ controls.append((track, diff))
+
+ controls.sort(key=lambda e: e[1])
+ if controls:
+ '''
+ debug_output('Found control: %s' %\
+ (str(controls[0][0].props.untranslated_label)),
+ self.parent.running_sugar)
+ '''
+ if self.channels is None:
+ if hasattr(controls[0][0], 'num_channels'):
+ channels = controls[0][0].num_channels
+ if channels > 0:
+ self.channels = channels
+ '''
+ debug_output('setting channels to %d' % (self.channels),
+ self.parent.running_sugar)
+ '''
+
+ return controls[0][0]
+
+ return None
+
+ def save_state(self):
+ '''Saves the state of all audio controls'''
+ self.master = self.get_master()
+ self.bias = self.get_bias()
+ self.dc_mode = self.get_dc_mode()
+ self.capture_gain = self.get_capture_gain()
+ self.mic_boost = self.get_mic_boost()
+
+ def resume_state(self):
+ '''Put back all audio control settings from the saved state'''
+ self.set_master(self.master)
+ self.set_bias(self.bias)
+ self.set_dc_mode(self.dc_mode)
+ self.set_capture_gain(self.capture_gain)
+ self.set_mic_boost(self.mic_boost)
+
+ def _get_mute(self, control, name, default):
+ '''Get mute status of a control'''
+ if not control:
+ return default
+ return bool(control.flags & gst.interfaces.MIXER_TRACK_MUTE)
+
+ def _set_mute(self, control, name, value):
+ '''Mute a control'''
+ if not control:
+ return
+ self._mixer.set_mute(control, value)
+
+ def _get_volume(self, control, name):
+ '''Get volume of a control and convert to a scale of 0-100'''
+ if not control:
+ return 100
+ volume = self._mixer.get_volume(control)
+ if type(volume) == tuple:
+ hw_volume = volume[0]
+ else:
+ hw_volume = volume
+ min_vol = control.min_volume
+ max_vol = control.max_volume
+ if max_vol == min_vol:
+ percent = 100
+ else:
+ percent = (hw_volume - min_vol) * 100 // (max_vol - min_vol)
+ return percent
+
+ def _set_volume(self, control, name, value):
+ '''Sets the level of a control on a scale of 0-100'''
+ if not control:
+ return
+ # convert value to scale of control
+ min_vol = control.min_volume
+ max_vol = control.max_volume
+ if min_vol != max_vol:
+ hw_volume = value * (max_vol - min_vol) // 100 + min_vol
+ self._mixer.set_volume(control,
+ (hw_volume,) * control.num_channels)
+
+ def amixer_set(self, control, state):
+ ''' Direct call to amixer for old systems. '''
+ if state:
+ output = check_output(
+ ['amixer', 'set', "%s" % (control), 'unmute'],
+ 'Problem with amixer set "%s" unmute' % (control),
+ self.parent.running_sugar)
+ else:
+ output = check_output(
+ ['amixer', 'set', "%s" % (control), 'mute'],
+ 'Problem with amixer set "%s" mute' % (control),
+ self.parent.running_sugar)
+
+ def mute_master(self):
+ '''Mutes the Master Control'''
+ if self._labels_available and self.parent.hw != XO1:
+ self._set_mute(self._master_control, 'Master', True)
+ else:
+ self.amixer_set('Master', False)
+
+ def unmute_master(self):
+ '''Unmutes the Master Control'''
+ if self._labels_available and self.parent.hw != XO1:
+ self._set_mute(self._master_control, 'Master', True)
+ else:
+ self.amixer_set('Master', True)
+
+ def set_master(self, master_val):
+ '''Sets the Master gain slider settings
+ master_val must be given as an integer between 0 and 100 indicating the
+ percentage of the slider to be set'''
+ if self._labels_available:
+ self._set_volume(self._master_control, 'Master', master_val)
+ else:
+ output = check_output(
+ ['amixer', 'set', 'Master', "%d%s" % (master_val, '%')],
+ 'Problem with amixer set Master',
+ self.parent.running_sugar)
+
+ def get_master(self):
+ '''Gets the MIC gain slider settings. The value returned is an
+ integer between 0-100 and is an indicative of the percentage 0 - 100%'''
+ if self._labels_available:
+ return self._get_volume(self._master_control, 'master')
+ else:
+ output = check_output(['amixer', 'get', 'Master'],
+ 'amixer: Could not get Master volume',
+ self.parent.running_sugar)
+ if output is None:
+ return 100
+ else:
+ output = output[find(output, 'Front Left:'):]
+ output = output[find(output, '[') + 1:]
+ output = output[:find(output, '%]')]
+ return int(output)
+
+ def set_bias(self, bias_state=False):
+ '''Enables / disables bias voltage.'''
+ if self._labels_available and self.parent.hw != XO1:
+ if self._mic_bias_control is None:
+ return
+ # If there is a flag property, use set_mute
+ if self._mic_bias_control not in self._mixer.list_tracks() or \
+ hasattr(self._mic_bias_control.props, 'flags'):
+ self._set_mute(
+ self._mic_bias_control, 'Mic Bias', not bias_state)
+ # We assume that values are sorted from lowest (=off) to highest.
+ # Since they are mixed strings ('Off', '50%', etc.), we cannot
+ # easily ensure this by sorting with the default sort order.
+ elif bias_state: # Otherwise, set with volume
+ self._mixer.set_volume(self._mic_bias_control,
+ self._mic_bias_control.max_volume)
+ else:
+ self._mixer.set_volume(self._mic_bias_control,
+ self._mic_bias_control.min_volume)
+ elif not self._labels_available:
+ self.amixer_set('V_REFOUT Enable', bias_state)
+ else:
+ self.amixer_set('MIC Bias Enable', bias_state)
+
+ def get_bias(self):
+ '''Check whether bias voltage is enabled.'''
+ if self._labels_available:
+ if self._mic_bias_control is None:
+ return False
+ if self._mic_bias_control not in self._mixer.list_tracks() or \
+ hasattr(self._mic_bias_control.props, 'flags'):
+ return not self._get_mute(
+ self._mic_bias_control, 'Mic Bias', False)
+ value = self._mixer.get_volume(self._mic_bias_control)
+ if value == self._mic_bias_control.min_volume:
+ return False
+ return True
+ else:
+ output = check_output(['amixer', 'get', "V_REFOUT Enable"],
+ 'amixer: Could not get mic bias voltage',
+ self.parent.running_sugar)
+ if output is None:
+ return False
+ else:
+ output = output[find(output, 'Mono:'):]
+ output = output[find(output, '[') + 1:]
+ output = output[:find(output, ']')]
+ if output == 'on':
+ return True
+ return False
+
+ def set_dc_mode(self, dc_mode=False):
+ '''Sets the DC Mode Enable control
+ pass False to mute and True to unmute'''
+ if self._labels_available and self.parent.hw != XO1:
+ if self._dc_control is not None:
+ self._set_mute(self._dc_control, 'DC mode', not dc_mode)
+ else:
+ self.amixer_set('DC Mode Enable', dc_mode)
+
+ def get_dc_mode(self):
+ '''Returns the setting of DC Mode Enable control
+ i.e. True: Unmuted and False: Muted'''
+ if self._labels_available and self.parent.hw != XO1:
+ if self._dc_control is not None:
+ return not self._get_mute(self._dc_control, 'DC mode', False)
+ else:
+ return False
+ else:
+ output = check_output(['amixer', 'get', "DC Mode Enable"],
+ 'amixer: Could not get DC Mode',
+ self.parent.running_sugar)
+ if output is None:
+ return False
+ else:
+ output = output[find(output, 'Mono:'):]
+ output = output[find(output, '[') + 1:]
+ output = output[:find(output, ']')]
+ if output == 'on':
+ return True
+ return False
+
+ def set_mic_boost(self, mic_boost=False):
+ '''Set Mic Boost.
+ True = +20dB, False = 0dB'''
+ if self._labels_available:
+ if self._mic_boost_control is None:
+ return
+ # If there is a volume, use set volume
+ if hasattr(self._mic_boost_control, 'min_volume'):
+ if mic_boost:
+ self._set_volume(self._mic_boost_control, 'boost', 100)
+ else:
+ self._set_volume(self._mic_boost_control, 'boost', 0)
+ # Else if there is a flag property, use set_mute
+ elif self._mic_boost_control not in self._mixer.list_tracks() or \
+ hasattr(self._mic_boost_control.props, 'flags'):
+ self._set_mute(
+ self._mic_boost_control, 'Mic Boost', not mic_boost)
+ else:
+ self.amixer_set('Mic Boost (+20dB)', mic_boost)
+
+ def get_mic_boost(self):
+ '''Return Mic Boost setting.
+ True = +20dB, False = 0dB'''
+ if self._labels_available:
+ if self._mic_boost_control is None:
+ return False
+ if self._mic_boost_control not in self._mixer.list_tracks() or \
+ hasattr(self._mic_boost_control.props, 'flags'):
+ return not self._get_mute(
+ self._mic_boost_control, 'Mic Boost', False)
+ else: # Compare to min value
+ value = self._mixer.get_volume(self._mic_boost_control)
+ if value != self._mic_boost_control.min_volume:
+ return True
+ return False
+ else:
+ output = check_output(['amixer', 'get', "Mic Boost (+20dB)"],
+ 'amixer: Could not get mic boost',
+ self.parent.running_sugar)
+ if output is None:
+ return False
+ else:
+ output = output[find(output, 'Mono:'):]
+ output = output[find(output, '[') + 1:]
+ output = output[:find(output, ']')]
+ if output == 'on':
+ return True
+ return False
+
+ def set_capture_gain(self, capture_val):
+ '''Sets the Capture gain slider settings
+ capture_val must be given as an integer between 0 and 100 indicating the
+ percentage of the slider to be set'''
+ if self._labels_available and self.parent.hw != XO1:
+ if self._capture_control is not None:
+ self._set_volume(self._capture_control, 'Capture', capture_val)
+ else:
+ output = check_output(
+ ['amixer', 'set', 'Capture', "%d%s" % (capture_val, '%')],
+ 'Problem with amixer set Capture',
+ self.parent.running_sugar)
+
+ def get_capture_gain(self):
+ '''Gets the Capture gain slider settings. The value returned is an
+ integer between 0-100 and is an indicative of the percentage 0 - 100%'''
+ if self._labels_available:
+ if self._capture_control is not None:
+ return self._get_volume(self._capture_control, 'Capture')
+ else:
+ return 0
+ else:
+ output = check_output(['amixer', 'get', 'Capture'],
+ 'amixer: Could not get Capture level',
+ self.parent.running_sugar)
+ if output is None:
+ return 100
+ else:
+ output = output[find(output, 'Front Left:'):]
+ output = output[find(output, '[') + 1:]
+ output = output[:find(output, '%]')]
+ return int(output)
+
+ def set_mic_gain(self, mic_val):
+ '''Sets the MIC gain slider settings
+ mic_val must be given as an integer between 0 and 100 indicating the
+ percentage of the slider to be set'''
+ if self._labels_available and self.parent.hw != XO1:
+ self._set_volume(self._mic_gain_control, 'Mic', mic_val)
+ else:
+ output = check_output(
+ ['amixer', 'set', 'Mic', "%d%s" % (mic_val, '%')],
+ 'Problem with amixer set Mic',
+ self.parent.running_sugar)
+
+ def get_mic_gain(self):
+ '''Gets the MIC gain slider settings. The value returned is an
+ integer between 0-100 and is an indicative of the percentage 0 - 100%'''
+ if self._labels_available and self.parent.hw != XO1:
+ return self._get_volume(self._mic_gain_control, 'Mic')
+ else:
+ output = check_output(['amixer', 'get', 'Mic'],
+ 'amixer: Could not get mic gain level',
+ self.parent.running_sugar)
+ if output is None:
+ return 100
+ else:
+ output = output[find(output, 'Mono:'):]
+ output = output[find(output, '[') + 1:]
+ output = output[:find(output, '%]')]
+ return int(output)
+
+ def _set_sensor_type(self, mode=None, bias=None, gain=None, boost=None):
+ '''Helper to modify (some) of the sensor settings.'''
+ if mode is not None:
+ self.set_dc_mode(mode)
+ if bias is not None:
+ self.set_bias(bias)
+ if gain is not None:
+ self.set_capture_gain(gain)
+ if boost is not None:
+ self.set_mic_boost(boost)
+ self.save_state()
+
+ def on_activity_quit(self):
+ '''When Activity quits'''
+ self.set_mic_boost(QUIT_MIC_BOOST)
+ self.set_dc_mode(QUIT_DC_MODE_ENABLE)
+ self.set_capture_gain(QUIT_CAPTURE_GAIN)
+ self.set_bias(QUIT_BIAS)
+ self.stop_sound_device()
+
+
+def check_output(command, warning, running_sugar=True):
+ ''' Workaround for old systems without subprocess.check_output'''
+ if hasattr(subprocess, 'check_output'):
+ try:
+ output = subprocess.check_output(command)
+ except subprocess.CalledProcessError:
+ debug_output(warning, running_sugar)
+ return None
+ else:
+ import commands
+
+ cmd = ''
+ for c in command:
+ cmd += c
+ cmd += ' '
+ (status, output) = commands.getstatusoutput(cmd)
+ if status != 0:
+ debug_output(warning, running_sugar)
+ return None
+ return output
diff --git a/plugins/audio_sensors/icons/extrasoff.svg b/plugins/audio_sensors/icons/extrasoff.svg
new file mode 100644
index 0000000..a956f03
--- /dev/null
+++ b/plugins/audio_sensors/icons/extrasoff.svg
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <rect
+ width="42.345783"
+ height="42.345783"
+ x="6.3271084"
+ y="6.3271084"
+ id="rect3016"
+ style="fill:#282828;fill-opacity:1;stroke:#282828;stroke-width:2.6542182;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="g3">
+ <polygon
+ points="26.891,12.363 17.238,16.369 14.659,4.975 20.555,2.531 "
+ id="polygon5"
+ style="fill:#ffffff" />
+ <polygon
+ points="42.646,26.88 38.649,17.228 50.04,14.654 52.477,20.55 "
+ id="polygon7"
+ style="fill:#ffffff" />
+ <polygon
+ points="28.117,42.645 37.775,38.645 40.349,50.029 34.453,52.471 "
+ id="polygon9"
+ style="fill:#ffffff" />
+ <polygon
+ points="37.824,16.315 28.171,12.309 34.394,2.439 40.295,4.882 "
+ id="polygon11"
+ style="fill:#ffffff" />
+ <polygon
+ points="38.628,37.791 42.623,28.139 52.493,34.365 50.055,40.258 "
+ id="polygon13"
+ style="fill:#ffffff" />
+ <polygon
+ points="16.385,37.76 12.391,28.105 2.515,34.34 4.953,40.234 "
+ id="polygon15"
+ style="fill:#ffffff" />
+ <polygon
+ points="12.319,26.875 16.32,17.216 4.936,14.643 2.493,20.539 "
+ id="polygon17"
+ style="fill:#ffffff" />
+ <polygon
+ points="26.939,42.623 17.287,38.629 14.719,50.018 20.609,52.461 "
+ id="polygon19"
+ style="fill:#ffffff" />
+ <path
+ d="m 39.925,22.352 c 2.845,6.863 -0.412,14.728 -7.274,17.574 -6.867,2.85 -14.734,-0.418 -17.578,-7.281 -2.84,-6.862 0.418,-14.733 7.279,-17.572 6.862,-2.845 14.734,0.417 17.573,7.279 z"
+ id="path21"
+ style="fill:none;stroke:#ffffff;stroke-width:11.69439983" />
+ </g>
+</svg>
diff --git a/plugins/audio_sensors/icons/extrason.svg b/plugins/audio_sensors/icons/extrason.svg
new file mode 100644
index 0000000..7ee08bf
--- /dev/null
+++ b/plugins/audio_sensors/icons/extrason.svg
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata20">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5">
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2431"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2428"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2557"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,9.2560985,9.9123239)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2561"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,8.962951,22.783517)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2461"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2463"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
+ </defs>
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <g
+ id="g3799">
+ <polygon
+ points="20.555,2.531 26.891,12.363 17.238,16.369 14.659,4.975 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon5"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="52.477,20.55 42.646,26.88 38.649,17.228 50.04,14.654 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon7"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="34.453,52.471 28.117,42.645 37.775,38.645 40.349,50.029 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon9"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="40.295,4.882 37.824,16.315 28.171,12.309 34.394,2.439 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon11"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="50.055,40.258 38.628,37.791 42.623,28.139 52.493,34.365 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon13"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="4.953,40.234 16.385,37.76 12.391,28.105 2.515,34.34 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon15"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="2.493,20.539 12.319,26.875 16.32,17.216 4.936,14.643 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon17"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="20.609,52.461 26.939,42.623 17.287,38.629 14.719,50.018 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon19"
+ style="fill:#ff0000;fill-opacity:1" />
+ <path
+ d="m 35.45648,24.236169 c 1.8208,4.389511 -0.26368,9.419891 -4.65536,11.240166 -4.39488,1.822833 -9.42976,-0.267349 -11.24992,-4.65686 -1.8176,-4.388871 0.26752,-9.423089 4.65856,-11.238887 4.39168,-1.819635 9.42976,0.26671 11.24672,4.655581 z"
+ id="path21"
+ style="fill:none;stroke:#ff0000;stroke-width:7.48202181;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/plugins/audio_sensors/icons/sensoroff.svg b/plugins/audio_sensors/icons/sensoroff.svg
new file mode 100644
index 0000000..0a16670
--- /dev/null
+++ b/plugins/audio_sensors/icons/sensoroff.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata15"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs13" />
+<rect
+ width="42.763924"
+ height="42.763924"
+ x="6.1180382"
+ y="6.1180382"
+ id="rect2986"
+ style="fill:#282828;fill-opacity:1;stroke:#282828;stroke-width:2.23607516;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><g
+ transform="matrix(0.87078705,0,0,0.87078705,3.2821055,2.9298726)"
+ id="network-wired_1_"
+ style="display:block">
+ <g
+ id="network-wired">
+ <line
+ id="line3076"
+ y2="23.993"
+ y1="32.438999"
+ x2="16.966999"
+ x1="16.966999"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round" />
+ <line
+ id="line3078"
+ y2="28.215"
+ y1="28.215"
+ x2="34.938"
+ x1="29.636999"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round" />
+
+ <rect
+ width="9.7939997"
+ height="7.599"
+ x="42.157001"
+ y="24.312"
+ id="rect3080"
+ style="fill:#ffffff;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round" />
+ <path
+ d="m 16.967,23.993 c 0,-2.334 -1.892,-4.224 -4.224,-4.224 -2.332,0 -4.223,1.889 -4.223,4.224"
+ id="path3082"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 25.413,32.439 c 0,2.334 -1.891,4.224 -4.224,4.224 -2.332,0 -4.223,-1.89 -4.223,-4.224"
+ id="path3084"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 25.413,32.439 c 0,-2.332 1.893,-4.226 4.224,-4.226"
+ id="path3086"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 8.52,23.993 c 0,2.332 -1.892,4.222 -4.223,4.222"
+ id="path3088"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+
+ <rect
+ width="14.477"
+ height="11.35"
+ x="31.945"
+ y="22.438"
+ id="rect3090"
+ style="fill:#ffffff;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/plugins/audio_sensors/icons/sensoron.svg b/plugins/audio_sensors/icons/sensoron.svg
new file mode 100644
index 0000000..d756860
--- /dev/null
+++ b/plugins/audio_sensors/icons/sensoron.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata15"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs13" /><rect
+ width="55"
+ height="55"
+ x="0"
+ y="0"
+ id="rect3269"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:nonzero;stroke:none" /><g
+ transform="translate(0.27777716,18.796296)"
+ id="g4054"><line
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-opacity:1"
+ x1="17.778971"
+ x2="17.778971"
+ y1="12.381037"
+ y2="5.0263696"
+ id="line3076" /><line
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-opacity:1"
+ x1="28.811842"
+ x2="33.427887"
+ y1="8.7028332"
+ y2="8.7028332"
+ id="line3078" /><rect
+ width="8.5284882"
+ height="6.6171107"
+ x="39.7141"
+ y="5.3041511"
+ id="rect3080"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:1.95927083;stroke-linecap:round;stroke-opacity:1" /><path
+ d="m 17.778971,5.0263697 c 0,-2.032417 -1.647529,-3.6782045 -3.678204,-3.6782045 -2.030675,0 -3.677334,1.6449167 -3.677334,3.6782045"
+ id="path3082"
+ style="fill:none;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 25.133639,12.381037 c 0,2.032417 -1.646658,3.678205 -3.678205,3.678205 -2.030675,0 -3.677333,-1.645788 -3.677333,-3.678205"
+ id="path3084"
+ style="fill:none;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 25.133639,12.381037 c 0,-2.030675 1.6484,-3.679946 3.678204,-3.679946"
+ id="path3086"
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 10.423433,5.0263697 c 0,2.0306754 -1.6475288,3.6764629 -3.6773334,3.6764629"
+ id="path3088"
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><rect
+ width="12.606384"
+ height="9.8834333"
+ x="30.821619"
+ y="3.6722956"
+ id="rect3090"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/plugins/audio_sensors/ringbuffer.py b/plugins/audio_sensors/ringbuffer.py
new file mode 100644
index 0000000..2afb5c9
--- /dev/null
+++ b/plugins/audio_sensors/ringbuffer.py
@@ -0,0 +1,108 @@
+# Copyright (C) 2009, Benjamin Berg, Sebastian Berg
+# Copyright (C) 2010, Walter Bender
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+import numpy as np
+
+
+class RingBuffer1d(object):
+ """This class implements an array being written in as a ring and that can
+ be read from continuously ending with the newest data or starting with the
+ oldest. It returns a numpy array copy of the data;
+ """
+
+ def __init__(self, length, dtype=None):
+ """Initialize the 1 dimensional ring buffer with the given lengths.
+ The initial values are all 0s
+ """
+ self.offset = 0
+
+ self._data = np.zeros(length, dtype=dtype)
+
+ self.stored = 0
+
+ def fill(self, number):
+ self._data.fill(number)
+ self.offset = 0
+
+ def append(self, data):
+ """Append to the ring buffer (and overwrite old data). If len(data)
+ is greater then the ring buffers length, the newest data takes
+ precedence.
+ """
+ data = np.asarray(data)
+
+ if len(self._data) == 0:
+ return
+
+ if len(data) >= len(self._data):
+ self._data[:] = data[-len(self._data):]
+ self.offset = 0
+ self.stored = len(self._data)
+
+ elif len(self._data) - self.offset >= len(data):
+ self._data[self.offset: self.offset + len(data)] = data
+ self.offset = self.offset + len(data)
+ self.stored += len(data)
+ else:
+ self._data[self.offset:] = data[:len(self._data) - self.offset]
+ self._data[:len(data) - (len(self._data) - self.offset)] = \
+ data[-len(data) + (len(self._data) - self.offset):]
+ self.offset = len(data) - (len(self._data) - self.offset)
+ self.stored += len(data)
+
+ if len(self._data) <= self.stored:
+ self.read = self._read
+
+ def read(self, number=None, step=1):
+ """Read the ring Buffer. Number can be positive or negative.
+ Positive values will give the latest information, negative values will
+ give the newest added information from the buffer. (in normal order)
+
+ Before the buffer is filled once: This returns just None
+ """
+ return np.array([])
+
+ def _read(self, number=None, step=1):
+ """Read the ring Buffer. Number can be positive or negative.
+ Positive values will give the latest information, negative values will
+ give the newest added information from the buffer. (in normal order)
+ """
+ if number == None:
+ number = len(self._data) // step
+
+ number *= step
+ assert abs(number) <= len(self._data), \
+ 'Number to read*step must be smaller then length'
+
+ if number < 0:
+ if abs(number) <= self.offset:
+ return self._data[self.offset + number:self.offset:step]
+
+ spam = (self.offset - 1) % step
+
+ return np.concatenate(
+ (self._data[step - spam - 1 + self.offset + number::step],
+ self._data[spam:self.offset:step]))
+
+ if number - (len(self._data) - self.offset) > 0:
+ spam = ((self.offset + number) - self.offset - 1) % step
+ return np.concatenate(
+ (self._data[self.offset:self.offset + number:step],
+ self._data[spam:number -
+ (len(self._data) - self.offset):step]))
+
+ return self._data[self.offset:self.offset + number:step].copy()
diff --git a/plugins/camera_sensor/camera_sensor.py b/plugins/camera_sensor/camera_sensor.py
new file mode 100644
index 0000000..cb1e226
--- /dev/null
+++ b/plugins/camera_sensor/camera_sensor.py
@@ -0,0 +1,332 @@
+#!/usr/bin/env python
+#Copyright (c) 2011, 2012 Walter Bender
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gst
+import gtk
+from fcntl import ioctl
+import os
+from time import time
+
+from gettext import gettext as _
+
+from plugins.camera_sensor.tacamera import Camera
+from plugins.camera_sensor.v4l2 import v4l2_control, V4L2_CID_AUTOGAIN, \
+ VIDIOC_G_CTRL, VIDIOC_S_CTRL
+
+from plugins.plugin import Plugin
+
+from TurtleArt.tapalette import make_palette
+from TurtleArt.talogo import media_blocks_dictionary
+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')
+ if os.path.exists('/dev/video1'):
+ self.devices.append('/dev/video1')
+ if len(self.devices) > 0:
+ self._status = True
+ else:
+ self._status = False
+
+ def setup(self):
+ ''' Set up the palettes '''
+ '''
+ sensors_palette = make_palette('sensor',
+ colors=["#FF6060", "#A06060"],
+ help_string=_(
+ 'Palette of sensor blocks'),
+ position=6)
+ '''
+ media_palette = make_palette('media',
+ colors=["#A0FF00", "#80A000"],
+ help_string=_('Palette of media objects'),
+ position=7)
+ sensors_palette = media_palette
+
+ # set up camera-specific blocks
+ media_blocks_dictionary['camera'] = self.prim_take_picture0
+ media_blocks_dictionary['camera1'] = self.prim_take_picture1
+
+ SKIN_PATHS.append('plugins/camera_sensor/images')
+
+ if self._status:
+ sensors_palette.add_block('luminance',
+ hidden=True,
+ style='box-style',
+ label=_('brightness'),
+ help_string=_(
+ 'light level detected by camera'),
+ value_block=True,
+ prim_name='luminance')
+ self._parent.lc.def_prim(
+ 'luminance', 0,
+ 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',
+ hidden=True,
+ style='box-style',
+ label=_('brightness'),
+ help_string=_(
+ 'Average RGB color from camera \
+is pushed to the stack'),
+ value_block=True,
+ prim_name='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',
+ hidden=True,
+ style='box-style-media',
+ label=' ',
+ default='CAMERA',
+ help_string=_('camera output'),
+ content_block=True)
+ if len(self.devices) > 1:
+ media_palette.add_block('camera1',
+ hidden=True,
+ style='box-style-media',
+ label=' ',
+ default='CAMERA',
+ help_string=_('camera output'),
+ content_block=True)
+ else:
+ media_palette.add_block('camera1',
+ hidden=True,
+ style='box-style-media',
+ label=' ',
+ default='CAMERA',
+ help_string=_('camera output'),
+ content_block=True)
+
+ else: # No camera, so blocks should do nothing
+ sensors_palette.add_block('luminance',
+ hidden=True,
+ style='box-style',
+ label=_('brightness'),
+ help_string=\
+ _('light level detected by camera'),
+ value_block=True,
+ prim_name='read_camera')
+ 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',
+ hidden=True,
+ style='box-style',
+ label=_('brightness'),
+ help_string=_(
+ 'Average RGB color from camera \
+is pushed to the stack'),
+ value_block=True,
+ prim_name='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,
+ style='box-style-media',
+ label=' ',
+ default='CAMERA',
+ help_string=_('camera output'),
+ content_block=True)
+
+ media_palette.add_block('camera1',
+ hidden=True,
+ style='box-style-media',
+ label=' ',
+ default='CAMERA',
+ help_string=_('camera output'),
+ content_block=True)
+
+ NO_IMPORT.append('camera')
+ BLOCKS_WITH_SKIN.append('camera')
+ NO_IMPORT.append('camera1')
+ BLOCKS_WITH_SKIN.append('camera1')
+ MEDIA_SHAPES.append('camerasmall')
+ MEDIA_SHAPES.append('cameraoff')
+ MEDIA_SHAPES.append('camera1small')
+ MEDIA_SHAPES.append('camera1off')
+
+ def start(self):
+ ''' Initialize the camera if there is an camera block in use '''
+ if len(self._parent.block_list.get_similar_blocks('block',
+ ['camera', 'camera1', 'read_camera', 'luminance'])) > 0:
+ if self._status and len(self.cameras) == 0:
+ for device in self.devices:
+ self.cameras.append(Camera(device))
+
+ def quit(self):
+ ''' This gets called when the activity quits '''
+ self._reset_the_camera()
+
+ def stop(self):
+ ''' This gets called by the stop button '''
+ self._reset_the_camera()
+
+ def clear(self):
+ ''' This gets called by the clean button and erase button '''
+ self._reset_the_camera()
+
+ def _reset_the_camera(self):
+ if self._status and len(self.cameras) > 0:
+ for i, camera in enumerate(self.cameras):
+ camera.stop_camera_input()
+ self._set_autogain(1, camera=i) # enable AUTOGAIN
+
+ def _status_report(self):
+ debug_output('Reporting camera status: %s' % (str(self._status)),
+ self._parent.running_sugar)
+ return self._status
+
+ # Block primitives used in talogo
+
+ def prim_take_picture0(self):
+ self._take_picture(camera=0)
+
+ def prim_take_picture1(self):
+ self._take_picture(camera=1)
+
+ def _take_picture(self, camera=0):
+ ''' method called by media block '''
+ self._set_autogain(1, camera) # enable AUTOGAIN
+ self._get_pixbuf_from_camera(camera)
+ self._parent.lc.pixbuf = self.cameras[camera].pixbuf
+
+ def prim_read_camera(self, luminance_only=False, camera=0):
+ """ Read average pixel from camera and push b, g, r to the stack """
+ self.luminance_only = luminance_only
+ if not self._status:
+ if self.luminance_only:
+ return -1
+ else:
+ self._parent.lc.heap.append(-1)
+ self._parent.lc.heap.append(-1)
+ self._parent.lc.heap.append(-1)
+ 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:
+ 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()
+ width = self.cameras[camera].pixbuf.get_width()
+ height = self.cameras[camera].pixbuf.get_height()
+
+ if array is not None:
+ length = int(len(array) / 3)
+ if length != width * height:
+ debug_output('array length != width x height (%d != %dx%d)' % \
+ (length, width, height),
+ self._parent.running_sugar)
+
+ # Average the 100 pixels in the center of the screen
+ r, g, b = 0, 0, 0
+ row_offset = int((height / 2 - 5) * width * 3)
+ column_offset = int(width / 2 - 5) * 3
+ for y in range(10):
+ i = row_offset + column_offset
+ for x in range(10):
+ r += ord(array[i])
+ i += 1
+ g += ord(array[i])
+ i += 1
+ b += ord(array[i])
+ i += 1
+ row_offset += width * 3
+ if self.luminance_only:
+ self.luminance = int((r * 0.3 + g * 0.6 + b * 0.1) / 100)
+ else:
+ self.r = int(r / 100)
+ self.g = int(g / 100)
+ self.b = int(b / 100)
+ else:
+ if self.luminance_only:
+ self.luminance = -1
+ else:
+ self.r = -1
+ 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:
+ return
+ try:
+ video_capture_device = open(self.devices[camera], 'rw')
+ except:
+ video_capture_device = None
+ debug_output('video capture device not available',
+ self._parent.running_sugar)
+ return
+ self._ag_control = v4l2_control(V4L2_CID_AUTOGAIN)
+ try:
+ ioctl(video_capture_device, VIDIOC_G_CTRL, self._ag_control)
+ self._ag_control.value = state
+ ioctl(video_capture_device, VIDIOC_S_CTRL, self._ag_control)
+ except:
+ pass
+ video_capture_device.close()
+
+ def _get_pixbuf_from_camera(self, camera):
+ ''' Regardless of how we get it, we want to return a pixbuf '''
+ self._parent.lc.pixbuf = None
+ if self._status:
+ self.cameras[camera].start_camera_input()
diff --git a/plugins/camera_sensor/glive.py b/plugins/camera_sensor/glive.py
new file mode 100644
index 0000000..9f8c5fa
--- /dev/null
+++ b/plugins/camera_sensor/glive.py
@@ -0,0 +1,638 @@
+#Copyright (c) 2008, Media Modifications Ltd.
+
+#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 os
+from gettext import gettext as _
+import time
+
+import gtk
+import gst
+import pygst
+pygst.require('0.10')
+import gobject
+gobject.threads_init()
+
+from sugar.activity.activity import get_bundle_path
+import logging
+
+from instance import Instance
+import constants
+import utils
+
+logger = logging.getLogger('glive')
+
+OGG_TRAITS = {
+ 0: { 'width': 160, 'height': 120, 'quality': 16 },
+ 1: { 'width': 384, 'height': 288, 'quality': 16 } }
+
+class Glive:
+ PHOTO_MODE_PHOTO = 0
+ PHOTO_MODE_AUDIO = 1
+
+ def __init__(self, activity_obj, model):
+ self.activity = activity_obj
+ self.model = model
+ self._eos_cb = None
+
+ self._has_camera = False
+ self._can_limit_framerate = False
+ self._playing = False
+ self._pic_exposure_open = False
+ self._thumb_exposure_open = False
+ self._photo_mode = self.PHOTO_MODE_PHOTO
+
+ self._audio_transcode_handler = None
+ self._transcode_id = None
+ self._video_transcode_handler = None
+ self._thumb_handoff_handler = None
+
+ self._audio_pixbuf = None
+
+ self._detect_camera()
+
+ self._pipeline = gst.Pipeline("Record")
+ self._create_photobin()
+ self._create_audiobin()
+ self._create_videobin()
+ self._create_xbin()
+ self._create_pipeline()
+
+ self._thumb_pipes = []
+ self._mux_pipes = []
+
+ bus = self._pipeline.get_bus()
+ bus.add_signal_watch()
+ bus.connect('message', self._bus_message_handler)
+
+ def _detect_camera(self):
+ v4l2src = gst.element_factory_make('v4l2src')
+ if v4l2src.props.device_name is None:
+ return
+
+ self._has_camera = True
+
+ # Figure out if we can place a framerate limit on the v4l2 element,
+ # which in theory will make it all the way down to the hardware.
+ # ideally, we should be able to do this by checking caps. However, I
+ # can't find a way to do this (at this time, XO-1 cafe camera driver
+ # doesn't support framerate changes, but gstreamer caps suggest
+ # otherwise)
+ pipeline = gst.Pipeline()
+ caps = gst.Caps('video/x-raw-yuv,framerate=10/1')
+ fsink = gst.element_factory_make('fakesink')
+ pipeline.add(v4l2src, fsink)
+ v4l2src.link(fsink, caps)
+ self._can_limit_framerate = pipeline.set_state(gst.STATE_PAUSED) != gst.STATE_CHANGE_FAILURE
+ pipeline.set_state(gst.STATE_NULL)
+
+ def get_has_camera(self):
+ return self._has_camera
+
+ def _create_photobin(self):
+ queue = gst.element_factory_make("queue", "pbqueue")
+ queue.set_property("leaky", True)
+ queue.set_property("max-size-buffers", 1)
+
+ colorspace = gst.element_factory_make("ffmpegcolorspace", "pbcolorspace")
+ jpeg = gst.element_factory_make("jpegenc", "pbjpeg")
+
+ sink = gst.element_factory_make("fakesink", "pbsink")
+ sink.connect("handoff", self._photo_handoff)
+ sink.set_property("signal-handoffs", True)
+
+ self._photobin = gst.Bin("photobin")
+ self._photobin.add(queue, colorspace, jpeg, sink)
+
+ gst.element_link_many(queue, colorspace, jpeg, sink)
+
+ pad = queue.get_static_pad("sink")
+ self._photobin.add_pad(gst.GhostPad("sink", pad))
+
+ def _create_audiobin(self):
+ src = gst.element_factory_make("alsasrc", "absrc")
+
+ # attempt to use direct access to the 0,0 device, solving some A/V
+ # sync issues
+ src.set_property("device", "plughw:0,0")
+ hwdev_available = src.set_state(gst.STATE_PAUSED) != gst.STATE_CHANGE_FAILURE
+ src.set_state(gst.STATE_NULL)
+ if not hwdev_available:
+ src.set_property("device", "default")
+
+ srccaps = gst.Caps("audio/x-raw-int,rate=16000,channels=1,depth=16")
+
+ # guarantee perfect stream, important for A/V sync
+ rate = gst.element_factory_make("audiorate")
+
+ # without a buffer here, gstreamer struggles at the start of the
+ # recording and then the A/V sync is bad for the whole video
+ # (possibly a gstreamer/ALSA bug -- even if it gets caught up, it
+ # should be able to resync without problem)
+ queue = gst.element_factory_make("queue", "audioqueue")
+ queue.set_property("leaky", True) # prefer fresh data
+ queue.set_property("max-size-time", 5000000000) # 5 seconds
+ queue.set_property("max-size-buffers", 500)
+ queue.connect("overrun", self._log_queue_overrun)
+
+ enc = gst.element_factory_make("wavenc", "abenc")
+
+ sink = gst.element_factory_make("filesink", "absink")
+ sink.set_property("location", os.path.join(Instance.instancePath, "output.wav"))
+
+ self._audiobin = gst.Bin("audiobin")
+ self._audiobin.add(src, rate, queue, enc, sink)
+
+ src.link(rate, srccaps)
+ gst.element_link_many(rate, queue, enc, sink)
+
+ def _create_videobin(self):
+ queue = gst.element_factory_make("queue", "videoqueue")
+ queue.set_property("max-size-time", 5000000000) # 5 seconds
+ queue.set_property("max-size-bytes", 33554432) # 32mb
+ queue.connect("overrun", self._log_queue_overrun)
+
+ scale = gst.element_factory_make("videoscale", "vbscale")
+
+ scalecapsfilter = gst.element_factory_make("capsfilter", "scalecaps")
+
+ scalecaps = gst.Caps('video/x-raw-yuv,width=160,height=120')
+ scalecapsfilter.set_property("caps", scalecaps)
+
+ colorspace = gst.element_factory_make("ffmpegcolorspace", "vbcolorspace")
+
+ enc = gst.element_factory_make("theoraenc", "vbenc")
+ enc.set_property("quality", 16)
+
+ mux = gst.element_factory_make("oggmux", "vbmux")
+
+ sink = gst.element_factory_make("filesink", "vbfile")
+ sink.set_property("location", os.path.join(Instance.instancePath, "output.ogg"))
+
+ self._videobin = gst.Bin("videobin")
+ self._videobin.add(queue, scale, scalecapsfilter, colorspace, enc, mux, sink)
+
+ queue.link(scale)
+ scale.link_pads(None, scalecapsfilter, "sink")
+ scalecapsfilter.link_pads("src", colorspace, None)
+ gst.element_link_many(colorspace, enc, mux, sink)
+
+ pad = queue.get_static_pad("sink")
+ self._videobin.add_pad(gst.GhostPad("sink", pad))
+
+ def _create_xbin(self):
+ scale = gst.element_factory_make("videoscale")
+ cspace = gst.element_factory_make("ffmpegcolorspace")
+ xsink = gst.element_factory_make("ximagesink", "xsink")
+ xsink.set_property("force-aspect-ratio", True)
+
+ # http://thread.gmane.org/gmane.comp.video.gstreamer.devel/29644
+ xsink.set_property("sync", False)
+
+ self._xbin = gst.Bin("xbin")
+ self._xbin.add(scale, cspace, xsink)
+ gst.element_link_many(scale, cspace, xsink)
+
+ pad = scale.get_static_pad("sink")
+ self._xbin.add_pad(gst.GhostPad("sink", pad))
+
+ def _config_videobin(self, quality, width, height):
+ vbenc = self._videobin.get_by_name("vbenc")
+ vbenc.set_property("quality", 16)
+ scaps = self._videobin.get_by_name("scalecaps")
+ scaps.set_property("caps", gst.Caps("video/x-raw-yuv,width=%d,height=%d" % (width, height)))
+
+ def _create_pipeline(self):
+ if not self._has_camera:
+ return
+
+ src = gst.element_factory_make("v4l2src", "camsrc")
+ try:
+ # old gst-plugins-good does not have this property
+ src.set_property("queue-size", 2)
+ except:
+ pass
+
+ # if possible, it is important to place the framerate limit directly
+ # on the v4l2src so that it gets communicated all the way down to the
+ # camera level
+ if self._can_limit_framerate:
+ srccaps = gst.Caps('video/x-raw-yuv,framerate=10/1')
+ else:
+ srccaps = gst.Caps('video/x-raw-yuv')
+
+ # we attempt to limit the framerate on the v4l2src directly, but we
+ # can't trust this: perhaps we are falling behind in our capture,
+ # or maybe the kernel driver doesn't provide the exact framerate.
+ # the videorate element guarantees a perfect framerate and is important
+ # for A/V sync because OGG does not store timestamps, it just stores
+ # the FPS value.
+ rate = gst.element_factory_make("videorate")
+ ratecaps = gst.Caps('video/x-raw-yuv,framerate=10/1')
+
+ tee = gst.element_factory_make("tee", "tee")
+ queue = gst.element_factory_make("queue", "dispqueue")
+
+ # prefer fresh frames
+ queue.set_property("leaky", True)
+ queue.set_property("max-size-buffers", 2)
+
+ self._pipeline.add(src, rate, tee, queue)
+ src.link(rate, srccaps)
+ rate.link(tee, ratecaps)
+ tee.link(queue)
+
+ self._xvsink = gst.element_factory_make("xvimagesink", "xsink")
+ self._xv_available = self._xvsink.set_state(gst.STATE_PAUSED) != gst.STATE_CHANGE_FAILURE
+ self._xvsink.set_state(gst.STATE_NULL)
+
+ # http://thread.gmane.org/gmane.comp.video.gstreamer.devel/29644
+ self._xvsink.set_property("sync", False)
+
+ self._xvsink.set_property("force-aspect-ratio", True)
+
+ def _log_queue_overrun(self, queue):
+ cbuffers = queue.get_property("current-level-buffers")
+ cbytes = queue.get_property("current-level-bytes")
+ ctime = queue.get_property("current-level-time")
+ logger.error("Buffer overrun in %s (%d buffers, %d bytes, %d time)"
+ % (queue.get_name(), cbuffers, cbytes, ctime))
+
+ def _thumb_element(self, name):
+ return self._thumb_pipes[-1].get_by_name(name)
+
+ def is_using_xv(self):
+ return self._pipeline.get_by_name("xsink") == self._xvsink
+
+ def _configure_xv(self):
+ if self.is_using_xv():
+ # nothing to do, Xv already configured
+ return self._xvsink
+
+ queue = self._pipeline.get_by_name("dispqueue")
+ if self._pipeline.get_by_name("xbin"):
+ # X sink is configured, so remove it
+ queue.unlink(self._xbin)
+ self._pipeline.remove(self._xbin)
+
+ self._pipeline.add(self._xvsink)
+ queue.link(self._xvsink)
+ return self._xvsink
+
+ def _configure_x(self):
+ if self._pipeline.get_by_name("xbin") == self._xbin:
+ # nothing to do, X already configured
+ return self._xbin.get_by_name("xsink")
+
+ queue = self._pipeline.get_by_name("dispqueue")
+ xvsink = self._pipeline.get_by_name("xsink")
+
+ if xvsink:
+ # Xv sink is configured, so remove it
+ queue.unlink(xvsink)
+ self._pipeline.remove(xvsink)
+
+ self._pipeline.add(self._xbin)
+ queue.link(self._xbin)
+ return self._xbin.get_by_name("xsink")
+
+ def play(self, use_xv=True):
+ if self._get_state() == gst.STATE_PLAYING:
+ return
+
+ if self._has_camera:
+ if use_xv and self._xv_available:
+ xsink = self._configure_xv()
+ else:
+ xsink = self._configure_x()
+
+ # X overlay must be set every time, it seems to forget when you stop
+ # the pipeline.
+ self.activity.set_glive_sink(xsink)
+
+ self._pipeline.set_state(gst.STATE_PLAYING)
+ self._playing = True
+
+ def pause(self):
+ self._pipeline.set_state(gst.STATE_PAUSED)
+ self._playing = False
+
+ def stop(self):
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._playing = False
+
+ def is_playing(self):
+ return self._playing
+
+ def _get_state(self):
+ return self._pipeline.get_state()[1]
+
+ def stop_recording_audio(self):
+ # We should be able to simply pause and remove the audiobin, but
+ # this seems to cause a gstreamer segfault. So we stop the whole
+ # pipeline while manipulating it.
+ # http://dev.laptop.org/ticket/10183
+ self._pipeline.set_state(gst.STATE_NULL)
+ self.model.shutter_sound()
+ self._pipeline.remove(self._audiobin)
+
+ audio_path = os.path.join(Instance.instancePath, "output.wav")
+ if not os.path.exists(audio_path) or os.path.getsize(audio_path) <= 0:
+ # FIXME: inform model of failure?
+ return
+
+ if self._audio_pixbuf:
+ self.model.still_ready(self._audio_pixbuf)
+
+ line = 'filesrc location=' + audio_path + ' name=audioFilesrc ! wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! filesink name=audioFilesink'
+ audioline = gst.parse_launch(line)
+
+ taglist = self._get_tags(constants.TYPE_AUDIO)
+
+ if self._audio_pixbuf:
+ pixbuf_b64 = utils.getStringEncodedFromPixbuf(self._audio_pixbuf)
+ taglist[gst.TAG_EXTENDED_COMMENT] = "coverart=" + pixbuf_b64
+
+ vorbis_enc = audioline.get_by_name('audioVorbisenc')
+ vorbis_enc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL)
+
+ audioFilesink = audioline.get_by_name('audioFilesink')
+ audioOggFilepath = os.path.join(Instance.instancePath, "output.ogg")
+ audioFilesink.set_property("location", audioOggFilepath)
+
+ audioBus = audioline.get_bus()
+ audioBus.add_signal_watch()
+ self._audio_transcode_handler = audioBus.connect('message', self._onMuxedAudioMessageCb, audioline)
+ self._transcode_id = gobject.timeout_add(200, self._transcodeUpdateCb, audioline)
+ audioline.set_state(gst.STATE_PLAYING)
+
+ def _get_tags(self, type):
+ tl = gst.TagList()
+ tl[gst.TAG_ARTIST] = self.model.get_nickname()
+ tl[gst.TAG_COMMENT] = "olpc"
+ #this is unfortunately, unreliable
+ #record.Record.log.debug("self.ca.metadata['title']->" + str(self.ca.metadata['title']) )
+ tl[gst.TAG_ALBUM] = "olpc" #self.ca.metadata['title']
+ tl[gst.TAG_DATE] = utils.getDateString(int(time.time()))
+ stringType = constants.MEDIA_INFO[type]['istr']
+
+ # Translators: photo by photographer, e.g. "Photo by Mary"
+ tl[gst.TAG_TITLE] = _('%(type)s by %(name)s') % {'type': stringType,
+ 'name': self.model.get_nickname()}
+ return tl
+
+ def _take_photo(self, photo_mode):
+ if self._pic_exposure_open:
+ return
+
+ self._photo_mode = photo_mode
+ self._pic_exposure_open = True
+ pad = self._photobin.get_static_pad("sink")
+ self._pipeline.add(self._photobin)
+ self._photobin.set_state(gst.STATE_PLAYING)
+ self._pipeline.get_by_name("tee").link(self._photobin)
+
+ def take_photo(self):
+ if self._has_camera:
+ self._take_photo(self.PHOTO_MODE_PHOTO)
+
+ def _photo_handoff(self, fsink, buffer, pad, user_data=None):
+ if not self._pic_exposure_open:
+ return
+
+ pad = self._photobin.get_static_pad("sink")
+ self._pipeline.get_by_name("tee").unlink(self._photobin)
+ self._pipeline.remove(self._photobin)
+
+ self._pic_exposure_open = False
+ pic = gtk.gdk.pixbuf_loader_new_with_mime_type("image/jpeg")
+ pic.write( buffer )
+ pic.close()
+ pixBuf = pic.get_pixbuf()
+ del pic
+
+ self.save_photo(pixBuf)
+
+ def save_photo(self, pixbuf):
+ if self._photo_mode == self.PHOTO_MODE_AUDIO:
+ self._audio_pixbuf = pixbuf
+ else:
+ self.model.save_photo(pixbuf)
+
+ def record_video(self, quality):
+ if not self._has_camera:
+ return
+
+ self._ogg_quality = quality
+ self._config_videobin(OGG_TRAITS[quality]['quality'],
+ OGG_TRAITS[quality]['width'],
+ OGG_TRAITS[quality]['height'])
+
+ # If we use pad blocking and adjust the pipeline on-the-fly, the
+ # resultant video has bad A/V sync :(
+ # If we pause the pipeline while adjusting it, the A/V sync is better
+ # but not perfect :(
+ # so we stop the whole thing while reconfiguring to get the best results
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._pipeline.add(self._videobin)
+ self._pipeline.get_by_name("tee").link(self._videobin)
+ self._pipeline.add(self._audiobin)
+ self.play()
+
+ def record_audio(self):
+ if self._has_camera:
+ self._audio_pixbuf = None
+ self._take_photo(self.PHOTO_MODE_AUDIO)
+
+ # we should be able to add the audiobin on the fly, but unfortunately
+ # this results in several seconds of silence being added at the start
+ # of the recording. So we stop the whole pipeline while adjusting it.
+ # SL#2040
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._pipeline.add(self._audiobin)
+ self.play()
+
+ def stop_recording_video(self):
+ if not self._has_camera:
+ return
+
+ # We stop the pipeline while we are adjusting the pipeline to stop
+ # recording because if we do it on-the-fly, the following video live
+ # feed to the screen becomes several seconds delayed. Weird!
+ # FIXME: retest on F11
+ # FIXME: could this be the result of audio shortening problems?
+ self._eos_cb = self._video_eos
+ self._pipeline.get_by_name('camsrc').send_event(gst.event_new_eos())
+ self._audiobin.get_by_name('absrc').send_event(gst.event_new_eos())
+
+ def _video_eos(self):
+ self._pipeline.set_state(gst.STATE_NULL)
+ self._pipeline.get_by_name("tee").unlink(self._videobin)
+ self._pipeline.remove(self._videobin)
+ self._pipeline.remove(self._audiobin)
+
+ self.model.shutter_sound()
+
+ if len(self._thumb_pipes) > 0:
+ thumbline = self._thumb_pipes[-1]
+ thumbline.get_by_name('thumb_fakesink').disconnect(self._thumb_handoff_handler)
+
+ ogg_path = os.path.join(Instance.instancePath, "output.ogg") #ogv
+ if not os.path.exists(ogg_path) or os.path.getsize(ogg_path) <= 0:
+ # FIXME: inform model of failure?
+ return
+
+ line = 'filesrc location=' + ogg_path + ' name=thumbFilesrc ! oggdemux name=thumbOggdemux ! theoradec name=thumbTheoradec ! tee name=thumb_tee ! queue name=thumb_queue ! ffmpegcolorspace name=thumbFfmpegcolorspace ! jpegenc name=thumbJPegenc ! fakesink name=thumb_fakesink'
+ thumbline = gst.parse_launch(line)
+ thumb_queue = thumbline.get_by_name('thumb_queue')
+ thumb_queue.set_property("leaky", True)
+ thumb_queue.set_property("max-size-buffers", 1)
+ thumb_tee = thumbline.get_by_name('thumb_tee')
+ thumb_fakesink = thumbline.get_by_name('thumb_fakesink')
+ self._thumb_handoff_handler = thumb_fakesink.connect("handoff", self.copyThumbPic)
+ thumb_fakesink.set_property("signal-handoffs", True)
+ self._thumb_pipes.append(thumbline)
+ self._thumb_exposure_open = True
+ thumbline.set_state(gst.STATE_PLAYING)
+
+ def copyThumbPic(self, fsink, buffer, pad, user_data=None):
+ if not self._thumb_exposure_open:
+ return
+
+ self._thumb_exposure_open = False
+ loader = gtk.gdk.pixbuf_loader_new_with_mime_type("image/jpeg")
+ loader.write(buffer)
+ loader.close()
+ self.thumbBuf = loader.get_pixbuf()
+ self.model.still_ready(self.thumbBuf)
+
+ self._thumb_element('thumb_tee').unlink(self._thumb_element('thumb_queue'))
+
+ oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
+ wavFilepath = os.path.join(Instance.instancePath, "output.wav")
+ muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv
+
+ muxline = gst.parse_launch('filesrc location=' + str(oggFilepath) + ' name=muxVideoFilesrc ! oggdemux name=muxOggdemux ! theoraparse ! oggmux name=muxOggmux ! filesink location=' + str(muxFilepath) + ' name=muxFilesink filesrc location=' + str(wavFilepath) + ' name=muxAudioFilesrc ! wavparse name=muxWavparse ! audioconvert name=muxAudioconvert ! vorbisenc name=muxVorbisenc ! muxOggmux.')
+ taglist = self._get_tags(constants.TYPE_VIDEO)
+ vorbis_enc = muxline.get_by_name('muxVorbisenc')
+ vorbis_enc.merge_tags(taglist, gst.TAG_MERGE_REPLACE_ALL)
+
+ muxBus = muxline.get_bus()
+ muxBus.add_signal_watch()
+ self._video_transcode_handler = muxBus.connect('message', self._onMuxedVideoMessageCb, muxline)
+ self._mux_pipes.append(muxline)
+ #add a listener here to monitor % of transcoding...
+ self._transcode_id = gobject.timeout_add(200, self._transcodeUpdateCb, muxline)
+ muxline.set_state(gst.STATE_PLAYING)
+
+ def _transcodeUpdateCb( self, pipe ):
+ position, duration = self._query_position( pipe )
+ if position != gst.CLOCK_TIME_NONE:
+ value = position * 100.0 / duration
+ value = value/100.0
+ self.model.set_progress(value, _('Saving...'))
+ return True
+
+ def _query_position(self, pipe):
+ try:
+ position, format = pipe.query_position(gst.FORMAT_TIME)
+ except:
+ position = gst.CLOCK_TIME_NONE
+
+ try:
+ duration, format = pipe.query_duration(gst.FORMAT_TIME)
+ except:
+ duration = gst.CLOCK_TIME_NONE
+
+ return (position, duration)
+
+ def _onMuxedVideoMessageCb(self, bus, message, pipe):
+ if message.type != gst.MESSAGE_EOS:
+ return True
+
+ gobject.source_remove(self._video_transcode_handler)
+ self._video_transcode_handler = None
+ gobject.source_remove(self._transcode_id)
+ self._transcode_id = None
+ pipe.set_state(gst.STATE_NULL)
+ pipe.get_bus().remove_signal_watch()
+ pipe.get_bus().disable_sync_message_emission()
+
+ wavFilepath = os.path.join(Instance.instancePath, "output.wav")
+ oggFilepath = os.path.join(Instance.instancePath, "output.ogg") #ogv
+ muxFilepath = os.path.join(Instance.instancePath, "mux.ogg") #ogv
+ os.remove( wavFilepath )
+ os.remove( oggFilepath )
+ self.model.save_video(muxFilepath, self.thumbBuf)
+ return False
+
+ def _onMuxedAudioMessageCb(self, bus, message, pipe):
+ if message.type != gst.MESSAGE_EOS:
+ return True
+
+ gobject.source_remove(self._audio_transcode_handler)
+ self._audio_transcode_handler = None
+ gobject.source_remove(self._transcode_id)
+ self._transcode_id = None
+ pipe.set_state(gst.STATE_NULL)
+ pipe.get_bus().remove_signal_watch()
+ pipe.get_bus().disable_sync_message_emission()
+
+ wavFilepath = os.path.join(Instance.instancePath, "output.wav")
+ oggFilepath = os.path.join(Instance.instancePath, "output.ogg")
+ os.remove( wavFilepath )
+ self.model.save_audio(oggFilepath, self._audio_pixbuf)
+ return False
+
+ def _bus_message_handler(self, bus, message):
+ t = message.type
+ if t == gst.MESSAGE_EOS:
+ if self._eos_cb:
+ cb = self._eos_cb
+ self._eos_cb = None
+ cb()
+ elif t == gst.MESSAGE_ERROR:
+ #todo: if we come out of suspend/resume with errors, then get us back up and running...
+ #todo: handle "No space left on the resource.gstfilesink.c"
+ #err, debug = message.parse_error()
+ pass
+
+ def abandonMedia(self):
+ self.stop()
+
+ if self._audio_transcode_handler:
+ gobject.source_remove(self._audio_transcode_handler)
+ self._audio_transcode_handler = None
+ if self._transcode_id:
+ gobject.source_remove(self._transcode_id)
+ self._transcode_id = None
+ if self._video_transcode_handler:
+ gobject.source_remove(self._video_transcode_handler)
+ self._video_transcode_handler = None
+
+ wav_path = os.path.join(Instance.instancePath, "output.wav")
+ if os.path.exists(wav_path):
+ os.remove(wav_path)
+ ogg_path = os.path.join(Instance.instancePath, "output.ogg") #ogv
+ if os.path.exists(ogg_path):
+ os.remove(ogg_path)
+ mux_path = os.path.join(Instance.instancePath, "mux.ogg") #ogv
+ if os.path.exists(mux_path):
+ os.remove(mux_path)
+
diff --git a/plugins/camera_sensor/icons/sensoroff.svg b/plugins/camera_sensor/icons/sensoroff.svg
new file mode 100644
index 0000000..0a16670
--- /dev/null
+++ b/plugins/camera_sensor/icons/sensoroff.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata15"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs13" />
+<rect
+ width="42.763924"
+ height="42.763924"
+ x="6.1180382"
+ y="6.1180382"
+ id="rect2986"
+ style="fill:#282828;fill-opacity:1;stroke:#282828;stroke-width:2.23607516;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><g
+ transform="matrix(0.87078705,0,0,0.87078705,3.2821055,2.9298726)"
+ id="network-wired_1_"
+ style="display:block">
+ <g
+ id="network-wired">
+ <line
+ id="line3076"
+ y2="23.993"
+ y1="32.438999"
+ x2="16.966999"
+ x1="16.966999"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round" />
+ <line
+ id="line3078"
+ y2="28.215"
+ y1="28.215"
+ x2="34.938"
+ x1="29.636999"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round" />
+
+ <rect
+ width="9.7939997"
+ height="7.599"
+ x="42.157001"
+ y="24.312"
+ id="rect3080"
+ style="fill:#ffffff;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round" />
+ <path
+ d="m 16.967,23.993 c 0,-2.334 -1.892,-4.224 -4.224,-4.224 -2.332,0 -4.223,1.889 -4.223,4.224"
+ id="path3082"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 25.413,32.439 c 0,2.334 -1.891,4.224 -4.224,4.224 -2.332,0 -4.223,-1.89 -4.223,-4.224"
+ id="path3084"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 25.413,32.439 c 0,-2.332 1.893,-4.226 4.224,-4.226"
+ id="path3086"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 8.52,23.993 c 0,2.332 -1.892,4.222 -4.223,4.222"
+ id="path3088"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+
+ <rect
+ width="14.477"
+ height="11.35"
+ x="31.945"
+ y="22.438"
+ id="rect3090"
+ style="fill:#ffffff;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/plugins/camera_sensor/icons/sensoron.svg b/plugins/camera_sensor/icons/sensoron.svg
new file mode 100644
index 0000000..d756860
--- /dev/null
+++ b/plugins/camera_sensor/icons/sensoron.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata15"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs13" /><rect
+ width="55"
+ height="55"
+ x="0"
+ y="0"
+ id="rect3269"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:nonzero;stroke:none" /><g
+ transform="translate(0.27777716,18.796296)"
+ id="g4054"><line
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-opacity:1"
+ x1="17.778971"
+ x2="17.778971"
+ y1="12.381037"
+ y2="5.0263696"
+ id="line3076" /><line
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-opacity:1"
+ x1="28.811842"
+ x2="33.427887"
+ y1="8.7028332"
+ y2="8.7028332"
+ id="line3078" /><rect
+ width="8.5284882"
+ height="6.6171107"
+ x="39.7141"
+ y="5.3041511"
+ id="rect3080"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:1.95927083;stroke-linecap:round;stroke-opacity:1" /><path
+ d="m 17.778971,5.0263697 c 0,-2.032417 -1.647529,-3.6782045 -3.678204,-3.6782045 -2.030675,0 -3.677334,1.6449167 -3.677334,3.6782045"
+ id="path3082"
+ style="fill:none;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 25.133639,12.381037 c 0,2.032417 -1.646658,3.678205 -3.678205,3.678205 -2.030675,0 -3.677333,-1.645788 -3.677333,-3.678205"
+ id="path3084"
+ style="fill:none;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 25.133639,12.381037 c 0,-2.030675 1.6484,-3.679946 3.678204,-3.679946"
+ id="path3086"
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 10.423433,5.0263697 c 0,2.0306754 -1.6475288,3.6764629 -3.6773334,3.6764629"
+ id="path3088"
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><rect
+ width="12.606384"
+ height="9.8834333"
+ x="30.821619"
+ y="3.6722956"
+ id="rect3090"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/plugins/camera_sensor/images/camera1off.svg b/plugins/camera_sensor/images/camera1off.svg
new file mode 100644
index 0000000..2abf8d2
--- /dev/null
+++ b/plugins/camera_sensor/images/camera1off.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata29"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs27">
+
+
+
+
+
+
+
+
+
+ </defs><g
+ transform="matrix(1.0741382,0,0,1.0741382,-2.9531883,-3.3782508)"
+ id="media-flash-usb"
+ style="stroke-width:2.20384002;stroke-miterlimit:4;stroke-dasharray:none;display:block"><g
+ id="g3115"
+ style="stroke-width:2.20384002;stroke-miterlimit:4;stroke-dasharray:none;display:inline"><path
+ d="m 15.885,16.723 c -6.641,0 -12.023,5.385 -12.023,12.025 0,6.639 5.383,12.023 12.023,12.023 h 0.014 27.259 V 16.723 H 15.899"
+ id="path3117"
+ style="fill:#ffffff;stroke:#010101;stroke-width:2.20384002;stroke-miterlimit:4;stroke-dasharray:none" /><rect
+ width="9.1400003"
+ height="14.182"
+ x="43.722"
+ y="21.731001"
+ id="rect3119"
+ style="fill:#ffffff;stroke:#010101;stroke-width:2.20384002;stroke-miterlimit:4;stroke-dasharray:none" /></g></g><g
+ transform="matrix(0.57244766,0,0,0.57244766,11.646526,13.522259)"
+ id="camera-external"
+ style="display:block"><g
+ id="g5"
+ style="display:inline"><g
+ id="g7"><polygon
+ points="48.209,38.973 48.209,14.52 38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 "
+ id="polygon9"
+ style="fill:#ffffff" /></g><g
+ id="g11"><polygon
+ points="48.209,38.973 48.209,14.52 38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 "
+ id="polygon13"
+ style="fill:none;stroke:#010101;stroke-width:3.5" /></g></g><path
+ d="m 20.601,26.441 c 0,3.67 2.979,6.648 6.65,6.648 3.667,0 6.646,-2.979 6.646,-6.648 0,-3.668 -2.979,-6.652 -6.646,-6.652 -3.67,0 -6.65,2.986 -6.65,6.652 z"
+ id="path15"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5;display:inline" /><rect
+ width="6.2870002"
+ height="4.1929998"
+ x="38.098999"
+ y="18.417999"
+ id="rect17"
+ style="fill:#010101;display:inline" /></g>
+</svg> \ No newline at end of file
diff --git a/plugins/camera_sensor/images/camera1small.svg b/plugins/camera_sensor/images/camera1small.svg
new file mode 100644
index 0000000..4d7f756
--- /dev/null
+++ b/plugins/camera_sensor/images/camera1small.svg
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="29"
+ height="15"
+ viewBox="0 0 29 15"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata29"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs27">
+
+
+
+
+
+
+
+
+
+ </defs><g
+ transform="matrix(0.49560305,0,0,0.51274162,-0.00570885,-0.18882692)"
+ id="g2994"><g
+ transform="matrix(1.142777,0,0,1.142777,-3.1426369,-17.855891)"
+ id="media-flash-usb"
+ style="stroke-width:2.20384002;stroke-miterlimit:4;stroke-dasharray:none;display:block"><g
+ id="g3115"
+ style="stroke-width:2.20384002;stroke-miterlimit:4;stroke-dasharray:none;display:inline"><path
+ d="m 15.885,16.723 c -6.641,0 -12.023,5.385 -12.023,12.025 0,6.639 5.383,12.023 12.023,12.023 h 0.014 27.259 V 16.723 H 15.899"
+ id="path3117"
+ style="fill:#ffffff;stroke:#010101;stroke-width:2.20384002;stroke-miterlimit:4;stroke-dasharray:none" /><rect
+ width="9.1400003"
+ height="14.182"
+ x="43.722"
+ y="21.731001"
+ id="rect3119"
+ style="fill:#ffffff;stroke:#010101;stroke-width:2.20384002;stroke-miterlimit:4;stroke-dasharray:none" /></g></g><g
+ transform="matrix(0.57244766,0,0,0.57244766,11.646526,1.105918)"
+ id="camera-external"
+ style="display:block"><g
+ id="g5"
+ style="display:inline"><g
+ id="g7"><polygon
+ points="34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 48.209,14.52 38.532,14.52 "
+ id="polygon9"
+ style="fill:#ffffff" /></g><g
+ id="g11"><polygon
+ points="34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 48.209,14.52 38.532,14.52 "
+ id="polygon13"
+ style="fill:none;stroke:#010101;stroke-width:3.5" /></g></g><path
+ d="m 20.601,26.441 c 0,3.67 2.979,6.648 6.65,6.648 3.667,0 6.646,-2.979 6.646,-6.648 0,-3.668 -2.979,-6.652 -6.646,-6.652 -3.67,0 -6.65,2.986 -6.65,6.652 z"
+ id="path15"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5;display:inline" /><rect
+ width="6.2870002"
+ height="4.1929998"
+ x="38.098999"
+ y="18.417999"
+ id="rect17"
+ style="fill:#010101;display:inline" /></g></g>
+</svg> \ No newline at end of file
diff --git a/plugins/camera_sensor/images/cameraoff.svg b/plugins/camera_sensor/images/cameraoff.svg
new file mode 100644
index 0000000..54d9c86
--- /dev/null
+++ b/plugins/camera_sensor/images/cameraoff.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="camera-external">
+ <g display="inline">
+ <g>
+ <polygon fill="&fill_color;" points="38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 48.209,14.52 "/>
+ </g>
+ <g>
+ <polygon fill="none" points="38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 48.209,14.52 " stroke="&stroke_color;" stroke-width="3.5"/>
+ </g>
+ </g>
+ <path d="M20.601,26.441c0,3.67,2.979,6.648,6.65,6.648 c3.667,0,6.646-2.979,6.646-6.648c0-3.668-2.979-6.652-6.646-6.652C23.581,19.789,20.601,22.775,20.601,26.441z" display="inline" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/>
+ <rect display="inline" fill="&stroke_color;" height="4.193" width="6.287" x="38.099" y="18.418"/>
+</g></svg> \ No newline at end of file
diff --git a/plugins/camera_sensor/images/camerasmall.svg b/plugins/camera_sensor/images/camerasmall.svg
new file mode 100644
index 0000000..690e940
--- /dev/null
+++ b/plugins/camera_sensor/images/camerasmall.svg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="26"
+ height="18.668091"
+ viewBox="0 0 26 18.668091"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata29"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs27">
+
+
+
+
+
+
+
+
+
+ </defs><g
+ transform="matrix(0.57244766,0,0,0.57244766,-2.5989125,-4.6436954)"
+ id="camera-external"
+ style="display:block"><g
+ id="g5"
+ style="display:inline"><g
+ id="g7"><polygon
+ points="48.209,14.52 38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 "
+ id="polygon9"
+ style="fill:#ffffff" /></g><g
+ id="g11"><polygon
+ points="48.209,14.52 38.532,14.52 34.904,9.862 18.783,9.862 15.155,14.52 6.29,14.52 6.29,38.973 48.209,38.973 "
+ id="polygon13"
+ style="fill:none;stroke:#010101;stroke-width:3.5" /></g></g><path
+ d="m 20.601,26.441 c 0,3.67 2.979,6.648 6.65,6.648 3.667,0 6.646,-2.979 6.646,-6.648 0,-3.668 -2.979,-6.652 -6.646,-6.652 -3.67,0 -6.65,2.986 -6.65,6.652 z"
+ id="path15"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5;display:inline" /><rect
+ width="6.2870002"
+ height="4.1929998"
+ x="38.098999"
+ y="18.417999"
+ id="rect17"
+ style="fill:#010101;display:inline" /></g></svg> \ No newline at end of file
diff --git a/plugins/camera_sensor/instance.py b/plugins/camera_sensor/instance.py
new file mode 100644
index 0000000..bcee466
--- /dev/null
+++ b/plugins/camera_sensor/instance.py
@@ -0,0 +1,22 @@
+import os
+
+from sugar import profile
+from sugar import util
+
+class Instance:
+ key = profile.get_pubkey()
+ keyHash = util.sha_data(key)
+
+ keyHashPrintable = util.printable_hash(keyHash)
+
+ instancePath = None
+
+ def __init__(self, ca):
+ self.__class__.instancePath = os.path.join(ca.get_activity_root(), "instance")
+ recreateTmp()
+
+
+def recreateTmp():
+ if (not os.path.exists(Instance.instancePath)):
+ os.makedirs(Instance.instancePath)
+
diff --git a/plugins/camera_sensor/tacamera.py b/plugins/camera_sensor/tacamera.py
new file mode 100644
index 0000000..fc0804d
--- /dev/null
+++ b/plugins/camera_sensor/tacamera.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2010, Walter Bender
+#Copyright (c) 2010, Tony Forster
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import gst, time
+import gobject
+
+from TurtleArt.tautils import debug_output
+
+
+class Camera():
+ ''' Sets up a pipe from the camera to a pixbuf and emits a signal
+ when the image is ready. '''
+
+ def __init__(self, device='/dev/video0'):
+ ''' Prepare camera pipeline to pixbuf and signal watch '''
+ self.pipe = gst.Pipeline('pipeline')
+ v4l2src = gst.element_factory_make('v4l2src', None)
+ v4l2src.props.device = device
+ self.pipe.add(v4l2src)
+ ffmpegcolorspace = gst.element_factory_make('ffmpegcolorspace', None)
+ self.pipe.add(ffmpegcolorspace)
+ gdkpixbufsink = gst.element_factory_make('gdkpixbufsink', None)
+ self.pipe.add(gdkpixbufsink)
+ gst.element_link_many(v4l2src, ffmpegcolorspace, gdkpixbufsink)
+ if self.pipe is not None:
+ self.bus = self.pipe.get_bus()
+ self.bus.add_signal_watch()
+ self.bus.connect('message', self._on_message)
+ status = True
+ else:
+ status = False
+
+ def _on_message(self, bus, message):
+ ''' We get a message if a pixbuf is available '''
+ if message.structure is not None:
+ if message.structure.get_name() == 'pixbuf':
+ self.pixbuf = message.structure['pixbuf']
+ self.image_ready = True
+
+ def start_camera_input(self):
+ ''' Start grabbing '''
+ self.pixbuf = None
+ self.image_ready = False
+ self.pipe.set_state(gst.STATE_PLAYING)
+ while not self.image_ready:
+ self.bus.poll(gst.MESSAGE_ANY, -1)
+
+ def stop_camera_input(self):
+ ''' Stop grabbing '''
+ self.pipe.set_state(gst.STATE_NULL)
diff --git a/plugins/camera_sensor/utils.py b/plugins/camera_sensor/utils.py
new file mode 100644
index 0000000..701e45f
--- /dev/null
+++ b/plugins/camera_sensor/utils.py
@@ -0,0 +1,63 @@
+import base64
+import rsvg
+import re
+import os
+import gtk
+import time
+from time import strftime
+
+import constants
+
+
+def getStringEncodedFromPixbuf(pixbuf):
+ data = [""]
+ pixbuf.save_to_callback(_saveDataToBufferCb, "png", {}, data)
+ return base64.b64encode(str(data[0]))
+
+
+def getStringFromPixbuf(pixbuf):
+ data = [""]
+ pixbuf.save_to_callback(_saveDataToBufferCb, "png", {}, data)
+ return str(data[0])
+
+
+def _saveDataToBufferCb(buf, data):
+ data[0] += buf
+ return True
+
+
+def getPixbufFromString(str):
+ pbl = gtk.gdk.PixbufLoader()
+ data = base64.b64decode( str )
+ pbl.write(data)
+ pbl.close()
+ return pbl.get_pixbuf()
+
+
+def load_colored_svg(filename, stroke, fill):
+ path = os.path.join(constants.GFX_PATH, filename)
+ data = open(path, 'r').read()
+
+ entity = '<!ENTITY fill_color "%s">' % fill
+ data = re.sub('<!ENTITY fill_color .*>', entity, data)
+
+ entity = '<!ENTITY stroke_color "%s">' % stroke
+ data = re.sub('<!ENTITY stroke_color .*>', entity, data)
+
+ return rsvg.Handle(data=data).get_pixbuf()
+
+def getUniqueFilepath( path, i ):
+ pathOb = os.path.abspath( path )
+ newPath = os.path.join( os.path.dirname(pathOb), str( str(i) + os.path.basename(pathOb) ) )
+ if (os.path.exists(newPath)):
+ i = i + 1
+ return getUniqueFilepath( pathOb, i )
+ else:
+ return os.path.abspath( newPath )
+
+def generate_thumbnail(pixbuf):
+ return pixbuf.scale_simple(108, 81, gtk.gdk.INTERP_BILINEAR)
+
+def getDateString( when ):
+ return strftime( "%c", time.localtime(when) )
+
diff --git a/plugins/camera_sensor/v4l2.py b/plugins/camera_sensor/v4l2.py
new file mode 100644
index 0000000..9c052fd
--- /dev/null
+++ b/plugins/camera_sensor/v4l2.py
@@ -0,0 +1,1914 @@
+# Python bindings for the v4l2 userspace api
+
+# Copyright (C) 1999-2009 the contributors
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# Alternatively you can redistribute this file under the terms of the
+# BSD license as stated below:
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# 3. The names of its contributors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+Python bindings for the v4l2 userspace api in Linux 2.6.34
+"""
+
+# see linux/videodev2.h
+
+import ctypes
+
+
+_IOC_NRBITS = 8
+_IOC_TYPEBITS = 8
+_IOC_SIZEBITS = 14
+_IOC_DIRBITS = 2
+
+_IOC_NRSHIFT = 0
+_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
+_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
+_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
+
+_IOC_NONE = 0
+_IOC_WRITE = 1
+_IOC_READ = 2
+
+
+def _IOC(dir_, type_, nr, size):
+ return (
+ ctypes.c_int32(dir_ << _IOC_DIRSHIFT).value |
+ ctypes.c_int32(ord(type_) << _IOC_TYPESHIFT).value |
+ ctypes.c_int32(nr << _IOC_NRSHIFT).value |
+ ctypes.c_int32(size << _IOC_SIZESHIFT).value)
+
+
+def _IOC_TYPECHECK(t):
+ return ctypes.sizeof(t)
+
+
+def _IO(type_, nr):
+ return _IOC(_IOC_NONE, type_, nr, 0)
+
+
+def _IOW(type_, nr, size):
+ return _IOC(_IOC_WRITE, type_, nr, _IOC_TYPECHECK(size))
+
+
+def _IOR(type_, nr, size):
+ return _IOC(_IOC_READ, type_, nr, _IOC_TYPECHECK(size))
+
+
+def _IOWR(type_, nr, size):
+ return _IOC(_IOC_READ | _IOC_WRITE, type_, nr, _IOC_TYPECHECK(size))
+
+
+#
+# type alias
+#
+
+enum = ctypes.c_uint
+c_int = ctypes.c_int
+
+
+#
+# time
+#
+
+class timeval(ctypes.Structure):
+ _fields_ = [
+ ('secs', ctypes.c_long),
+ ('usecs', ctypes.c_long),
+ ]
+
+
+#
+# v4l2
+#
+
+
+VIDEO_MAX_FRAME = 32
+
+
+VID_TYPE_CAPTURE = 1
+VID_TYPE_TUNER = 2
+VID_TYPE_TELETEXT = 4
+VID_TYPE_OVERLAY = 8
+VID_TYPE_CHROMAKEY = 16
+VID_TYPE_CLIPPING = 32
+VID_TYPE_FRAMERAM = 64
+VID_TYPE_SCALES = 128
+VID_TYPE_MONOCHROME = 256
+VID_TYPE_SUBCAPTURE = 512
+VID_TYPE_MPEG_DECODER = 1024
+VID_TYPE_MPEG_ENCODER = 2048
+VID_TYPE_MJPEG_DECODER = 4096
+VID_TYPE_MJPEG_ENCODER = 8192
+
+
+def v4l2_fourcc(a, b, c, d):
+ return ord(a) | (ord(b) << 8) | (ord(c) << 16) | (ord(d) << 24)
+
+
+v4l2_field = enum
+(
+ V4L2_FIELD_ANY,
+ V4L2_FIELD_NONE,
+ V4L2_FIELD_TOP,
+ V4L2_FIELD_BOTTOM,
+ V4L2_FIELD_INTERLACED,
+ V4L2_FIELD_SEQ_TB,
+ V4L2_FIELD_SEQ_BT,
+ V4L2_FIELD_ALTERNATE,
+ V4L2_FIELD_INTERLACED_TB,
+ V4L2_FIELD_INTERLACED_BT,
+) = range(10)
+
+
+def V4L2_FIELD_HAS_TOP(field):
+ return (
+ field == V4L2_FIELD_TOP or
+ field == V4L2_FIELD_INTERLACED or
+ field == V4L2_FIELD_INTERLACED_TB or
+ field == V4L2_FIELD_INTERLACED_BT or
+ field == V4L2_FIELD_SEQ_TB or
+ field == V4L2_FIELD_SEQ_BT)
+
+
+def V4L2_FIELD_HAS_BOTTOM(field):
+ return (
+ field == V4L2_FIELD_BOTTOM or
+ field == V4L2_FIELD_INTERLACED or
+ field == V4L2_FIELD_INTERLACED_TB or
+ field == V4L2_FIELD_INTERLACED_BT or
+ field == V4L2_FIELD_SEQ_TB or
+ field == V4L2_FIELD_SEQ_BT)
+
+
+def V4L2_FIELD_HAS_BOTH(field):
+ return (
+ field == V4L2_FIELD_INTERLACED or
+ field == V4L2_FIELD_INTERLACED_TB or
+ field == V4L2_FIELD_INTERLACED_BT or
+ field == V4L2_FIELD_SEQ_TB or
+ field == V4L2_FIELD_SEQ_BT)
+
+
+v4l2_buf_type = enum
+(
+ V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT,
+ V4L2_BUF_TYPE_VIDEO_OVERLAY,
+ V4L2_BUF_TYPE_VBI_CAPTURE,
+ V4L2_BUF_TYPE_VBI_OUTPUT,
+ V4L2_BUF_TYPE_SLICED_VBI_CAPTURE,
+ V4L2_BUF_TYPE_SLICED_VBI_OUTPUT,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY,
+ V4L2_BUF_TYPE_PRIVATE,
+) = range(1, 9) + [0x80]
+
+
+v4l2_ctrl_type = enum
+(
+ V4L2_CTRL_TYPE_INTEGER,
+ V4L2_CTRL_TYPE_BOOLEAN,
+ V4L2_CTRL_TYPE_MENU,
+ V4L2_CTRL_TYPE_BUTTON,
+ V4L2_CTRL_TYPE_INTEGER64,
+ V4L2_CTRL_TYPE_CTRL_CLASS,
+ V4L2_CTRL_TYPE_STRING,
+) = range(1, 8)
+
+
+v4l2_tuner_type = enum
+(
+ V4L2_TUNER_RADIO,
+ V4L2_TUNER_ANALOG_TV,
+ V4L2_TUNER_DIGITAL_TV,
+) = range(1, 4)
+
+
+v4l2_memory = enum
+(
+ V4L2_MEMORY_MMAP,
+ V4L2_MEMORY_USERPTR,
+ V4L2_MEMORY_OVERLAY,
+) = range(1, 4)
+
+
+v4l2_colorspace = enum
+(
+ V4L2_COLORSPACE_SMPTE170M,
+ V4L2_COLORSPACE_SMPTE240M,
+ V4L2_COLORSPACE_REC709,
+ V4L2_COLORSPACE_BT878,
+ V4L2_COLORSPACE_470_SYSTEM_M,
+ V4L2_COLORSPACE_470_SYSTEM_BG,
+ V4L2_COLORSPACE_JPEG,
+ V4L2_COLORSPACE_SRGB,
+) = range(1, 9)
+
+
+v4l2_priority = enum
+(
+ V4L2_PRIORITY_UNSET,
+ V4L2_PRIORITY_BACKGROUND,
+ V4L2_PRIORITY_INTERACTIVE,
+ V4L2_PRIORITY_RECORD,
+ V4L2_PRIORITY_DEFAULT,
+) = range(0, 4) + [2]
+
+
+class v4l2_rect(ctypes.Structure):
+ _fields_ = [
+ ('left', ctypes.c_int32),
+ ('top', ctypes.c_int32),
+ ('width', ctypes.c_int32),
+ ('height', ctypes.c_int32),
+ ]
+
+
+class v4l2_fract(ctypes.Structure):
+ _fields_ = [
+ ('numerator', ctypes.c_uint32),
+ ('denominator', ctypes.c_uint32),
+ ]
+
+
+#
+# Driver capabilities
+#
+
+class v4l2_capability(ctypes.Structure):
+ _fields_ = [
+ ('driver', ctypes.c_char * 16),
+ ('card', ctypes.c_char * 32),
+ ('bus_info', ctypes.c_char * 32),
+ ('version', ctypes.c_uint32),
+ ('capabilities', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 4),
+ ]
+
+
+#
+# Values for 'capabilities' field
+#
+
+V4L2_CAP_VIDEO_CAPTURE = 0x00000001
+V4L2_CAP_VIDEO_OUTPUT = 0x00000002
+V4L2_CAP_VIDEO_OVERLAY = 0x00000004
+V4L2_CAP_VBI_CAPTURE = 0x00000010
+V4L2_CAP_VBI_OUTPUT = 0x00000020
+V4L2_CAP_SLICED_VBI_CAPTURE = 0x00000040
+V4L2_CAP_SLICED_VBI_OUTPUT = 0x00000080
+V4L2_CAP_RDS_CAPTURE = 0x00000100
+V4L2_CAP_VIDEO_OUTPUT_OVERLAY = 0x00000200
+V4L2_CAP_HW_FREQ_SEEK = 0x00000400
+V4L2_CAP_RDS_OUTPUT = 0x00000800
+
+V4L2_CAP_TUNER = 0x00010000
+V4L2_CAP_AUDIO = 0x00020000
+V4L2_CAP_RADIO = 0x00040000
+V4L2_CAP_MODULATOR = 0x00080000
+
+V4L2_CAP_READWRITE = 0x01000000
+V4L2_CAP_ASYNCIO = 0x02000000
+V4L2_CAP_STREAMING = 0x04000000
+
+
+#
+# Video image format
+#
+
+class v4l2_pix_format(ctypes.Structure):
+ _fields_ = [
+ ('width', ctypes.c_uint32),
+ ('height', ctypes.c_uint32),
+ ('pixelformat', ctypes.c_uint32),
+ ('field', v4l2_field),
+ ('bytesperline', ctypes.c_uint32),
+ ('sizeimage', ctypes.c_uint32),
+ ('colorspace', v4l2_colorspace),
+ ('priv', ctypes.c_uint32),
+ ]
+
+# RGB formats
+V4L2_PIX_FMT_RGB332 = v4l2_fourcc('R', 'G', 'B', '1')
+V4L2_PIX_FMT_RGB444 = v4l2_fourcc('R', '4', '4', '4')
+V4L2_PIX_FMT_RGB555 = v4l2_fourcc('R', 'G', 'B', 'O')
+V4L2_PIX_FMT_RGB565 = v4l2_fourcc('R', 'G', 'B', 'P')
+V4L2_PIX_FMT_RGB555X = v4l2_fourcc('R', 'G', 'B', 'Q')
+V4L2_PIX_FMT_RGB565X = v4l2_fourcc('R', 'G', 'B', 'R')
+V4L2_PIX_FMT_BGR24 = v4l2_fourcc('B', 'G', 'R', '3')
+V4L2_PIX_FMT_RGB24 = v4l2_fourcc('R', 'G', 'B', '3')
+V4L2_PIX_FMT_BGR32 = v4l2_fourcc('B', 'G', 'R', '4')
+V4L2_PIX_FMT_RGB32 = v4l2_fourcc('R', 'G', 'B', '4')
+
+# Grey formats
+V4L2_PIX_FMT_GREY = v4l2_fourcc('G', 'R', 'E', 'Y')
+V4L2_PIX_FMT_Y10 = v4l2_fourcc('Y', '1', '0', ' ')
+V4L2_PIX_FMT_Y16 = v4l2_fourcc('Y', '1', '6', ' ')
+
+# Palette formats
+V4L2_PIX_FMT_PAL8 = v4l2_fourcc('P', 'A', 'L', '8')
+
+# Luminance+Chrominance formats
+V4L2_PIX_FMT_YVU410 = v4l2_fourcc('Y', 'V', 'U', '9')
+V4L2_PIX_FMT_YVU420 = v4l2_fourcc('Y', 'V', '1', '2')
+V4L2_PIX_FMT_YUYV = v4l2_fourcc('Y', 'U', 'Y', 'V')
+V4L2_PIX_FMT_YYUV = v4l2_fourcc('Y', 'Y', 'U', 'V')
+V4L2_PIX_FMT_YVYU = v4l2_fourcc('Y', 'V', 'Y', 'U')
+V4L2_PIX_FMT_UYVY = v4l2_fourcc('U', 'Y', 'V', 'Y')
+V4L2_PIX_FMT_VYUY = v4l2_fourcc('V', 'Y', 'U', 'Y')
+V4L2_PIX_FMT_YUV422P = v4l2_fourcc('4', '2', '2', 'P')
+V4L2_PIX_FMT_YUV411P = v4l2_fourcc('4', '1', '1', 'P')
+V4L2_PIX_FMT_Y41P = v4l2_fourcc('Y', '4', '1', 'P')
+V4L2_PIX_FMT_YUV444 = v4l2_fourcc('Y', '4', '4', '4')
+V4L2_PIX_FMT_YUV555 = v4l2_fourcc('Y', 'U', 'V', 'O')
+V4L2_PIX_FMT_YUV565 = v4l2_fourcc('Y', 'U', 'V', 'P')
+V4L2_PIX_FMT_YUV32 = v4l2_fourcc('Y', 'U', 'V', '4')
+V4L2_PIX_FMT_YUV410 = v4l2_fourcc('Y', 'U', 'V', '9')
+V4L2_PIX_FMT_YUV420 = v4l2_fourcc('Y', 'U', '1', '2')
+V4L2_PIX_FMT_HI240 = v4l2_fourcc('H', 'I', '2', '4')
+V4L2_PIX_FMT_HM12 = v4l2_fourcc('H', 'M', '1', '2')
+
+# two planes -- one Y, one Cr + Cb interleaved
+V4L2_PIX_FMT_NV12 = v4l2_fourcc('N', 'V', '1', '2')
+V4L2_PIX_FMT_NV21 = v4l2_fourcc('N', 'V', '2', '1')
+V4L2_PIX_FMT_NV16 = v4l2_fourcc('N', 'V', '1', '6')
+V4L2_PIX_FMT_NV61 = v4l2_fourcc('N', 'V', '6', '1')
+
+# Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm
+V4L2_PIX_FMT_SBGGR8 = v4l2_fourcc('B', 'A', '8', '1')
+V4L2_PIX_FMT_SGBRG8 = v4l2_fourcc('G', 'B', 'R', 'G')
+V4L2_PIX_FMT_SGRBG8 = v4l2_fourcc('G', 'R', 'B', 'G')
+V4L2_PIX_FMT_SRGGB8 = v4l2_fourcc('R', 'G', 'G', 'B')
+V4L2_PIX_FMT_SBGGR10 = v4l2_fourcc('B', 'G', '1', '0')
+V4L2_PIX_FMT_SGBRG10 = v4l2_fourcc('G', 'B', '1', '0')
+V4L2_PIX_FMT_SGRBG10 = v4l2_fourcc('B', 'A', '1', '0')
+V4L2_PIX_FMT_SRGGB10 = v4l2_fourcc('R', 'G', '1', '0')
+V4L2_PIX_FMT_SGRBG10DPCM8 = v4l2_fourcc('B', 'D', '1', '0')
+V4L2_PIX_FMT_SBGGR16 = v4l2_fourcc('B', 'Y', 'R', '2')
+
+# compressed formats
+V4L2_PIX_FMT_MJPEG = v4l2_fourcc('M', 'J', 'P', 'G')
+V4L2_PIX_FMT_JPEG = v4l2_fourcc('J', 'P', 'E', 'G')
+V4L2_PIX_FMT_DV = v4l2_fourcc('d', 'v', 's', 'd')
+V4L2_PIX_FMT_MPEG = v4l2_fourcc('M', 'P', 'E', 'G')
+
+# Vendor-specific formats
+V4L2_PIX_FMT_CPIA1 = v4l2_fourcc('C', 'P', 'I', 'A')
+V4L2_PIX_FMT_WNVA = v4l2_fourcc('W', 'N', 'V', 'A')
+V4L2_PIX_FMT_SN9C10X = v4l2_fourcc('S', '9', '1', '0')
+V4L2_PIX_FMT_SN9C20X_I420 = v4l2_fourcc('S', '9', '2', '0')
+V4L2_PIX_FMT_PWC1 = v4l2_fourcc('P', 'W', 'C', '1')
+V4L2_PIX_FMT_PWC2 = v4l2_fourcc('P', 'W', 'C', '2')
+V4L2_PIX_FMT_ET61X251 = v4l2_fourcc('E', '6', '2', '5')
+V4L2_PIX_FMT_SPCA501 = v4l2_fourcc('S', '5', '0', '1')
+V4L2_PIX_FMT_SPCA505 = v4l2_fourcc('S', '5', '0', '5')
+V4L2_PIX_FMT_SPCA508 = v4l2_fourcc('S', '5', '0', '8')
+V4L2_PIX_FMT_SPCA561 = v4l2_fourcc('S', '5', '6', '1')
+V4L2_PIX_FMT_PAC207 = v4l2_fourcc('P', '2', '0', '7')
+V4L2_PIX_FMT_MR97310A = v4l2_fourcc('M', '3', '1', '0')
+V4L2_PIX_FMT_SN9C2028 = v4l2_fourcc('S', 'O', 'N', 'X')
+V4L2_PIX_FMT_SQ905C = v4l2_fourcc('9', '0', '5', 'C')
+V4L2_PIX_FMT_PJPG = v4l2_fourcc('P', 'J', 'P', 'G')
+V4L2_PIX_FMT_OV511 = v4l2_fourcc('O', '5', '1', '1')
+V4L2_PIX_FMT_OV518 = v4l2_fourcc('O', '5', '1', '8')
+V4L2_PIX_FMT_STV0680 = v4l2_fourcc('S', '6', '8', '0')
+
+
+#
+# Format enumeration
+#
+
+class v4l2_fmtdesc(ctypes.Structure):
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('type', ctypes.c_int),
+ ('flags', ctypes.c_uint32),
+ ('description', ctypes.c_char * 32),
+ ('pixelformat', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 4),
+ ]
+
+V4L2_FMT_FLAG_COMPRESSED = 0x0001
+V4L2_FMT_FLAG_EMULATED = 0x0002
+
+
+#
+# Experimental frame size and frame rate enumeration
+#
+
+v4l2_frmsizetypes = enum
+(
+ V4L2_FRMSIZE_TYPE_DISCRETE,
+ V4L2_FRMSIZE_TYPE_CONTINUOUS,
+ V4L2_FRMSIZE_TYPE_STEPWISE,
+) = range(1, 4)
+
+
+class v4l2_frmsize_discrete(ctypes.Structure):
+ _fields_ = [
+ ('width', ctypes.c_uint32),
+ ('height', ctypes.c_uint32),
+ ]
+
+
+class v4l2_frmsize_stepwise(ctypes.Structure):
+ _fields_ = [
+ ('min_width', ctypes.c_uint32),
+ ('min_height', ctypes.c_uint32),
+ ('step_width', ctypes.c_uint32),
+ ('min_height', ctypes.c_uint32),
+ ('max_height', ctypes.c_uint32),
+ ('step_height', ctypes.c_uint32),
+ ]
+
+
+class v4l2_frmsizeenum(ctypes.Structure):
+ class _u(ctypes.Union):
+ _fields_ = [
+ ('discrete', v4l2_frmsize_discrete),
+ ('stepwise', v4l2_frmsize_stepwise),
+ ]
+
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('pixel_format', ctypes.c_uint32),
+ ('type', ctypes.c_uint32),
+ ('_u', _u),
+ ('reserved', ctypes.c_uint32 * 2)
+ ]
+
+ _anonymous_ = ('_u',)
+
+
+#
+# Frame rate enumeration
+#
+
+v4l2_frmivaltypes = enum
+(
+ V4L2_FRMIVAL_TYPE_DISCRETE,
+ V4L2_FRMIVAL_TYPE_CONTINUOUS,
+ V4L2_FRMIVAL_TYPE_STEPWISE,
+) = range(1, 4)
+
+
+class v4l2_frmival_stepwise(ctypes.Structure):
+ _fields_ = [
+ ('min', v4l2_fract),
+ ('max', v4l2_fract),
+ ('step', v4l2_fract),
+ ]
+
+
+class v4l2_frmivalenum(ctypes.Structure):
+ class _u(ctypes.Union):
+ _fields_ = [
+ ('discrete', v4l2_fract),
+ ('stepwise', v4l2_frmival_stepwise),
+ ]
+
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('pixel_format', ctypes.c_uint32),
+ ('width', ctypes.c_uint32),
+ ('height', ctypes.c_uint32),
+ ('type', ctypes.c_uint32),
+ ('_u', _u),
+ ('reserved', ctypes.c_uint32 * 2),
+ ]
+
+ _anonymous_ = ('_u',)
+
+
+#
+# Timecode
+#
+
+class v4l2_timecode(ctypes.Structure):
+ _fields_ = [
+ ('type', ctypes.c_uint32),
+ ('flags', ctypes.c_uint32),
+ ('frames', ctypes.c_uint8),
+ ('seconds', ctypes.c_uint8),
+ ('minutes', ctypes.c_uint8),
+ ('hours', ctypes.c_uint8),
+ ('userbits', ctypes.c_uint8 * 4),
+ ]
+
+
+V4L2_TC_TYPE_24FPS = 1
+V4L2_TC_TYPE_25FPS = 2
+V4L2_TC_TYPE_30FPS = 3
+V4L2_TC_TYPE_50FPS = 4
+V4L2_TC_TYPE_60FPS = 5
+
+V4L2_TC_FLAG_DROPFRAME = 0x0001
+V4L2_TC_FLAG_COLORFRAME = 0x0002
+V4L2_TC_USERBITS_field = 0x000C
+V4L2_TC_USERBITS_USERDEFINED = 0x0000
+V4L2_TC_USERBITS_8BITCHARS = 0x0008
+
+
+class v4l2_jpegcompression(ctypes.Structure):
+ _fields_ = [
+ ('quality', ctypes.c_int),
+ ('APPn', ctypes.c_int),
+ ('APP_len', ctypes.c_int),
+ ('APP_data', ctypes.c_char * 60),
+ ('COM_len', ctypes.c_int),
+ ('COM_data', ctypes.c_char * 60),
+ ('jpeg_markers', ctypes.c_uint32),
+ ]
+
+
+V4L2_JPEG_MARKER_DHT = 1 << 3
+V4L2_JPEG_MARKER_DQT = 1 << 4
+V4L2_JPEG_MARKER_DRI = 1 << 5
+V4L2_JPEG_MARKER_COM = 1 << 6
+V4L2_JPEG_MARKER_APP = 1 << 7
+
+
+#
+# Memory-mapping buffers
+#
+
+class v4l2_requestbuffers(ctypes.Structure):
+ _fields_ = [
+ ('count', ctypes.c_uint32),
+ ('type', v4l2_buf_type),
+ ('memory', v4l2_memory),
+ ('reserved', ctypes.c_uint32 * 2),
+ ]
+
+
+class v4l2_buffer(ctypes.Structure):
+ class _u(ctypes.Union):
+ _fields_ = [
+ ('offset', ctypes.c_uint32),
+ ('userptr', ctypes.c_ulong),
+ ]
+
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('type', v4l2_buf_type),
+ ('bytesused', ctypes.c_uint32),
+ ('flags', ctypes.c_uint32),
+ ('field', v4l2_field),
+ ('timestamp', timeval),
+ ('timecode', v4l2_timecode),
+ ('sequence', ctypes.c_uint32),
+ ('memory', v4l2_memory),
+ ('m', _u),
+ ('length', ctypes.c_uint32),
+ ('input', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32),
+ ]
+
+
+V4L2_BUF_FLAG_MAPPED = 0x0001
+V4L2_BUF_FLAG_QUEUED = 0x0002
+V4L2_BUF_FLAG_DONE = 0x0004
+V4L2_BUF_FLAG_KEYFRAME = 0x0008
+V4L2_BUF_FLAG_PFRAME = 0x0010
+V4L2_BUF_FLAG_BFRAME = 0x0020
+V4L2_BUF_FLAG_TIMECODE = 0x0100
+V4L2_BUF_FLAG_INPUT = 0x0200
+
+
+#
+# Overlay preview
+#
+
+class v4l2_framebuffer(ctypes.Structure):
+ _fields_ = [
+ ('capability', ctypes.c_uint32),
+ ('flags', ctypes.c_uint32),
+ ('base', ctypes.c_void_p),
+ ('fmt', v4l2_pix_format),
+ ]
+
+V4L2_FBUF_CAP_EXTERNOVERLAY = 0x0001
+V4L2_FBUF_CAP_CHROMAKEY = 0x0002
+V4L2_FBUF_CAP_LIST_CLIPPING = 0x0004
+V4L2_FBUF_CAP_BITMAP_CLIPPING = 0x0008
+V4L2_FBUF_CAP_LOCAL_ALPHA = 0x0010
+V4L2_FBUF_CAP_GLOBAL_ALPHA = 0x0020
+V4L2_FBUF_CAP_LOCAL_INV_ALPHA = 0x0040
+V4L2_FBUF_CAP_SRC_CHROMAKEY = 0x0080
+
+V4L2_FBUF_FLAG_PRIMARY = 0x0001
+V4L2_FBUF_FLAG_OVERLAY = 0x0002
+V4L2_FBUF_FLAG_CHROMAKEY = 0x0004
+V4L2_FBUF_FLAG_LOCAL_ALPHA = 0x0008
+V4L2_FBUF_FLAG_GLOBAL_ALPHA = 0x0010
+V4L2_FBUF_FLAG_LOCAL_INV_ALPHA = 0x0020
+V4L2_FBUF_FLAG_SRC_CHROMAKEY = 0x0040
+
+
+class v4l2_clip(ctypes.Structure):
+ pass
+v4l2_clip._fields_ = [
+ ('c', v4l2_rect),
+ ('next', ctypes.POINTER(v4l2_clip)),
+]
+
+
+class v4l2_window(ctypes.Structure):
+ _fields_ = [
+ ('w', v4l2_rect),
+ ('field', v4l2_field),
+ ('chromakey', ctypes.c_uint32),
+ ('clips', ctypes.POINTER(v4l2_clip)),
+ ('clipcount', ctypes.c_uint32),
+ ('bitmap', ctypes.c_void_p),
+ ('global_alpha', ctypes.c_uint8),
+ ]
+
+
+#
+# Capture parameters
+#
+
+class v4l2_captureparm(ctypes.Structure):
+ _fields_ = [
+ ('capability', ctypes.c_uint32),
+ ('capturemode', ctypes.c_uint32),
+ ('timeperframe', v4l2_fract),
+ ('extendedmode', ctypes.c_uint32),
+ ('readbuffers', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 4),
+ ]
+
+
+V4L2_MODE_HIGHQUALITY = 0x0001
+V4L2_CAP_TIMEPERFRAME = 0x1000
+
+
+class v4l2_outputparm(ctypes.Structure):
+ _fields_ = [
+ ('capability', ctypes.c_uint32),
+ ('outputmode', ctypes.c_uint32),
+ ('timeperframe', v4l2_fract),
+ ('extendedmode', ctypes.c_uint32),
+ ('writebuffers', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 4),
+ ]
+
+
+#
+# Input image cropping
+#
+
+class v4l2_cropcap(ctypes.Structure):
+ _fields_ = [
+ ('type', v4l2_buf_type),
+ ('bounds', v4l2_rect),
+ ('defrect', v4l2_rect),
+ ('pixelaspect', v4l2_fract),
+ ]
+
+
+class v4l2_crop(ctypes.Structure):
+ _fields_ = [
+ ('type', ctypes.c_int),
+ ('c', v4l2_rect),
+ ]
+
+
+#
+# Analog video standard
+#
+
+v4l2_std_id = ctypes.c_uint64
+
+
+V4L2_STD_PAL_B = 0x00000001
+V4L2_STD_PAL_B1 = 0x00000002
+V4L2_STD_PAL_G = 0x00000004
+V4L2_STD_PAL_H = 0x00000008
+V4L2_STD_PAL_I = 0x00000010
+V4L2_STD_PAL_D = 0x00000020
+V4L2_STD_PAL_D1 = 0x00000040
+V4L2_STD_PAL_K = 0x00000080
+
+V4L2_STD_PAL_M = 0x00000100
+V4L2_STD_PAL_N = 0x00000200
+V4L2_STD_PAL_Nc = 0x00000400
+V4L2_STD_PAL_60 = 0x00000800
+
+V4L2_STD_NTSC_M = 0x00001000
+V4L2_STD_NTSC_M_JP = 0x00002000
+V4L2_STD_NTSC_443 = 0x00004000
+V4L2_STD_NTSC_M_KR = 0x00008000
+
+V4L2_STD_SECAM_B = 0x00010000
+V4L2_STD_SECAM_D = 0x00020000
+V4L2_STD_SECAM_G = 0x00040000
+V4L2_STD_SECAM_H = 0x00080000
+V4L2_STD_SECAM_K = 0x00100000
+V4L2_STD_SECAM_K1 = 0x00200000
+V4L2_STD_SECAM_L = 0x00400000
+V4L2_STD_SECAM_LC = 0x00800000
+
+V4L2_STD_ATSC_8_VSB = 0x01000000
+V4L2_STD_ATSC_16_VSB = 0x02000000
+
+
+# some common needed stuff
+V4L2_STD_PAL_BG = (V4L2_STD_PAL_B | V4L2_STD_PAL_B1 | V4L2_STD_PAL_G)
+V4L2_STD_PAL_DK = (V4L2_STD_PAL_D | V4L2_STD_PAL_D1 | V4L2_STD_PAL_K)
+V4L2_STD_PAL = (V4L2_STD_PAL_BG | V4L2_STD_PAL_DK | V4L2_STD_PAL_H | V4L2_STD_PAL_I)
+V4L2_STD_NTSC = (V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP | V4L2_STD_NTSC_M_KR)
+V4L2_STD_SECAM_DK = (V4L2_STD_SECAM_D | V4L2_STD_SECAM_K | V4L2_STD_SECAM_K1)
+V4L2_STD_SECAM = (V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H | V4L2_STD_SECAM_DK | V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC)
+
+V4L2_STD_525_60 = (V4L2_STD_PAL_M | V4L2_STD_PAL_60 | V4L2_STD_NTSC | V4L2_STD_NTSC_443)
+V4L2_STD_625_50 = (V4L2_STD_PAL | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_SECAM)
+V4L2_STD_ATSC = (V4L2_STD_ATSC_8_VSB | V4L2_STD_ATSC_16_VSB)
+
+V4L2_STD_UNKNOWN = 0
+V4L2_STD_ALL = (V4L2_STD_525_60 | V4L2_STD_625_50)
+
+# some merged standards
+V4L2_STD_MN = (V4L2_STD_PAL_M | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_NTSC)
+V4L2_STD_B = (V4L2_STD_PAL_B | V4L2_STD_PAL_B1 | V4L2_STD_SECAM_B)
+V4L2_STD_GH = (V4L2_STD_PAL_G | V4L2_STD_PAL_H|V4L2_STD_SECAM_G | V4L2_STD_SECAM_H)
+V4L2_STD_DK = (V4L2_STD_PAL_DK | V4L2_STD_SECAM_DK)
+
+
+class v4l2_standard(ctypes.Structure):
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('id', v4l2_std_id),
+ ('name', ctypes.c_char * 24),
+ ('frameperiod', v4l2_fract),
+ ('framelines', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 4),
+ ]
+
+
+#
+# Video timings dv preset
+#
+
+class v4l2_dv_preset(ctypes.Structure):
+ _fields_ = [
+ ('preset', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 4)
+ ]
+
+
+#
+# DV preset enumeration
+#
+
+class v4l2_dv_enum_preset(ctypes.Structure):
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('preset', ctypes.c_uint32),
+ ('name', ctypes.c_char * 32),
+ ('width', ctypes.c_uint32),
+ ('height', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 4),
+ ]
+
+#
+# DV preset values
+#
+
+V4L2_DV_INVALID = 0
+V4L2_DV_480P59_94 = 1
+V4L2_DV_576P50 = 2
+V4L2_DV_720P24 = 3
+V4L2_DV_720P25 = 4
+V4L2_DV_720P30 = 5
+V4L2_DV_720P50 = 6
+V4L2_DV_720P59_94 = 7
+V4L2_DV_720P60 = 8
+V4L2_DV_1080I29_97 = 9
+V4L2_DV_1080I30 = 10
+V4L2_DV_1080I25 = 11
+V4L2_DV_1080I50 = 12
+V4L2_DV_1080I60 = 13
+V4L2_DV_1080P24 = 14
+V4L2_DV_1080P25 = 15
+V4L2_DV_1080P30 = 16
+V4L2_DV_1080P50 = 17
+V4L2_DV_1080P60 = 18
+
+
+#
+# DV BT timings
+#
+
+class v4l2_bt_timings(ctypes.Structure):
+ _fields_ = [
+ ('width', ctypes.c_uint32),
+ ('height', ctypes.c_uint32),
+ ('interlaced', ctypes.c_uint32),
+ ('polarities', ctypes.c_uint32),
+ ('pixelclock', ctypes.c_uint64),
+ ('hfrontporch', ctypes.c_uint32),
+ ('hsync', ctypes.c_uint32),
+ ('hbackporch', ctypes.c_uint32),
+ ('vfrontporch', ctypes.c_uint32),
+ ('vsync', ctypes.c_uint32),
+ ('vbackporch', ctypes.c_uint32),
+ ('il_vfrontporch', ctypes.c_uint32),
+ ('il_vsync', ctypes.c_uint32),
+ ('il_vbackporch', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 16),
+ ]
+
+ _pack_ = True
+
+# Interlaced or progressive format
+V4L2_DV_PROGRESSIVE = 0
+V4L2_DV_INTERLACED = 1
+
+# Polarities. If bit is not set, it is assumed to be negative polarity
+V4L2_DV_VSYNC_POS_POL = 0x00000001
+V4L2_DV_HSYNC_POS_POL = 0x00000002
+
+
+class v4l2_dv_timings(ctypes.Structure):
+ class _u(ctypes.Union):
+ _fields_ = [
+ ('bt', v4l2_bt_timings),
+ ('reserved', ctypes.c_uint32 * 32),
+ ]
+
+ _fields_ = [
+ ('type', ctypes.c_uint32),
+ ('_u', _u),
+ ]
+
+ _anonymous_ = ('_u',)
+ _pack_ = True
+
+
+# Values for the type field
+V4L2_DV_BT_656_1120 = 0
+
+
+#
+# Video inputs
+#
+
+class v4l2_input(ctypes.Structure):
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('name', ctypes.c_char * 32),
+ ('type', ctypes.c_uint32),
+ ('audioset', ctypes.c_uint32),
+ ('tuner', ctypes.c_uint32),
+ ('std', v4l2_std_id),
+ ('status', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 4),
+ ]
+
+
+V4L2_INPUT_TYPE_TUNER = 1
+V4L2_INPUT_TYPE_CAMERA = 2
+
+V4L2_IN_ST_NO_POWER = 0x00000001
+V4L2_IN_ST_NO_SIGNAL = 0x00000002
+V4L2_IN_ST_NO_COLOR = 0x00000004
+
+V4L2_IN_ST_HFLIP = 0x00000010
+V4L2_IN_ST_VFLIP = 0x00000020
+
+V4L2_IN_ST_NO_H_LOCK = 0x00000100
+V4L2_IN_ST_COLOR_KILL = 0x00000200
+
+V4L2_IN_ST_NO_SYNC = 0x00010000
+V4L2_IN_ST_NO_EQU = 0x00020000
+V4L2_IN_ST_NO_CARRIER = 0x00040000
+
+V4L2_IN_ST_MACROVISION = 0x01000000
+V4L2_IN_ST_NO_ACCESS = 0x02000000
+V4L2_IN_ST_VTR = 0x04000000
+
+V4L2_IN_CAP_PRESETS = 0x00000001
+V4L2_IN_CAP_CUSTOM_TIMINGS = 0x00000002
+V4L2_IN_CAP_STD = 0x00000004
+
+#
+# Video outputs
+#
+
+class v4l2_output(ctypes.Structure):
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('name', ctypes.c_char * 32),
+ ('type', ctypes.c_uint32),
+ ('audioset', ctypes.c_uint32),
+ ('modulator', ctypes.c_uint32),
+ ('std', v4l2_std_id),
+ ('reserved', ctypes.c_uint32 * 4),
+ ]
+
+
+V4L2_OUTPUT_TYPE_MODULATOR = 1
+V4L2_OUTPUT_TYPE_ANALOG = 2
+V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY = 3
+
+V4L2_OUT_CAP_PRESETS = 0x00000001
+V4L2_OUT_CAP_CUSTOM_TIMINGS = 0x00000002
+V4L2_OUT_CAP_STD = 0x00000004
+
+#
+# Controls
+#
+
+class v4l2_control(ctypes.Structure):
+ _fields_ = [
+ ('id', ctypes.c_uint32),
+ ('value', ctypes.c_int32),
+ ]
+
+
+class v4l2_ext_control(ctypes.Structure):
+ class _u(ctypes.Union):
+ _fields_ = [
+ ('value', ctypes.c_int32),
+ ('value64', ctypes.c_int64),
+ ('reserved', ctypes.c_void_p),
+ ]
+
+ _fields_ = [
+ ('id', ctypes.c_uint32),
+ ('reserved2', ctypes.c_uint32 * 2),
+ ('_u', _u)
+ ]
+
+ _anonymous_ = ('_u',)
+ _pack_ = True
+
+
+class v4l2_ext_controls(ctypes.Structure):
+ _fields_ = [
+ ('ctrl_class', ctypes.c_uint32),
+ ('count', ctypes.c_uint32),
+ ('error_idx', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 2),
+ ('controls', ctypes.POINTER(v4l2_ext_control)),
+ ]
+
+
+V4L2_CTRL_CLASS_USER = 0x00980000
+V4L2_CTRL_CLASS_MPEG = 0x00990000
+V4L2_CTRL_CLASS_CAMERA = 0x009a0000
+V4L2_CTRL_CLASS_FM_TX = 0x009b0000
+
+
+def V4L2_CTRL_ID_MASK():
+ return 0x0fffffff
+
+
+def V4L2_CTRL_ID2CLASS(id_):
+ return id_ & 0x0fff0000 # unsigned long
+
+
+def V4L2_CTRL_DRIVER_PRIV(id_):
+ return (id_ & 0xffff) >= 0x1000
+
+
+class v4l2_queryctrl(ctypes.Structure):
+ _fields_ = [
+ ('id', ctypes.c_uint32),
+ ('type', v4l2_ctrl_type),
+ ('name', ctypes.c_char * 32),
+ ('minimum', ctypes.c_int32),
+ ('maximum', ctypes.c_int32),
+ ('step', ctypes.c_int32),
+ ('default', ctypes.c_int32),
+ ('flags', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 2),
+ ]
+
+
+class v4l2_querymenu(ctypes.Structure):
+ _fields_ = [
+ ('id', ctypes.c_uint32),
+ ('index', ctypes.c_uint32),
+ ('name', ctypes.c_char * 32),
+ ('reserved', ctypes.c_uint32),
+ ]
+
+
+V4L2_CTRL_FLAG_DISABLED = 0x0001
+V4L2_CTRL_FLAG_GRABBED = 0x0002
+V4L2_CTRL_FLAG_READ_ONLY = 0x0004
+V4L2_CTRL_FLAG_UPDATE = 0x0008
+V4L2_CTRL_FLAG_INACTIVE = 0x0010
+V4L2_CTRL_FLAG_SLIDER = 0x0020
+V4L2_CTRL_FLAG_WRITE_ONLY = 0x0040
+
+V4L2_CTRL_FLAG_NEXT_CTRL = 0x80000000
+
+V4L2_CID_BASE = V4L2_CTRL_CLASS_USER | 0x900
+V4L2_CID_USER_BASE = V4L2_CID_BASE
+V4L2_CID_PRIVATE_BASE = 0x08000000
+
+V4L2_CID_USER_CLASS = V4L2_CTRL_CLASS_USER | 1
+V4L2_CID_BRIGHTNESS = V4L2_CID_BASE + 0
+V4L2_CID_CONTRAST = V4L2_CID_BASE + 1
+V4L2_CID_SATURATION = V4L2_CID_BASE + 2
+V4L2_CID_HUE = V4L2_CID_BASE + 3
+V4L2_CID_AUDIO_VOLUME = V4L2_CID_BASE + 5
+V4L2_CID_AUDIO_BALANCE = V4L2_CID_BASE + 6
+V4L2_CID_AUDIO_BASS = V4L2_CID_BASE + 7
+V4L2_CID_AUDIO_TREBLE = V4L2_CID_BASE + 8
+V4L2_CID_AUDIO_MUTE = V4L2_CID_BASE + 9
+V4L2_CID_AUDIO_LOUDNESS = V4L2_CID_BASE + 10
+V4L2_CID_BLACK_LEVEL = V4L2_CID_BASE + 11 # Deprecated
+V4L2_CID_AUTO_WHITE_BALANCE = V4L2_CID_BASE + 12
+V4L2_CID_DO_WHITE_BALANCE = V4L2_CID_BASE + 13
+V4L2_CID_RED_BALANCE = V4L2_CID_BASE + 14
+V4L2_CID_BLUE_BALANCE = V4L2_CID_BASE + 15
+V4L2_CID_GAMMA = V4L2_CID_BASE + 16
+V4L2_CID_WHITENESS = V4L2_CID_GAMMA # Deprecated
+V4L2_CID_EXPOSURE = V4L2_CID_BASE + 17
+V4L2_CID_AUTOGAIN = V4L2_CID_BASE + 18
+V4L2_CID_GAIN = V4L2_CID_BASE + 19
+V4L2_CID_HFLIP = V4L2_CID_BASE + 20
+V4L2_CID_VFLIP = V4L2_CID_BASE + 21
+
+# Deprecated; use V4L2_CID_PAN_RESET and V4L2_CID_TILT_RESET
+V4L2_CID_HCENTER = V4L2_CID_BASE + 22
+V4L2_CID_VCENTER = V4L2_CID_BASE + 23
+
+V4L2_CID_POWER_LINE_FREQUENCY = V4L2_CID_BASE + 24
+
+v4l2_power_line_frequency = enum
+(
+ V4L2_CID_POWER_LINE_FREQUENCY_DISABLED,
+ V4L2_CID_POWER_LINE_FREQUENCY_50HZ,
+ V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
+) = range(3)
+
+V4L2_CID_HUE_AUTO = V4L2_CID_BASE + 25
+V4L2_CID_WHITE_BALANCE_TEMPERATURE = V4L2_CID_BASE + 26
+V4L2_CID_SHARPNESS = V4L2_CID_BASE + 27
+V4L2_CID_BACKLIGHT_COMPENSATION = V4L2_CID_BASE + 28
+V4L2_CID_CHROMA_AGC = V4L2_CID_BASE + 29
+V4L2_CID_COLOR_KILLER = V4L2_CID_BASE + 30
+V4L2_CID_COLORFX = V4L2_CID_BASE + 31
+
+v4l2_colorfx = enum
+(
+ V4L2_COLORFX_NONE,
+ V4L2_COLORFX_BW,
+ V4L2_COLORFX_SEPIA,
+) = range(3)
+
+V4L2_CID_AUTOBRIGHTNESS = V4L2_CID_BASE + 32
+V4L2_CID_BAND_STOP_FILTER = V4L2_CID_BASE + 33
+
+V4L2_CID_ROTATE = V4L2_CID_BASE + 34
+V4L2_CID_BG_COLOR = V4L2_CID_BASE + 35
+V4L2_CID_LASTP1 = V4L2_CID_BASE + 36
+
+V4L2_CID_MPEG_BASE = V4L2_CTRL_CLASS_MPEG | 0x900
+V4L2_CID_MPEG_CLASS = V4L2_CTRL_CLASS_MPEG | 1
+
+# MPEG streams
+V4L2_CID_MPEG_STREAM_TYPE = V4L2_CID_MPEG_BASE + 0
+
+v4l2_mpeg_stream_type = enum
+(
+ V4L2_MPEG_STREAM_TYPE_MPEG2_PS,
+ V4L2_MPEG_STREAM_TYPE_MPEG2_TS,
+ V4L2_MPEG_STREAM_TYPE_MPEG1_SS,
+ V4L2_MPEG_STREAM_TYPE_MPEG2_DVD,
+ V4L2_MPEG_STREAM_TYPE_MPEG1_VCD,
+ V4L2_MPEG_STREAM_TYPE_MPEG2_SVCD,
+) = range(6)
+
+V4L2_CID_MPEG_STREAM_PID_PMT = V4L2_CID_MPEG_BASE + 1
+V4L2_CID_MPEG_STREAM_PID_AUDIO = V4L2_CID_MPEG_BASE + 2
+V4L2_CID_MPEG_STREAM_PID_VIDEO = V4L2_CID_MPEG_BASE + 3
+V4L2_CID_MPEG_STREAM_PID_PCR = V4L2_CID_MPEG_BASE + 4
+V4L2_CID_MPEG_STREAM_PES_ID_AUDIO = V4L2_CID_MPEG_BASE + 5
+V4L2_CID_MPEG_STREAM_PES_ID_VIDEO = V4L2_CID_MPEG_BASE + 6
+V4L2_CID_MPEG_STREAM_VBI_FMT = V4L2_CID_MPEG_BASE + 7
+
+v4l2_mpeg_stream_vbi_fmt = enum
+(
+ V4L2_MPEG_STREAM_VBI_FMT_NONE,
+ V4L2_MPEG_STREAM_VBI_FMT_IVTV,
+) = range(2)
+
+V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ = V4L2_CID_MPEG_BASE + 100
+
+v4l2_mpeg_audio_sampling_freq = enum
+(
+ V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100,
+ V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000,
+ V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000,
+) = range(3)
+
+V4L2_CID_MPEG_AUDIO_ENCODING = V4L2_CID_MPEG_BASE + 101
+
+v4l2_mpeg_audio_encoding = enum
+(
+ V4L2_MPEG_AUDIO_ENCODING_LAYER_1,
+ V4L2_MPEG_AUDIO_ENCODING_LAYER_2,
+ V4L2_MPEG_AUDIO_ENCODING_LAYER_3,
+ V4L2_MPEG_AUDIO_ENCODING_AAC,
+ V4L2_MPEG_AUDIO_ENCODING_AC3,
+) = range(5)
+
+V4L2_CID_MPEG_AUDIO_L1_BITRATE = V4L2_CID_MPEG_BASE + 102
+
+v4l2_mpeg_audio_l1_bitrate = enum
+(
+ V4L2_MPEG_AUDIO_L1_BITRATE_32K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_64K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_96K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_128K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_160K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_192K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_224K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_256K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_288K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_320K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_352K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_384K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_416K,
+ V4L2_MPEG_AUDIO_L1_BITRATE_448K,
+) = range(14)
+
+V4L2_CID_MPEG_AUDIO_L2_BITRATE = V4L2_CID_MPEG_BASE + 103
+
+v4l2_mpeg_audio_l2_bitrate = enum
+(
+ V4L2_MPEG_AUDIO_L2_BITRATE_32K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_48K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_56K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_64K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_80K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_96K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_112K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_128K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_160K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_192K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_224K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_256K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_320K,
+ V4L2_MPEG_AUDIO_L2_BITRATE_384K,
+) = range(14)
+
+V4L2_CID_MPEG_AUDIO_L3_BITRATE = V4L2_CID_MPEG_BASE + 104
+
+v4l2_mpeg_audio_l3_bitrate = enum
+(
+ V4L2_MPEG_AUDIO_L3_BITRATE_32K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_40K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_48K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_56K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_64K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_80K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_96K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_112K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_128K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_160K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_192K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_224K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_256K,
+ V4L2_MPEG_AUDIO_L3_BITRATE_320K,
+) = range(14)
+
+V4L2_CID_MPEG_AUDIO_MODE = V4L2_CID_MPEG_BASE + 105
+
+v4l2_mpeg_audio_mode = enum
+(
+ V4L2_MPEG_AUDIO_MODE_STEREO,
+ V4L2_MPEG_AUDIO_MODE_JOINT_STEREO,
+ V4L2_MPEG_AUDIO_MODE_DUAL,
+ V4L2_MPEG_AUDIO_MODE_MONO,
+) = range(4)
+
+V4L2_CID_MPEG_AUDIO_MODE_EXTENSION = V4L2_CID_MPEG_BASE + 106
+
+v4l2_mpeg_audio_mode_extension = enum
+(
+ V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_4,
+ V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_8,
+ V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_12,
+ V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_16,
+) = range(4)
+
+V4L2_CID_MPEG_AUDIO_EMPHASIS = V4L2_CID_MPEG_BASE + 107
+
+v4l2_mpeg_audio_emphasis = enum
+(
+ V4L2_MPEG_AUDIO_EMPHASIS_NONE,
+ V4L2_MPEG_AUDIO_EMPHASIS_50_DIV_15_uS,
+ V4L2_MPEG_AUDIO_EMPHASIS_CCITT_J17,
+) = range(3)
+
+V4L2_CID_MPEG_AUDIO_CRC = V4L2_CID_MPEG_BASE + 108
+
+v4l2_mpeg_audio_crc = enum
+(
+ V4L2_MPEG_AUDIO_CRC_NONE,
+ V4L2_MPEG_AUDIO_CRC_CRC16,
+) = range(2)
+
+V4L2_CID_MPEG_AUDIO_MUTE = V4L2_CID_MPEG_BASE + 109
+V4L2_CID_MPEG_AUDIO_AAC_BITRATE = V4L2_CID_MPEG_BASE + 110
+V4L2_CID_MPEG_AUDIO_AC3_BITRATE = V4L2_CID_MPEG_BASE + 111
+
+v4l2_mpeg_audio_ac3_bitrate = enum
+(
+ V4L2_MPEG_AUDIO_AC3_BITRATE_32K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_40K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_48K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_56K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_64K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_80K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_96K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_112K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_128K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_160K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_192K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_224K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_256K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_320K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_384K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_448K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_512K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_576K,
+ V4L2_MPEG_AUDIO_AC3_BITRATE_640K,
+) = range(19)
+
+V4L2_CID_MPEG_VIDEO_ENCODING = V4L2_CID_MPEG_BASE + 200
+
+v4l2_mpeg_video_encoding = enum
+(
+ V4L2_MPEG_VIDEO_ENCODING_MPEG_1,
+ V4L2_MPEG_VIDEO_ENCODING_MPEG_2,
+ V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC,
+) = range(3)
+
+V4L2_CID_MPEG_VIDEO_ASPECT = V4L2_CID_MPEG_BASE + 201
+
+v4l2_mpeg_video_aspect = enum
+(
+ V4L2_MPEG_VIDEO_ASPECT_1x1,
+ V4L2_MPEG_VIDEO_ASPECT_4x3,
+ V4L2_MPEG_VIDEO_ASPECT_16x9,
+ V4L2_MPEG_VIDEO_ASPECT_221x100,
+) = range(4)
+
+V4L2_CID_MPEG_VIDEO_B_FRAMES = V4L2_CID_MPEG_BASE + 202
+V4L2_CID_MPEG_VIDEO_GOP_SIZE = V4L2_CID_MPEG_BASE + 203
+V4L2_CID_MPEG_VIDEO_GOP_CLOSURE = V4L2_CID_MPEG_BASE + 204
+V4L2_CID_MPEG_VIDEO_PULLDOWN = V4L2_CID_MPEG_BASE + 205
+V4L2_CID_MPEG_VIDEO_BITRATE_MODE = V4L2_CID_MPEG_BASE + 206
+
+v4l2_mpeg_video_bitrate_mode = enum
+(
+ V4L2_MPEG_VIDEO_BITRATE_MODE_VBR,
+ V4L2_MPEG_VIDEO_BITRATE_MODE_CBR,
+) = range(2)
+
+V4L2_CID_MPEG_VIDEO_BITRATE = V4L2_CID_MPEG_BASE + 207
+V4L2_CID_MPEG_VIDEO_BITRATE_PEAK = V4L2_CID_MPEG_BASE + 208
+V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION = V4L2_CID_MPEG_BASE + 209
+V4L2_CID_MPEG_VIDEO_MUTE = V4L2_CID_MPEG_BASE + 210
+V4L2_CID_MPEG_VIDEO_MUTE_YUV = V4L2_CID_MPEG_BASE + 211
+
+V4L2_CID_MPEG_CX2341X_BASE = V4L2_CTRL_CLASS_MPEG | 0x1000
+V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE = V4L2_CID_MPEG_CX2341X_BASE + 0
+
+v4l2_mpeg_cx2341x_video_spatial_filter_mode = enum
+(
+ V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_MANUAL,
+ V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_AUTO,
+) = range(2)
+
+V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER = V4L2_CID_MPEG_CX2341X_BASE + 1
+V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 2
+
+v4l2_mpeg_cx2341x_video_luma_spatial_filter_type = enum
+(
+ V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_OFF,
+ V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_1D_HOR,
+ V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_1D_VERT,
+ V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_2D_HV_SEPARABLE,
+ V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_2D_SYM_NON_SEPARABLE,
+) = range(5)
+
+V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 3
+
+v4l2_mpeg_cx2341x_video_chroma_spatial_filter_type = enum
+(
+ V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_OFF,
+ V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_1D_HOR,
+) = range(2)
+
+V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE = V4L2_CID_MPEG_CX2341X_BASE + 4
+
+v4l2_mpeg_cx2341x_video_temporal_filter_mode = enum
+(
+ V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_MANUAL,
+ V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_AUTO,
+) = range(2)
+
+V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER = V4L2_CID_MPEG_CX2341X_BASE + 5
+V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 6
+
+v4l2_mpeg_cx2341x_video_median_filter_type = enum
+(
+ V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_OFF,
+ V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_HOR,
+ V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_VERT,
+ V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_HOR_VERT,
+ V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_DIAG,
+) = range(5)
+
+V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM = V4L2_CID_MPEG_CX2341X_BASE + 7
+V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP = V4L2_CID_MPEG_CX2341X_BASE + 8
+V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM = V4L2_CID_MPEG_CX2341X_BASE + 9
+V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP = V4L2_CID_MPEG_CX2341X_BASE + 10
+V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS = V4L2_CID_MPEG_CX2341X_BASE + 11
+
+V4L2_CID_CAMERA_CLASS_BASE = V4L2_CTRL_CLASS_CAMERA | 0x900
+V4L2_CID_CAMERA_CLASS = V4L2_CTRL_CLASS_CAMERA | 1
+
+V4L2_CID_EXPOSURE_AUTO = V4L2_CID_CAMERA_CLASS_BASE + 1
+
+v4l2_exposure_auto_type = enum
+(
+ V4L2_EXPOSURE_AUTO,
+ V4L2_EXPOSURE_MANUAL,
+ V4L2_EXPOSURE_SHUTTER_PRIORITY,
+ V4L2_EXPOSURE_APERTURE_PRIORITY,
+) = range(4)
+
+V4L2_CID_EXPOSURE_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 2
+V4L2_CID_EXPOSURE_AUTO_PRIORITY = V4L2_CID_CAMERA_CLASS_BASE + 3
+
+V4L2_CID_PAN_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 4
+V4L2_CID_TILT_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 5
+V4L2_CID_PAN_RESET = V4L2_CID_CAMERA_CLASS_BASE + 6
+V4L2_CID_TILT_RESET = V4L2_CID_CAMERA_CLASS_BASE + 7
+
+V4L2_CID_PAN_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 8
+V4L2_CID_TILT_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 9
+
+V4L2_CID_FOCUS_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 10
+V4L2_CID_FOCUS_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 11
+V4L2_CID_FOCUS_AUTO = V4L2_CID_CAMERA_CLASS_BASE + 12
+
+V4L2_CID_ZOOM_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 13
+V4L2_CID_ZOOM_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 14
+V4L2_CID_ZOOM_CONTINUOUS = V4L2_CID_CAMERA_CLASS_BASE + 15
+
+V4L2_CID_PRIVACY = V4L2_CID_CAMERA_CLASS_BASE + 16
+
+V4L2_CID_FM_TX_CLASS_BASE = V4L2_CTRL_CLASS_FM_TX | 0x900
+V4L2_CID_FM_TX_CLASS = V4L2_CTRL_CLASS_FM_TX | 1
+
+V4L2_CID_RDS_TX_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 1
+V4L2_CID_RDS_TX_PI = V4L2_CID_FM_TX_CLASS_BASE + 2
+V4L2_CID_RDS_TX_PTY = V4L2_CID_FM_TX_CLASS_BASE + 3
+V4L2_CID_RDS_TX_PS_NAME = V4L2_CID_FM_TX_CLASS_BASE + 5
+V4L2_CID_RDS_TX_RADIO_TEXT = V4L2_CID_FM_TX_CLASS_BASE + 6
+
+V4L2_CID_AUDIO_LIMITER_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 64
+V4L2_CID_AUDIO_LIMITER_RELEASE_TIME = V4L2_CID_FM_TX_CLASS_BASE + 65
+V4L2_CID_AUDIO_LIMITER_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 66
+
+V4L2_CID_AUDIO_COMPRESSION_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 80
+V4L2_CID_AUDIO_COMPRESSION_GAIN = V4L2_CID_FM_TX_CLASS_BASE + 81
+V4L2_CID_AUDIO_COMPRESSION_THRESHOLD = V4L2_CID_FM_TX_CLASS_BASE + 82
+V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME = V4L2_CID_FM_TX_CLASS_BASE + 83
+V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME = V4L2_CID_FM_TX_CLASS_BASE + 84
+
+V4L2_CID_PILOT_TONE_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 96
+V4L2_CID_PILOT_TONE_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 97
+V4L2_CID_PILOT_TONE_FREQUENCY = V4L2_CID_FM_TX_CLASS_BASE + 98
+
+V4L2_CID_TUNE_PREEMPHASIS = V4L2_CID_FM_TX_CLASS_BASE + 112
+
+v4l2_preemphasis = enum
+(
+ V4L2_PREEMPHASIS_DISABLED,
+ V4L2_PREEMPHASIS_50_uS,
+ V4L2_PREEMPHASIS_75_uS,
+) = range(3)
+
+V4L2_CID_TUNE_POWER_LEVEL = V4L2_CID_FM_TX_CLASS_BASE + 113
+V4L2_CID_TUNE_ANTENNA_CAPACITOR = V4L2_CID_FM_TX_CLASS_BASE + 114
+
+
+#
+# Tuning
+#
+
+class v4l2_tuner(ctypes.Structure):
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('name', ctypes.c_char * 32),
+ ('type', v4l2_tuner_type),
+ ('capability', ctypes.c_uint32),
+ ('rangelow', ctypes.c_uint32),
+ ('rangehigh', ctypes.c_uint32),
+ ('rxsubchans', ctypes.c_uint32),
+ ('audmode', ctypes.c_uint32),
+ ('signal', ctypes.c_int32),
+ ('afc', ctypes.c_int32),
+ ('reserved', ctypes.c_uint32 * 4),
+ ]
+
+
+class v4l2_modulator(ctypes.Structure):
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('name', ctypes.c_char * 32),
+ ('capability', ctypes.c_uint32),
+ ('rangelow', ctypes.c_uint32),
+ ('rangehigh', ctypes.c_uint32),
+ ('txsubchans', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 4),
+ ]
+
+
+V4L2_TUNER_CAP_LOW = 0x0001
+V4L2_TUNER_CAP_NORM = 0x0002
+V4L2_TUNER_CAP_STEREO = 0x0010
+V4L2_TUNER_CAP_LANG2 = 0x0020
+V4L2_TUNER_CAP_SAP = 0x0020
+V4L2_TUNER_CAP_LANG1 = 0x0040
+V4L2_TUNER_CAP_RDS = 0x0080
+
+V4L2_TUNER_SUB_MONO = 0x0001
+V4L2_TUNER_SUB_STEREO = 0x0002
+V4L2_TUNER_SUB_LANG2 = 0x0004
+V4L2_TUNER_SUB_SAP = 0x0004
+V4L2_TUNER_SUB_LANG1 = 0x0008
+V4L2_TUNER_SUB_RDS = 0x0010
+
+V4L2_TUNER_MODE_MONO = 0x0000
+V4L2_TUNER_MODE_STEREO = 0x0001
+V4L2_TUNER_MODE_LANG2 = 0x0002
+V4L2_TUNER_MODE_SAP = 0x0002
+V4L2_TUNER_MODE_LANG1 = 0x0003
+V4L2_TUNER_MODE_LANG1_LANG2 = 0x0004
+
+
+class v4l2_frequency(ctypes.Structure):
+ _fields_ = [
+ ('tuner', ctypes.c_uint32),
+ ('type', v4l2_tuner_type),
+ ('frequency', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 8),
+ ]
+
+
+class v4l2_hw_freq_seek(ctypes.Structure):
+ _fields_ = [
+ ('tuner', ctypes.c_uint32),
+ ('type', v4l2_tuner_type),
+ ('seek_upward', ctypes.c_uint32),
+ ('wrap_around', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 8),
+ ]
+
+
+#
+# RDS
+#
+
+class v4l2_rds_data(ctypes.Structure):
+ _fields_ = [
+ ('lsb', ctypes.c_char),
+ ('msb', ctypes.c_char),
+ ('block', ctypes.c_char),
+ ]
+
+ _pack_ = True
+
+
+V4L2_RDS_BLOCK_MSK = 0x7
+V4L2_RDS_BLOCK_A = 0
+V4L2_RDS_BLOCK_B = 1
+V4L2_RDS_BLOCK_C = 2
+V4L2_RDS_BLOCK_D = 3
+V4L2_RDS_BLOCK_C_ALT = 4
+V4L2_RDS_BLOCK_INVALID = 7
+
+V4L2_RDS_BLOCK_CORRECTED = 0x40
+V4L2_RDS_BLOCK_ERROR = 0x80
+
+
+#
+# Audio
+#
+
+class v4l2_audio(ctypes.Structure):
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('name', ctypes.c_char * 32),
+ ('capability', ctypes.c_uint32),
+ ('mode', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 2),
+ ]
+
+
+V4L2_AUDCAP_STEREO = 0x00001
+V4L2_AUDCAP_AVL = 0x00002
+
+V4L2_AUDMODE_AVL = 0x00001
+
+
+class v4l2_audioout(ctypes.Structure):
+ _fields_ = [
+ ('index', ctypes.c_uint32),
+ ('name', ctypes.c_char * 32),
+ ('capability', ctypes.c_uint32),
+ ('mode', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 2),
+ ]
+
+
+#
+# Mpeg services (experimental)
+#
+
+V4L2_ENC_IDX_FRAME_I = 0
+V4L2_ENC_IDX_FRAME_P = 1
+V4L2_ENC_IDX_FRAME_B = 2
+V4L2_ENC_IDX_FRAME_MASK = 0xf
+
+
+class v4l2_enc_idx_entry(ctypes.Structure):
+ _fields_ = [
+ ('offset', ctypes.c_uint64),
+ ('pts', ctypes.c_uint64),
+ ('length', ctypes.c_uint32),
+ ('flags', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 2),
+ ]
+
+
+V4L2_ENC_IDX_ENTRIES = 64
+
+
+class v4l2_enc_idx(ctypes.Structure):
+ _fields_ = [
+ ('entries', ctypes.c_uint32),
+ ('entries_cap', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 4),
+ ('entry', v4l2_enc_idx_entry * V4L2_ENC_IDX_ENTRIES),
+ ]
+
+
+V4L2_ENC_CMD_START = 0
+V4L2_ENC_CMD_STOP = 1
+V4L2_ENC_CMD_PAUSE = 2
+V4L2_ENC_CMD_RESUME = 3
+
+V4L2_ENC_CMD_STOP_AT_GOP_END = 1 << 0
+
+
+class v4l2_encoder_cmd(ctypes.Structure):
+ class _u(ctypes.Union):
+ class _s(ctypes.Structure):
+ _fields_ = [
+ ('data', ctypes.c_uint32 * 8),
+ ]
+
+ _fields_ = [
+ ('raw', _s),
+ ]
+
+ _fields_ = [
+ ('cmd', ctypes.c_uint32),
+ ('flags', ctypes.c_uint32),
+ ('_u', _u),
+ ]
+
+ _anonymous_ = ('_u',)
+
+
+#
+# Data services (VBI)
+#
+
+class v4l2_vbi_format(ctypes.Structure):
+ _fields_ = [
+ ('sampling_rate', ctypes.c_uint32),
+ ('offset', ctypes.c_uint32),
+ ('samples_per_line', ctypes.c_uint32),
+ ('sample_format', ctypes.c_uint32),
+ ('start', ctypes.c_int32 * 2),
+ ('count', ctypes.c_uint32 * 2),
+ ('flags', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 2),
+ ]
+
+
+V4L2_VBI_UNSYNC = 1 << 0
+V4L2_VBI_INTERLACED = 1 << 1
+
+
+class v4l2_sliced_vbi_format(ctypes.Structure):
+ _fields_ = [
+ ('service_set', ctypes.c_uint16),
+ ('service_lines', ctypes.c_uint16 * 2 * 24),
+ ('io_size', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32 * 2),
+ ]
+
+
+V4L2_SLICED_TELETEXT_B = 0x0001
+V4L2_SLICED_VPS = 0x0400
+V4L2_SLICED_CAPTION_525 = 0x1000
+V4L2_SLICED_WSS_625 = 0x4000
+V4L2_SLICED_VBI_525 = V4L2_SLICED_CAPTION_525
+V4L2_SLICED_VBI_625 = (
+ V4L2_SLICED_TELETEXT_B | V4L2_SLICED_VPS | V4L2_SLICED_WSS_625)
+
+
+class v4l2_sliced_vbi_cap(ctypes.Structure):
+ _fields_ = [
+ ('service_set', ctypes.c_uint16),
+ ('service_lines', ctypes.c_uint16 * 2 * 24),
+ ('type', v4l2_buf_type),
+ ('reserved', ctypes.c_uint32 * 3),
+ ]
+
+
+class v4l2_sliced_vbi_data(ctypes.Structure):
+ _fields_ = [
+ ('id', ctypes.c_uint32),
+ ('field', ctypes.c_uint32),
+ ('line', ctypes.c_uint32),
+ ('reserved', ctypes.c_uint32),
+ ('data', ctypes.c_char * 48),
+ ]
+
+
+#
+# Sliced VBI data inserted into MPEG Streams
+#
+
+
+V4L2_MPEG_VBI_IVTV_TELETEXT_B = 1
+V4L2_MPEG_VBI_IVTV_CAPTION_525 = 4
+V4L2_MPEG_VBI_IVTV_WSS_625 = 5
+V4L2_MPEG_VBI_IVTV_VPS = 7
+
+
+class v4l2_mpeg_vbi_itv0_line(ctypes.Structure):
+ _fields_ = [
+ ('id', ctypes.c_char),
+ ('data', ctypes.c_char * 42),
+ ]
+
+ _pack_ = True
+
+
+class v4l2_mpeg_vbi_itv0(ctypes.Structure):
+ _fields_ = [
+ ('linemask', ctypes.c_uint32 * 2), # how to define __le32 in ctypes?
+ ('line', v4l2_mpeg_vbi_itv0_line * 35),
+ ]
+
+ _pack_ = True
+
+
+class v4l2_mpeg_vbi_ITV0(ctypes.Structure):
+ _fields_ = [
+ ('line', v4l2_mpeg_vbi_itv0_line * 36),
+ ]
+
+ _pack_ = True
+
+
+V4L2_MPEG_VBI_IVTV_MAGIC0 = "itv0"
+V4L2_MPEG_VBI_IVTV_MAGIC1 = "ITV0"
+
+
+class v4l2_mpeg_vbi_fmt_ivtv(ctypes.Structure):
+ class _u(ctypes.Union):
+ _fields_ = [
+ ('itv0', v4l2_mpeg_vbi_itv0),
+ ('ITV0', v4l2_mpeg_vbi_ITV0),
+ ]
+
+ _fields_ = [
+ ('magic', ctypes.c_char * 4),
+ ('_u', _u)
+ ]
+
+ _anonymous_ = ('_u',)
+ _pack_ = True
+
+
+#
+# Aggregate structures
+#
+
+class v4l2_format(ctypes.Structure):
+ class _u(ctypes.Union):
+ _fields_ = [
+ ('pix', v4l2_pix_format),
+ ('win', v4l2_window),
+ ('vbi', v4l2_vbi_format),
+ ('sliced', v4l2_sliced_vbi_format),
+ ('raw_data', ctypes.c_char * 200),
+ ]
+
+ _fields_ = [
+ ('type', v4l2_buf_type),
+ ('fmt', _u),
+ ]
+
+
+class v4l2_streamparm(ctypes.Structure):
+ class _u(ctypes.Union):
+ _fields_ = [
+ ('capture', v4l2_captureparm),
+ ('output', v4l2_outputparm),
+ ('raw_data', ctypes.c_char * 200),
+ ]
+
+ _fields_ = [
+ ('type', v4l2_buf_type),
+ ('parm', _u)
+ ]
+
+
+#
+# Advanced debugging
+#
+
+V4L2_CHIP_MATCH_HOST = 0
+V4L2_CHIP_MATCH_I2C_DRIVER = 1
+V4L2_CHIP_MATCH_I2C_ADDR = 2
+V4L2_CHIP_MATCH_AC97 = 3
+
+
+class v4l2_dbg_match(ctypes.Structure):
+ class _u(ctypes.Union):
+ _fields_ = [
+ ('addr', ctypes.c_uint32),
+ ('name', ctypes.c_char * 32),
+ ]
+
+ _fields_ = [
+ ('type', ctypes.c_uint32),
+ ('_u', _u),
+ ]
+
+ _anonymous_ = ('_u',)
+ _pack_ = True
+
+
+class v4l2_dbg_register(ctypes.Structure):
+ _fields_ = [
+ ('match', v4l2_dbg_match),
+ ('size', ctypes.c_uint32),
+ ('reg', ctypes.c_uint64),
+ ('val', ctypes.c_uint64),
+ ]
+
+ _pack_ = True
+
+
+class v4l2_dbg_chip_ident(ctypes.Structure):
+ _fields_ = [
+ ('match', v4l2_dbg_match),
+ ('ident', ctypes.c_uint32),
+ ('revision', ctypes.c_uint32),
+ ]
+
+ _pack_ = True
+
+
+#
+# ioctl codes for video devices
+#
+
+VIDIOC_QUERYCAP = _IOR('V', 0, v4l2_capability)
+VIDIOC_RESERVED = _IO('V', 1)
+VIDIOC_ENUM_FMT = _IOWR('V', 2, v4l2_fmtdesc)
+VIDIOC_G_FMT = _IOWR('V', 4, v4l2_format)
+VIDIOC_S_FMT = _IOWR('V', 5, v4l2_format)
+VIDIOC_REQBUFS = _IOWR('V', 8, v4l2_requestbuffers)
+VIDIOC_QUERYBUF = _IOWR('V', 9, v4l2_buffer)
+VIDIOC_G_FBUF = _IOR('V', 10, v4l2_framebuffer)
+VIDIOC_S_FBUF = _IOW('V', 11, v4l2_framebuffer)
+VIDIOC_OVERLAY = _IOW('V', 14, ctypes.c_int)
+VIDIOC_QBUF = _IOWR('V', 15, v4l2_buffer)
+VIDIOC_DQBUF = _IOWR('V', 17, v4l2_buffer)
+VIDIOC_STREAMON = _IOW('V', 18, ctypes.c_int)
+VIDIOC_STREAMOFF = _IOW('V', 19, ctypes.c_int)
+VIDIOC_G_PARM = _IOWR('V', 21, v4l2_streamparm)
+VIDIOC_S_PARM = _IOWR('V', 22, v4l2_streamparm)
+VIDIOC_G_STD = _IOR('V', 23, v4l2_std_id)
+VIDIOC_S_STD = _IOW('V', 24, v4l2_std_id)
+VIDIOC_ENUMSTD = _IOWR('V', 25, v4l2_standard)
+VIDIOC_ENUMINPUT = _IOWR('V', 26, v4l2_input)
+VIDIOC_G_CTRL = _IOWR('V', 27, v4l2_control)
+VIDIOC_S_CTRL = _IOWR('V', 28, v4l2_control)
+VIDIOC_G_TUNER = _IOWR('V', 29, v4l2_tuner)
+VIDIOC_S_TUNER = _IOW('V', 30, v4l2_tuner)
+VIDIOC_G_AUDIO = _IOR('V', 33, v4l2_audio)
+VIDIOC_S_AUDIO = _IOW('V', 34, v4l2_audio)
+VIDIOC_QUERYCTRL = _IOWR('V', 36, v4l2_queryctrl)
+VIDIOC_QUERYMENU = _IOWR('V', 37, v4l2_querymenu)
+VIDIOC_G_INPUT = _IOR('V', 38, ctypes.c_int)
+VIDIOC_S_INPUT = _IOWR('V', 39, ctypes.c_int)
+VIDIOC_G_OUTPUT = _IOR('V', 46, ctypes.c_int)
+VIDIOC_S_OUTPUT = _IOWR('V', 47, ctypes.c_int)
+VIDIOC_ENUMOUTPUT = _IOWR('V', 48, v4l2_output)
+VIDIOC_G_AUDOUT = _IOR('V', 49, v4l2_audioout)
+VIDIOC_S_AUDOUT = _IOW('V', 50, v4l2_audioout)
+VIDIOC_G_MODULATOR = _IOWR('V', 54, v4l2_modulator)
+VIDIOC_S_MODULATOR = _IOW('V', 55, v4l2_modulator)
+VIDIOC_G_FREQUENCY = _IOWR('V', 56, v4l2_frequency)
+VIDIOC_S_FREQUENCY = _IOW('V', 57, v4l2_frequency)
+VIDIOC_CROPCAP = _IOWR('V', 58, v4l2_cropcap)
+VIDIOC_G_CROP = _IOWR('V', 59, v4l2_crop)
+VIDIOC_S_CROP = _IOW('V', 60, v4l2_crop)
+VIDIOC_G_JPEGCOMP = _IOR('V', 61, v4l2_jpegcompression)
+VIDIOC_S_JPEGCOMP = _IOW('V', 62, v4l2_jpegcompression)
+VIDIOC_QUERYSTD = _IOR('V', 63, v4l2_std_id)
+VIDIOC_TRY_FMT = _IOWR('V', 64, v4l2_format)
+VIDIOC_ENUMAUDIO = _IOWR('V', 65, v4l2_audio)
+VIDIOC_ENUMAUDOUT = _IOWR('V', 66, v4l2_audioout)
+VIDIOC_G_PRIORITY = _IOR('V', 67, v4l2_priority)
+VIDIOC_S_PRIORITY = _IOW('V', 68, v4l2_priority)
+VIDIOC_G_SLICED_VBI_CAP = _IOWR('V', 69, v4l2_sliced_vbi_cap)
+VIDIOC_LOG_STATUS = _IO('V', 70)
+VIDIOC_G_EXT_CTRLS = _IOWR('V', 71, v4l2_ext_controls)
+VIDIOC_S_EXT_CTRLS = _IOWR('V', 72, v4l2_ext_controls)
+VIDIOC_TRY_EXT_CTRLS = _IOWR('V', 73, v4l2_ext_controls)
+
+VIDIOC_ENUM_FRAMESIZES = _IOWR('V', 74, v4l2_frmsizeenum)
+VIDIOC_ENUM_FRAMEINTERVALS = _IOWR('V', 75, v4l2_frmivalenum)
+VIDIOC_G_ENC_INDEX = _IOR('V', 76, v4l2_enc_idx)
+VIDIOC_ENCODER_CMD = _IOWR('V', 77, v4l2_encoder_cmd)
+VIDIOC_TRY_ENCODER_CMD = _IOWR('V', 78, v4l2_encoder_cmd)
+
+VIDIOC_DBG_S_REGISTER = _IOW('V', 79, v4l2_dbg_register)
+VIDIOC_DBG_G_REGISTER = _IOWR('V', 80, v4l2_dbg_register)
+
+VIDIOC_DBG_G_CHIP_IDENT = _IOWR('V', 81, v4l2_dbg_chip_ident)
+
+VIDIOC_S_HW_FREQ_SEEK = _IOW('V', 82, v4l2_hw_freq_seek)
+VIDIOC_ENUM_DV_PRESETS = _IOWR('V', 83, v4l2_dv_enum_preset)
+VIDIOC_S_DV_PRESET = _IOWR('V', 84, v4l2_dv_preset)
+VIDIOC_G_DV_PRESET = _IOWR('V', 85, v4l2_dv_preset)
+VIDIOC_QUERY_DV_PRESET = _IOR('V', 86, v4l2_dv_preset)
+VIDIOC_S_DV_TIMINGS = _IOWR('V', 87, v4l2_dv_timings)
+VIDIOC_G_DV_TIMINGS = _IOWR('V', 88, v4l2_dv_timings)
+
+VIDIOC_OVERLAY_OLD = _IOWR('V', 14, ctypes.c_int)
+VIDIOC_S_PARM_OLD = _IOW('V', 22, v4l2_streamparm)
+VIDIOC_S_CTRL_OLD = _IOW('V', 28, v4l2_control)
+VIDIOC_G_AUDIO_OLD = _IOWR('V', 33, v4l2_audio)
+VIDIOC_G_AUDOUT_OLD = _IOWR('V', 49, v4l2_audioout)
+VIDIOC_CROPCAP_OLD = _IOR('V', 58, v4l2_cropcap)
+
+BASE_VIDIOC_PRIVATE = 192
diff --git a/plugins/light_sensor/icons/extrasoff.svg b/plugins/light_sensor/icons/extrasoff.svg
new file mode 100644
index 0000000..a956f03
--- /dev/null
+++ b/plugins/light_sensor/icons/extrasoff.svg
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <rect
+ width="42.345783"
+ height="42.345783"
+ x="6.3271084"
+ y="6.3271084"
+ id="rect3016"
+ style="fill:#282828;fill-opacity:1;stroke:#282828;stroke-width:2.6542182;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="g3">
+ <polygon
+ points="26.891,12.363 17.238,16.369 14.659,4.975 20.555,2.531 "
+ id="polygon5"
+ style="fill:#ffffff" />
+ <polygon
+ points="42.646,26.88 38.649,17.228 50.04,14.654 52.477,20.55 "
+ id="polygon7"
+ style="fill:#ffffff" />
+ <polygon
+ points="28.117,42.645 37.775,38.645 40.349,50.029 34.453,52.471 "
+ id="polygon9"
+ style="fill:#ffffff" />
+ <polygon
+ points="37.824,16.315 28.171,12.309 34.394,2.439 40.295,4.882 "
+ id="polygon11"
+ style="fill:#ffffff" />
+ <polygon
+ points="38.628,37.791 42.623,28.139 52.493,34.365 50.055,40.258 "
+ id="polygon13"
+ style="fill:#ffffff" />
+ <polygon
+ points="16.385,37.76 12.391,28.105 2.515,34.34 4.953,40.234 "
+ id="polygon15"
+ style="fill:#ffffff" />
+ <polygon
+ points="12.319,26.875 16.32,17.216 4.936,14.643 2.493,20.539 "
+ id="polygon17"
+ style="fill:#ffffff" />
+ <polygon
+ points="26.939,42.623 17.287,38.629 14.719,50.018 20.609,52.461 "
+ id="polygon19"
+ style="fill:#ffffff" />
+ <path
+ d="m 39.925,22.352 c 2.845,6.863 -0.412,14.728 -7.274,17.574 -6.867,2.85 -14.734,-0.418 -17.578,-7.281 -2.84,-6.862 0.418,-14.733 7.279,-17.572 6.862,-2.845 14.734,0.417 17.573,7.279 z"
+ id="path21"
+ style="fill:none;stroke:#ffffff;stroke-width:11.69439983" />
+ </g>
+</svg>
diff --git a/plugins/light_sensor/icons/extrason.svg b/plugins/light_sensor/icons/extrason.svg
new file mode 100644
index 0000000..7ee08bf
--- /dev/null
+++ b/plugins/light_sensor/icons/extrason.svg
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata20">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5">
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2431"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2428"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2557"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,9.2560985,9.9123239)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2561"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,8.962951,22.783517)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2461"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2463"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
+ </defs>
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <g
+ id="g3799">
+ <polygon
+ points="20.555,2.531 26.891,12.363 17.238,16.369 14.659,4.975 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon5"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="52.477,20.55 42.646,26.88 38.649,17.228 50.04,14.654 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon7"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="34.453,52.471 28.117,42.645 37.775,38.645 40.349,50.029 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon9"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="40.295,4.882 37.824,16.315 28.171,12.309 34.394,2.439 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon11"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="50.055,40.258 38.628,37.791 42.623,28.139 52.493,34.365 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon13"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="4.953,40.234 16.385,37.76 12.391,28.105 2.515,34.34 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon15"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="2.493,20.539 12.319,26.875 16.32,17.216 4.936,14.643 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon17"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="20.609,52.461 26.939,42.623 17.287,38.629 14.719,50.018 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon19"
+ style="fill:#ff0000;fill-opacity:1" />
+ <path
+ d="m 35.45648,24.236169 c 1.8208,4.389511 -0.26368,9.419891 -4.65536,11.240166 -4.39488,1.822833 -9.42976,-0.267349 -11.24992,-4.65686 -1.8176,-4.388871 0.26752,-9.423089 4.65856,-11.238887 4.39168,-1.819635 9.42976,0.26671 11.24672,4.655581 z"
+ id="path21"
+ style="fill:none;stroke:#ff0000;stroke-width:7.48202181;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/plugins/light_sensor/icons/sensoroff.svg b/plugins/light_sensor/icons/sensoroff.svg
new file mode 100644
index 0000000..0a16670
--- /dev/null
+++ b/plugins/light_sensor/icons/sensoroff.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata15"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs13" />
+<rect
+ width="42.763924"
+ height="42.763924"
+ x="6.1180382"
+ y="6.1180382"
+ id="rect2986"
+ style="fill:#282828;fill-opacity:1;stroke:#282828;stroke-width:2.23607516;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><g
+ transform="matrix(0.87078705,0,0,0.87078705,3.2821055,2.9298726)"
+ id="network-wired_1_"
+ style="display:block">
+ <g
+ id="network-wired">
+ <line
+ id="line3076"
+ y2="23.993"
+ y1="32.438999"
+ x2="16.966999"
+ x1="16.966999"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round" />
+ <line
+ id="line3078"
+ y2="28.215"
+ y1="28.215"
+ x2="34.938"
+ x1="29.636999"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round" />
+
+ <rect
+ width="9.7939997"
+ height="7.599"
+ x="42.157001"
+ y="24.312"
+ id="rect3080"
+ style="fill:#ffffff;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round" />
+ <path
+ d="m 16.967,23.993 c 0,-2.334 -1.892,-4.224 -4.224,-4.224 -2.332,0 -4.223,1.889 -4.223,4.224"
+ id="path3082"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 25.413,32.439 c 0,2.334 -1.891,4.224 -4.224,4.224 -2.332,0 -4.223,-1.89 -4.223,-4.224"
+ id="path3084"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 25.413,32.439 c 0,-2.332 1.893,-4.226 4.224,-4.226"
+ id="path3086"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 8.52,23.993 c 0,2.332 -1.892,4.222 -4.223,4.222"
+ id="path3088"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+
+ <rect
+ width="14.477"
+ height="11.35"
+ x="31.945"
+ y="22.438"
+ id="rect3090"
+ style="fill:#ffffff;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/plugins/light_sensor/icons/sensoron.svg b/plugins/light_sensor/icons/sensoron.svg
new file mode 100644
index 0000000..d756860
--- /dev/null
+++ b/plugins/light_sensor/icons/sensoron.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata15"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs13" /><rect
+ width="55"
+ height="55"
+ x="0"
+ y="0"
+ id="rect3269"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:nonzero;stroke:none" /><g
+ transform="translate(0.27777716,18.796296)"
+ id="g4054"><line
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-opacity:1"
+ x1="17.778971"
+ x2="17.778971"
+ y1="12.381037"
+ y2="5.0263696"
+ id="line3076" /><line
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-opacity:1"
+ x1="28.811842"
+ x2="33.427887"
+ y1="8.7028332"
+ y2="8.7028332"
+ id="line3078" /><rect
+ width="8.5284882"
+ height="6.6171107"
+ x="39.7141"
+ y="5.3041511"
+ id="rect3080"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:1.95927083;stroke-linecap:round;stroke-opacity:1" /><path
+ d="m 17.778971,5.0263697 c 0,-2.032417 -1.647529,-3.6782045 -3.678204,-3.6782045 -2.030675,0 -3.677334,1.6449167 -3.677334,3.6782045"
+ id="path3082"
+ style="fill:none;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 25.133639,12.381037 c 0,2.032417 -1.646658,3.678205 -3.678205,3.678205 -2.030675,0 -3.677333,-1.645788 -3.677333,-3.678205"
+ id="path3084"
+ style="fill:none;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 25.133639,12.381037 c 0,-2.030675 1.6484,-3.679946 3.678204,-3.679946"
+ id="path3086"
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 10.423433,5.0263697 c 0,2.0306754 -1.6475288,3.6764629 -3.6773334,3.6764629"
+ id="path3088"
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><rect
+ width="12.606384"
+ height="9.8834333"
+ x="30.821619"
+ y="3.6722956"
+ id="rect3090"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/plugins/light_sensor/light_sensor.py b/plugins/light_sensor/light_sensor.py
new file mode 100644
index 0000000..ebf8587
--- /dev/null
+++ b/plugins/light_sensor/light_sensor.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+#Copyright (c) 2011 Walter Bender
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+
+from gettext import gettext as _
+
+from plugins.plugin import Plugin
+
+from TurtleArt.tapalette import make_palette
+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')
+
+
+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):
+ # set up light-sensor specific blocks
+ palette = make_palette('extras',
+ colors=["#FF0000", "#A00000"],
+ help_string=_('Palette of extra options'),
+ position=8,
+ translation=_('extras'))
+ '''
+ palette = make_palette('sensor',
+ colors=["#FF6060", "#A06060"],
+ help_string=_('Palette of sensor blocks'),
+ position=6)
+ '''
+
+ if self._status:
+ palette.add_block('lightsensor',
+ hidden=True,
+ style='box-style',
+ label=_('brightness'),
+ value_block=True,
+ help_string=\
+ _('light level detected by light sensor'),
+ prim_name='lightsensor')
+ else:
+ palette.add_block('lightsensor',
+ style='box-style',
+ label=_('brightness'),
+ value_block=True,
+ help_string=\
+ _('light level detected by light sensor'),
+ hidden=True,
+ prim_name='lightsensor')
+
+ self._parent.lc.def_prim(
+ 'lightsensor', 0,
+ 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
+
+ def prim_lightsensor(self):
+ if not self._status:
+ return -1
+ else:
+ fh = open(LIGHT_SENSOR_DEVICE)
+ string = fh.read()
+ fh.close()
+ 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/device.py b/plugins/rfid/device.py
new file mode 100644
index 0000000..04a82b2
--- /dev/null
+++ b/plugins/rfid/device.py
@@ -0,0 +1,61 @@
+import gobject
+
+class RFIDDevice(gobject.GObject):
+ """
+ Ancestor class for every supported device.
+ The main class for the device driver must be called "RFIDReader".
+ """
+ # signal "tag-read" has to be emitted when a tag has been read.
+ # The handler must receive the ISO-11784 hex value of the tag.
+ # signal "disconnected" has to be emitted when the device is
+ # unplugged or an error has been detected.
+ __gsignals__ = {
+ 'tag-read': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_STRING,)),
+ 'disconnected': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_STRING,))
+ }
+ def __init__(self):
+ """
+ Initializer. Subclasses must call this method.
+ """
+ self.__gobject_init__()
+
+ def get_present(self):
+ """
+ This method must detect if the device is present, returning True if so,
+ or False otherwise.
+ """
+ raise NotImplementedError
+
+ def get_version(self):
+ """
+ Returns a descriptive text of the device.
+ """
+ raise NotImplementedError
+
+ def do_connect(self):
+ """
+ Connects to the device.
+ Must return True if successfull, False otherwise.
+ """
+ raise NotImplementedError
+
+ def do_disconnect(self):
+ """
+ Disconnects from the device.
+ """
+ raise NotImplementedError
+
+ def read_tag(self):
+ """
+ Returns the 64 bit data in hex format of the last read tag.
+ """
+ raise NotImplementedError
+
+ def write_tag(self, hex_val):
+ """
+ Could be implemented if the device is capable of writing tags.
+ Receives the hex value (according to ISO 11784) to be written.
+ Returns True if successfull or False if something went wrong.
+ """
diff --git a/plugins/rfid/icons/extrasoff.svg b/plugins/rfid/icons/extrasoff.svg
new file mode 100644
index 0000000..a956f03
--- /dev/null
+++ b/plugins/rfid/icons/extrasoff.svg
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <rect
+ width="42.345783"
+ height="42.345783"
+ x="6.3271084"
+ y="6.3271084"
+ id="rect3016"
+ style="fill:#282828;fill-opacity:1;stroke:#282828;stroke-width:2.6542182;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="g3">
+ <polygon
+ points="26.891,12.363 17.238,16.369 14.659,4.975 20.555,2.531 "
+ id="polygon5"
+ style="fill:#ffffff" />
+ <polygon
+ points="42.646,26.88 38.649,17.228 50.04,14.654 52.477,20.55 "
+ id="polygon7"
+ style="fill:#ffffff" />
+ <polygon
+ points="28.117,42.645 37.775,38.645 40.349,50.029 34.453,52.471 "
+ id="polygon9"
+ style="fill:#ffffff" />
+ <polygon
+ points="37.824,16.315 28.171,12.309 34.394,2.439 40.295,4.882 "
+ id="polygon11"
+ style="fill:#ffffff" />
+ <polygon
+ points="38.628,37.791 42.623,28.139 52.493,34.365 50.055,40.258 "
+ id="polygon13"
+ style="fill:#ffffff" />
+ <polygon
+ points="16.385,37.76 12.391,28.105 2.515,34.34 4.953,40.234 "
+ id="polygon15"
+ style="fill:#ffffff" />
+ <polygon
+ points="12.319,26.875 16.32,17.216 4.936,14.643 2.493,20.539 "
+ id="polygon17"
+ style="fill:#ffffff" />
+ <polygon
+ points="26.939,42.623 17.287,38.629 14.719,50.018 20.609,52.461 "
+ id="polygon19"
+ style="fill:#ffffff" />
+ <path
+ d="m 39.925,22.352 c 2.845,6.863 -0.412,14.728 -7.274,17.574 -6.867,2.85 -14.734,-0.418 -17.578,-7.281 -2.84,-6.862 0.418,-14.733 7.279,-17.572 6.862,-2.845 14.734,0.417 17.573,7.279 z"
+ id="path21"
+ style="fill:none;stroke:#ffffff;stroke-width:11.69439983" />
+ </g>
+</svg>
diff --git a/plugins/rfid/icons/extrason.svg b/plugins/rfid/icons/extrason.svg
new file mode 100644
index 0000000..7ee08bf
--- /dev/null
+++ b/plugins/rfid/icons/extrason.svg
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.0"
+ width="55"
+ height="55"
+ id="svg2">
+ <metadata
+ id="metadata20">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5">
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2431"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2428"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient3172"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3166">
+ <stop
+ id="stop3168"
+ style="stop-color:#ffffff;stop-opacity:1"
+ offset="0" />
+ <stop
+ id="stop3170"
+ style="stop-color:#ffff00;stop-opacity:1"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2557"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,9.2560985,9.9123239)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2561"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,8.962951,22.783517)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2461"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,32.193732)" />
+ <linearGradient
+ x1="0"
+ y1="22"
+ x2="74"
+ y2="22"
+ id="linearGradient2463"
+ xlink:href="#linearGradient3166"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.3333,0,0,0.3333,27.031478,45.064925)" />
+ </defs>
+ <rect
+ width="55"
+ height="55"
+ rx="0"
+ x="0"
+ y="0"
+ id="rect2839"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:evenodd;stroke:none" />
+ <g
+ id="g3799">
+ <polygon
+ points="20.555,2.531 26.891,12.363 17.238,16.369 14.659,4.975 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon5"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="52.477,20.55 42.646,26.88 38.649,17.228 50.04,14.654 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon7"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="34.453,52.471 28.117,42.645 37.775,38.645 40.349,50.029 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon9"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="40.295,4.882 37.824,16.315 28.171,12.309 34.394,2.439 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon11"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="50.055,40.258 38.628,37.791 42.623,28.139 52.493,34.365 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon13"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="4.953,40.234 16.385,37.76 12.391,28.105 2.515,34.34 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon15"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="2.493,20.539 12.319,26.875 16.32,17.216 4.936,14.643 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon17"
+ style="fill:#ff0000;fill-opacity:1" />
+ <polygon
+ points="20.609,52.461 26.939,42.623 17.287,38.629 14.719,50.018 "
+ transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)"
+ id="polygon19"
+ style="fill:#ff0000;fill-opacity:1" />
+ <path
+ d="m 35.45648,24.236169 c 1.8208,4.389511 -0.26368,9.419891 -4.65536,11.240166 -4.39488,1.822833 -9.42976,-0.267349 -11.24992,-4.65686 -1.8176,-4.388871 0.26752,-9.423089 4.65856,-11.238887 4.39168,-1.819635 9.42976,0.26671 11.24672,4.655581 z"
+ id="path21"
+ style="fill:none;stroke:#ff0000;stroke-width:7.48202181;stroke-opacity:1" />
+ </g>
+</svg>
diff --git a/plugins/rfid/icons/sensoroff.svg b/plugins/rfid/icons/sensoroff.svg
new file mode 100644
index 0000000..0a16670
--- /dev/null
+++ b/plugins/rfid/icons/sensoroff.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata15"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs13" />
+<rect
+ width="42.763924"
+ height="42.763924"
+ x="6.1180382"
+ y="6.1180382"
+ id="rect2986"
+ style="fill:#282828;fill-opacity:1;stroke:#282828;stroke-width:2.23607516;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><g
+ transform="matrix(0.87078705,0,0,0.87078705,3.2821055,2.9298726)"
+ id="network-wired_1_"
+ style="display:block">
+ <g
+ id="network-wired">
+ <line
+ id="line3076"
+ y2="23.993"
+ y1="32.438999"
+ x2="16.966999"
+ x1="16.966999"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round" />
+ <line
+ id="line3078"
+ y2="28.215"
+ y1="28.215"
+ x2="34.938"
+ x1="29.636999"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round" />
+
+ <rect
+ width="9.7939997"
+ height="7.599"
+ x="42.157001"
+ y="24.312"
+ id="rect3080"
+ style="fill:#ffffff;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round" />
+ <path
+ d="m 16.967,23.993 c 0,-2.334 -1.892,-4.224 -4.224,-4.224 -2.332,0 -4.223,1.889 -4.223,4.224"
+ id="path3082"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 25.413,32.439 c 0,2.334 -1.891,4.224 -4.224,4.224 -2.332,0 -4.223,-1.89 -4.223,-4.224"
+ id="path3084"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 25.413,32.439 c 0,-2.332 1.893,-4.226 4.224,-4.226"
+ id="path3086"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ <path
+ d="m 8.52,23.993 c 0,2.332 -1.892,4.222 -4.223,4.222"
+ id="path3088"
+ style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+
+ <rect
+ width="14.477"
+ height="11.35"
+ x="31.945"
+ y="22.438"
+ id="rect3090"
+ style="fill:#ffffff;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" />
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/plugins/rfid/icons/sensoron.svg b/plugins/rfid/icons/sensoron.svg
new file mode 100644
index 0000000..d756860
--- /dev/null
+++ b/plugins/rfid/icons/sensoron.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="55"
+ height="55"
+ viewBox="0 0 55 55"
+ id="svg2"
+ xml:space="preserve"><metadata
+ id="metadata15"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs13" /><rect
+ width="55"
+ height="55"
+ x="0"
+ y="0"
+ id="rect3269"
+ style="fill:#ffd200;fill-opacity:1;fill-rule:nonzero;stroke:none" /><g
+ transform="translate(0.27777716,18.796296)"
+ id="g4054"><line
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-opacity:1"
+ x1="17.778971"
+ x2="17.778971"
+ y1="12.381037"
+ y2="5.0263696"
+ id="line3076" /><line
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-opacity:1"
+ x1="28.811842"
+ x2="33.427887"
+ y1="8.7028332"
+ y2="8.7028332"
+ id="line3078" /><rect
+ width="8.5284882"
+ height="6.6171107"
+ x="39.7141"
+ y="5.3041511"
+ id="rect3080"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:1.95927083;stroke-linecap:round;stroke-opacity:1" /><path
+ d="m 17.778971,5.0263697 c 0,-2.032417 -1.647529,-3.6782045 -3.678204,-3.6782045 -2.030675,0 -3.677334,1.6449167 -3.677334,3.6782045"
+ id="path3082"
+ style="fill:none;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 25.133639,12.381037 c 0,2.032417 -1.646658,3.678205 -3.678205,3.678205 -2.030675,0 -3.677333,-1.645788 -3.677333,-3.678205"
+ id="path3084"
+ style="fill:none;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 25.133639,12.381037 c 0,-2.030675 1.6484,-3.679946 3.678204,-3.679946"
+ id="path3086"
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path
+ d="m 10.423433,5.0263697 c 0,2.0306754 -1.6475288,3.6764629 -3.6773334,3.6764629"
+ id="path3088"
+ style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><rect
+ width="12.606384"
+ height="9.8834333"
+ x="30.821619"
+ y="3.6722956"
+ id="rect3090"
+ style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04775476;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/plugins/rfid/rfid.py b/plugins/rfid/rfid.py
new file mode 100644
index 0000000..ae092e1
--- /dev/null
+++ b/plugins/rfid/rfid.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+#Copyright (C) 2010,11 Emiliano Pastorino <epastorino@plan.ceibal.edu.uy>
+#Copyright (c) 2011 Walter Bender
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import dbus
+from dbus.mainloop.glib import DBusGMainLoop
+from gettext import gettext as _
+
+from plugins.rfid.rfidutils import strhex2bin, strbin2dec, find_device
+from plugins.plugin import Plugin
+
+from TurtleArt.tapalette import make_palette
+from TurtleArt.tautils import debug_output
+from TurtleArt.taprimitive import Primitive
+from TurtleArt.tatype import TYPE_STRING
+
+import logging
+_logger = logging.getLogger('turtleart-activity RFID plugin')
+
+HAL_SERVICE = 'org.freedesktop.Hal'
+HAL_MGR_PATH = '/org/freedesktop/Hal/Manager'
+HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager'
+HAL_DEV_IFACE = 'org.freedesktop.Hal.Device'
+REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\
+ 'a-z,A-Z,0-9,_]*serial_usb_[0-9]'
+
+
+class Rfid(Plugin):
+
+ def __init__(self, parent):
+ Plugin.__init__(self)
+ self._parent = parent
+ self._status = False
+
+ """
+ The following code will initialize a USB RFID reader. Please note that
+ in order to make this initialization function work, it is necessary to
+ set the permission for the ttyUSB device to 0666. You can do this by
+ adding a rule to /etc/udev/rules.d
+
+ As root (using sudo or su), copy the following text into a new file in
+ /etc/udev/rules.d/94-ttyUSB-rules
+
+ KERNEL=="ttyUSB[0-9]",MODE="0666"
+
+ You only have to do this once.
+ """
+
+ self.rfid_connected = False
+ self.rfid_device = find_device(path=parent.path)
+ self.rfid_idn = ''
+
+ if self.rfid_device is not None:
+ _logger.info("RFID device found")
+ self.rfid_connected = self.rfid_device.do_connect()
+ if self.rfid_connected:
+ self.rfid_device.connect("tag-read", self._tag_read_cb)
+ self.rfid_device.connect("disconnected", self._disconnected_cb)
+
+ loop = DBusGMainLoop()
+ bus = dbus.SystemBus(mainloop=loop)
+ hmgr_iface = dbus.Interface(bus.get_object(HAL_SERVICE,
+ HAL_MGR_PATH), HAL_MGR_IFACE)
+
+ hmgr_iface.connect_to_signal('DeviceAdded', self._device_added_cb)
+
+ self._status = True
+
+ def setup(self):
+ # set up RFID-specific blocks
+ palette = make_palette('extras',
+ colors=["#FF0000", "#A00000"],
+ help_string=_('Palette of extra options'),
+ position=8,
+ translation=_('extras'))
+ '''
+ palette = make_palette('sensor',
+ colors=["#FF6060", "#A06060"],
+ help_string=_('Palette of sensor blocks'),
+ position=6)
+ '''
+
+ if self._status:
+ palette.add_block('rfid',
+ hidden=True,
+ style='box-style',
+ label=_('RFID'),
+ help_string=_('read value from RFID device'),
+ value_block=True,
+ prim_name='rfid')
+ else:
+ palette.add_block('rfid',
+ hidden=True,
+ style='box-style',
+ label=_('RFID'),
+ help_string=_('read value from RFID device'),
+ value_block=True,
+ prim_name='rfid')
+
+ self._parent.lc.def_prim(
+ 'rfid', 0,
+ 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)))
+ return self._status
+
+ def _device_added_cb(self, path):
+ """
+ Called from hal connection when a new device is plugged.
+ """
+ if not self.rfid_connected:
+ self.rfid_device = find_device(path=parent.path)
+ _logger.debug("DEVICE_ADDED: %s" % self.rfid_device)
+ if self.rfid_device is not None:
+ _logger.debug("DEVICE_ADDED: RFID device is not None!")
+ self.rfid_connected = self._device.do_connect()
+ if self.rfid_connected:
+ _logger.debug("DEVICE_ADDED: Connected!")
+ self.rfid_device.connect("tag-read", self._tag_read_cb)
+ self.rfid_device.connect("disconnected", self._disconnected_cb)
+
+ def _disconnected_cb(self, device, text):
+ """
+ Called when the device is disconnected.
+ """
+ self.rfid_connected = False
+ self.rfid_device = None
+
+ def _tag_read_cb(self, device, tagid):
+ """
+ Callback for "tag-read" signal. Receives the read tag id.
+ """
+ idbin = strhex2bin(tagid)
+ self.rfid_idn = strbin2dec(idbin[26:64])
+ while self.rfid_idn.__len__() < 9:
+ self.rfid_idn = '0' + self.rfid_idn
+ print tagid, idbin, self.rfid_idn
+ self.tw.lc.update_label_value('rfid', self.rfid_idn)
+
+ # Block primitives used in talogo
+
+ def prim_read_rfid(self):
+ if self._status:
+ return self.rfid_idn
+ else:
+ return '0'
+
+ def after_rfid(self):
+ if self._parent.lc.update_values:
+ self._parent.lc.update_label_value('rfid', self.rfid_idn)
diff --git a/plugins/rfid/rfidrweusb.py b/plugins/rfid/rfidrweusb.py
new file mode 100644
index 0000000..bd12631
--- /dev/null
+++ b/plugins/rfid/rfidrweusb.py
@@ -0,0 +1,200 @@
+from device import RFIDDevice
+from serial import Serial
+import dbus
+from dbus.mainloop.glib import DBusGMainLoop
+import gobject
+from time import sleep
+import utils
+
+HAL_SERVICE = 'org.freedesktop.Hal'
+HAL_MGR_PATH = '/org/freedesktop/Hal/Manager'
+HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager'
+HAL_DEV_IFACE = 'org.freedesktop.Hal.Device'
+REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\
+ 'a-z,A-Z,0-9,_]*serial_usb_[0-9]'
+
+VERSIONS = ['301']
+
+class RFIDReader(RFIDDevice):
+ """
+ RFIDRW-E-W interface.
+ """
+
+ def __init__(self):
+
+ RFIDDevice.__init__(self)
+ self.last_tag = ""
+ self.tags = []
+ self.ser = Serial()
+ self.device = ''
+ self.device_path = ''
+ self._connected = False
+
+ loop = DBusGMainLoop()
+ self.bus = dbus.SystemBus(mainloop=loop)
+ hmgr_iface = dbus.Interface(self.bus.get_object(HAL_SERVICE,
+ HAL_MGR_PATH), HAL_MGR_IFACE)
+
+ hmgr_iface.connect_to_signal('DeviceRemoved', self._device_removed_cb)
+
+ def get_present(self):
+ """
+ Checks if RFID-RW-USB device is present.
+ Returns True if so, False otherwise.
+ """
+ hmgr_if = dbus.Interface(self.bus.get_object(HAL_SERVICE, HAL_MGR_PATH),
+ HAL_MGR_IFACE)
+ serialusb_devices = set(hmgr_if.FindDeviceStringMatch('serial.type',
+ 'usb')) & set(hmgr_if.FindDeviceStringMatch(
+ 'info.subsystem', 'tty'))
+ for i in serialusb_devices:
+ serialusb_if = dbus.Interface(self.bus.get_object(HAL_SERVICE, i),
+ HAL_DEV_IFACE)
+ if serialusb_if.PropertyExists('info.parent'):
+ parent_udi = str(serialusb_if.GetProperty('info.parent'))
+ parent = dbus.Interface(self.bus.get_object(HAL_SERVICE,
+ parent_udi), HAL_DEV_IFACE)
+ if parent.PropertyExists('info.linux.driver') and \
+ str(parent.GetProperty('info.linux.driver')) == 'ftdi_sio':
+ device = str(serialusb_if.GetProperty('linux.device_file'))
+ ser = Serial(device, 9600, timeout=0.1)
+ ser.read(100)
+ ser.write('v')
+ ser.write('e')
+ ser.write('r')
+ ser.write('\x0D')
+ resp = ser.read(4)
+ if resp[0:-1] in VERSIONS:
+ self.device = device
+ self.device_path = i
+ return True
+ return False
+
+ def do_connect(self):
+ """
+ Connects to the device.
+ Returns True if successfull, False otherwise.
+ """
+ retval = False
+ if self.get_present():
+ try:
+ self.ser = Serial(self.device, 9600, timeout=0.1)
+ self._connected = True
+ if self._select_animal_tag:
+ #gobject.idle_add(self._loop)
+ gobject.timeout_add(1000, self._loop)
+ retval = True
+ except:
+ self._connected = False
+ return retval
+
+ def do_disconnect(self):
+ """
+ Disconnect from the device.
+ """
+ self.ser.close()
+ self._connected = False
+
+ def read_tag(self):
+ """
+ Returns the last read value.
+ """
+ return self.last_tag
+
+ def _select_animal_tag(self):
+ """
+ Sends the "Select Tag 2" (animal tag) command to the device.
+ """
+ self.ser.read(100)
+ self.ser.write('s')
+ self.ser.write('t')
+ self.ser.write('2')
+ self.ser.write('\x0d')
+ resp = self.ser.read(3)[0:-1]
+ if resp == 'OK':
+ return True
+ return False
+
+ def get_version(self):
+ """
+ Sends the version command to the device and returns
+ a string with the device version.
+ """
+ #self.ser.flushInput()
+ ver = "???"
+ self.ser.read(100)
+ self.ser.write('v')
+ self.ser.write('e')
+ self.ser.write('r')
+ self.ser.write('\x0d')
+ resp = self.ser.read(4)[0:-1]
+ if resp in VERSIONS:
+ return "RFIDRW-E-USB " + resp
+ return ver
+
+ def _device_removed_cb(self, path):
+ """
+ Called when a device is removed.
+ Checks if the removed device is itself and emits the "disconnected"
+ signal if so.
+ """
+ if path == self.device_path:
+ self.device_path = ''
+ self.ser.close()
+ self._connected = False
+ self.tags = []
+ self.emit("disconnected","RFID-RW-USB")
+
+ def _loop(self):
+ """
+ Threaded loop for reading data from the device.
+ """
+ if not self._connected:
+ return False
+
+ self.ser.read(100)
+ self.ser.write('r')
+ self.ser.write('a')
+ self.ser.write('t')
+ self.ser.write('\x0d')
+ resp = self.ser.read(33)[0:-1].split('_')
+ if resp.__len__() is not 6 or resp in self.tags:
+ return True
+
+ self.tags.append(resp)
+ anbit_bin = utils.dec2bin(int(resp[2]))
+ reserved_bin = '00000000000000'
+ databit_bin = utils.dec2bin(int(resp[3]))
+ country_bin = utils.dec2bin(int(resp[0]))
+ while country_bin.__len__() < 10:
+ country_bin = '0' + country_bin
+ id_bin = utils.dec2bin(int(resp[1]))
+ while id_bin.__len__() < 10:
+ id_bin = '0' + id_bin
+
+ tag_bin = anbit_bin + reserved_bin + databit_bin + country_bin + id_bin
+ data = utils.bin2hex(tag_bin)
+ self.emit("tag-read", data)
+ self.last_tag = data
+ #sleep(1)
+ return True
+
+# Testing
+#if __name__ == '__main__':
+# def handler(device, idhex):
+# """
+# Handler for "tag-read" signal.
+# Prints the tag id.
+# """
+# print "ID: ", idhex
+#
+# dev = RFIDReader()
+# if dev.get_present():
+# print "SIPI!"
+# dev.do_connect()
+# dev.connect('tag-read', handler)
+# else:
+# print "Not connected"
+#
+# mloop = gobject.MainLoop()
+# mloop.run()
diff --git a/plugins/rfid/rfidutils.py b/plugins/rfid/rfidutils.py
new file mode 100644
index 0000000..a00e518
--- /dev/null
+++ b/plugins/rfid/rfidutils.py
@@ -0,0 +1,127 @@
+# utils.py - Helper functions for tis2000.py
+# Copyright (C) 2010 Emiliano Pastorino <epastorino@plan.ceibal.edu.uy>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+
+def find_device(path=None):
+ """
+ Search for devices.
+ Return a device instance or None.
+ """
+ device = None
+ if path is not None:
+ path = os.path.join(path, 'plugins/rfid')
+ else:
+ path = os.path.join('.', 'plugins/rfid')
+ for i in os.listdir(path):
+ if not os.path.isdir(os.path.join(path, i)):
+ try:
+ _tempmod = __import__('rfid.%s'%i.split('.')[0], globals(),
+ locals(), ['RFIDReader'], -1)
+ devtemp = _tempmod.RFIDReader()
+ if devtemp.get_present() == True:
+ device = devtemp
+ except Exception, e:
+ # logging.error("FIND_DEVICE: %s: %s"%(i, e))
+ pass
+ if device is None:
+ logging.debug("No RFID device found")
+ return device
+
+
+def strhex2bin(strhex):
+ """
+ Convert a string representing an hex value into a
+ string representing the same value in binary format.
+ """
+ dic = { '0':"0000",
+ '1':"0001",
+ '2':"0010",
+ '3':"0011",
+ '4':"0100",
+ '5':"0101",
+ '6':"0110",
+ '7':"0111",
+ '8':"1000",
+ '9':"1001",
+ 'A':"1010",
+ 'B':"1011",
+ 'C':"1100",
+ 'D':"1101",
+ 'E':"1110",
+ 'F':"1111"
+ }
+ binstr = ""
+ for i in strhex:
+ binstr = binstr + dic[i.upper()]
+ return binstr
+
+def strbin2dec(strbin):
+ """
+ Convert a string representing a binary value into a
+ string representing the same value in decimal format.
+ """
+ strdec = "0"
+ for i in range(1, strbin.__len__()+1):
+ strdec = str(int(strdec)+int(strbin[-i])*int(pow(2, i-1)))
+ return strdec
+
+def dec2bin(ndec):
+ """
+ Convert a decimal number into a string representing
+ the same value in binary format.
+ """
+ if ndec < 1:
+ return "0"
+ binary = []
+ while ndec != 0:
+ binary.append(ndec%2)
+ ndec = ndec/2
+ strbin = ""
+ binary.reverse()
+ for i in binary:
+ strbin = strbin+str(i)
+ return strbin
+
+def bin2hex(strbin):
+ """
+ Convert a string representing a binary number into a string
+ representing the same value in hexadecimal format.
+ """
+ dic = { "0000":"0",
+ "0001":"1",
+ "0010":"2",
+ "0011":"3",
+ "0100":"4",
+ "0101":"5",
+ "0110":"6",
+ "0111":"7",
+ "1000":"8",
+ "1001":"9",
+ "1010":"A",
+ "1011":"B",
+ "1100":"C",
+ "1101":"D",
+ "1110":"E",
+ "1111":"F"
+ }
+ while strbin.__len__()%4 != 0:
+ strbin = '0' + strbin
+ strh = ""
+ for i in range(0, strbin.__len__()/4):
+ strh = strh + dic[str(strbin[i*4:i*4+4])]
+ return strh
diff --git a/plugins/rfid/serial/__init__.py b/plugins/rfid/serial/__init__.py
new file mode 100644
index 0000000..681ad5c
--- /dev/null
+++ b/plugins/rfid/serial/__init__.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+#portable serial port access with python
+#this is a wrapper module for different platform implementations
+#
+# (C)2001-2002 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+
+VERSION = '2.4'
+
+import sys
+
+if sys.platform == 'cli':
+ from serialcli import *
+else:
+ import os
+ #chose an implementation, depending on os
+ if os.name == 'nt': #sys.platform == 'win32':
+ from serialwin32 import *
+ elif os.name == 'posix':
+ from serialposix import *
+ elif os.name == 'java':
+ from serialjava import *
+ else:
+ raise Exception("Sorry: no implementation for your platform ('%s') available" % os.name)
+
diff --git a/plugins/rfid/serial/serialposix.py b/plugins/rfid/serial/serialposix.py
new file mode 100644
index 0000000..174e2f7
--- /dev/null
+++ b/plugins/rfid/serial/serialposix.py
@@ -0,0 +1,492 @@
+#!/usr/bin/env python
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# module for serial IO for POSIX compatible systems, like Linux
+# see __init__.py
+#
+# (C) 2001-2008 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+#
+# parts based on code from Grant B. Edwards <grante@visi.com>:
+# ftp://ftp.visi.com/users/grante/python/PosixSerial.py
+# references: http://www.easysw.com/~mike/serial/serial.html
+
+import sys, os, fcntl, termios, struct, select, errno
+from serialutil import *
+
+#Do check the Python version as some constants have moved.
+if (sys.hexversion < 0x020100f0):
+ import TERMIOS
+else:
+ TERMIOS = termios
+
+if (sys.hexversion < 0x020200f0):
+ import FCNTL
+else:
+ FCNTL = fcntl
+
+#try to detect the os so that a device can be selected...
+plat = sys.platform.lower()
+
+if plat[:5] == 'linux': #Linux (confirmed)
+ def device(port):
+ return '/dev/ttyS%d' % port
+
+elif plat == 'cygwin': #cywin/win32 (confirmed)
+ def device(port):
+ return '/dev/com%d' % (port + 1)
+
+elif plat == 'openbsd3': #BSD (confirmed)
+ def device(port):
+ return '/dev/ttyp%d' % port
+
+elif plat[:3] == 'bsd' or \
+ plat[:7] == 'freebsd' or \
+ plat[:7] == 'openbsd' or \
+ plat[:6] == 'darwin': #BSD (confirmed for freebsd4: cuaa%d)
+ def device(port):
+ return '/dev/cuad%d' % port
+
+elif plat[:6] == 'netbsd': #NetBSD 1.6 testing by Erk
+ def device(port):
+ return '/dev/dty%02d' % port
+
+elif plat[:4] == 'irix': #IRIX (partialy tested)
+ def device(port):
+ return '/dev/ttyf%d' % (port+1) #XXX different device names depending on flow control
+
+elif plat[:2] == 'hp': #HP-UX (not tested)
+ def device(port):
+ return '/dev/tty%dp0' % (port+1)
+
+elif plat[:5] == 'sunos': #Solaris/SunOS (confirmed)
+ def device(port):
+ return '/dev/tty%c' % (ord('a')+port)
+
+elif plat[:3] == 'aix': #aix
+ def device(port):
+ return '/dev/tty%d' % (port)
+
+else:
+ #platform detection has failed...
+ print """don't know how to number ttys on this system.
+! Use an explicit path (eg /dev/ttyS1) or send this information to
+! the author of this module:
+
+sys.platform = %r
+os.name = %r
+serialposix.py version = %s
+
+also add the device name of the serial port and where the
+counting starts for the first serial port.
+e.g. 'first serial port: /dev/ttyS0'
+and with a bit luck you can get this module running...
+""" % (sys.platform, os.name, VERSION)
+ #no exception, just continue with a brave attempt to build a device name
+ #even if the device name is not correct for the platform it has chances
+ #to work using a string with the real device name as port paramter.
+ def device(portum):
+ return '/dev/ttyS%d' % portnum
+ #~ raise Exception, "this module does not run on this platform, sorry."
+
+#whats up with "aix", "beos", ....
+#they should work, just need to know the device names.
+
+
+#load some constants for later use.
+#try to use values from TERMIOS, use defaults from linux otherwise
+TIOCMGET = hasattr(TERMIOS, 'TIOCMGET') and TERMIOS.TIOCMGET or 0x5415
+TIOCMBIS = hasattr(TERMIOS, 'TIOCMBIS') and TERMIOS.TIOCMBIS or 0x5416
+TIOCMBIC = hasattr(TERMIOS, 'TIOCMBIC') and TERMIOS.TIOCMBIC or 0x5417
+TIOCMSET = hasattr(TERMIOS, 'TIOCMSET') and TERMIOS.TIOCMSET or 0x5418
+
+#TIOCM_LE = hasattr(TERMIOS, 'TIOCM_LE') and TERMIOS.TIOCM_LE or 0x001
+TIOCM_DTR = hasattr(TERMIOS, 'TIOCM_DTR') and TERMIOS.TIOCM_DTR or 0x002
+TIOCM_RTS = hasattr(TERMIOS, 'TIOCM_RTS') and TERMIOS.TIOCM_RTS or 0x004
+#TIOCM_ST = hasattr(TERMIOS, 'TIOCM_ST') and TERMIOS.TIOCM_ST or 0x008
+#TIOCM_SR = hasattr(TERMIOS, 'TIOCM_SR') and TERMIOS.TIOCM_SR or 0x010
+
+TIOCM_CTS = hasattr(TERMIOS, 'TIOCM_CTS') and TERMIOS.TIOCM_CTS or 0x020
+TIOCM_CAR = hasattr(TERMIOS, 'TIOCM_CAR') and TERMIOS.TIOCM_CAR or 0x040
+TIOCM_RNG = hasattr(TERMIOS, 'TIOCM_RNG') and TERMIOS.TIOCM_RNG or 0x080
+TIOCM_DSR = hasattr(TERMIOS, 'TIOCM_DSR') and TERMIOS.TIOCM_DSR or 0x100
+TIOCM_CD = hasattr(TERMIOS, 'TIOCM_CD') and TERMIOS.TIOCM_CD or TIOCM_CAR
+TIOCM_RI = hasattr(TERMIOS, 'TIOCM_RI') and TERMIOS.TIOCM_RI or TIOCM_RNG
+#TIOCM_OUT1 = hasattr(TERMIOS, 'TIOCM_OUT1') and TERMIOS.TIOCM_OUT1 or 0x2000
+#TIOCM_OUT2 = hasattr(TERMIOS, 'TIOCM_OUT2') and TERMIOS.TIOCM_OUT2 or 0x4000
+TIOCINQ = hasattr(TERMIOS, 'FIONREAD') and TERMIOS.FIONREAD or 0x541B
+
+TIOCM_zero_str = struct.pack('I', 0)
+TIOCM_RTS_str = struct.pack('I', TIOCM_RTS)
+TIOCM_DTR_str = struct.pack('I', TIOCM_DTR)
+
+TIOCSBRK = hasattr(TERMIOS, 'TIOCSBRK') and TERMIOS.TIOCSBRK or 0x5427
+TIOCCBRK = hasattr(TERMIOS, 'TIOCCBRK') and TERMIOS.TIOCCBRK or 0x5428
+
+ASYNC_SPD_MASK = 0x1030
+ASYNC_SPD_CUST = 0x0030
+
+baudrate_constants = {
+ 0: 0000000, # hang up
+ 50: 0000001,
+ 75: 0000002,
+ 110: 0000003,
+ 134: 0000004,
+ 150: 0000005,
+ 200: 0000006,
+ 300: 0000007,
+ 600: 0000010,
+ 1200: 0000011,
+ 1800: 0000012,
+ 2400: 0000013,
+ 4800: 0000014,
+ 9600: 0000015,
+ 19200: 0000016,
+ 38400: 0000017,
+ 57600: 0010001,
+ 115200: 0010002,
+ 230400: 0010003,
+ 460800: 0010004,
+ 500000: 0010005,
+ 576000: 0010006,
+ 921600: 0010007,
+ 1000000: 0010010,
+ 1152000: 0010011,
+ 1500000: 0010012,
+ 2000000: 0010013,
+ 2500000: 0010014,
+ 3000000: 0010015,
+ 3500000: 0010016,
+ 4000000: 0010017
+}
+
+
+class Serial(SerialBase):
+ """Serial port class POSIX implementation. Serial port configuration is
+ done with termios and fcntl. Runs on Linux and many other Un*x like
+ systems."""
+
+ def open(self):
+ """Open port with current settings. This may throw a SerialException
+ if the port cannot be opened."""
+ if self._port is None:
+ raise SerialException("Port must be configured before it can be used.")
+ self.fd = None
+ #open
+ try:
+ self.fd = os.open(self.portstr, os.O_RDWR|os.O_NOCTTY|os.O_NONBLOCK)
+ except Exception, msg:
+ self.fd = None
+ raise SerialException("could not open port %s: %s" % (self._port, msg))
+ #~ fcntl.fcntl(self.fd, FCNTL.F_SETFL, 0) #set blocking
+
+ try:
+ self._reconfigurePort()
+ except:
+ os.close(self.fd)
+ self.fd = None
+ else:
+ self._isOpen = True
+ #~ self.flushInput()
+
+
+ def _reconfigurePort(self):
+ """Set communication parameters on opened port."""
+ if self.fd is None:
+ raise SerialException("Can only operate on a valid port handle")
+ custom_baud = None
+
+ vmin = vtime = 0 #timeout is done via select
+ if self._interCharTimeout is not None:
+ vmin = 1
+ vtime = int(self._interCharTimeout * 10)
+ try:
+ iflag, oflag, cflag, lflag, ispeed, ospeed, cc = termios.tcgetattr(self.fd)
+ except termios.error, msg: #if a port is nonexistent but has a /dev file, it'll fail here
+ raise SerialException("Could not configure port: %s" % msg)
+ #set up raw mode / no echo / binary
+ cflag |= (TERMIOS.CLOCAL|TERMIOS.CREAD)
+ lflag &= ~(TERMIOS.ICANON|TERMIOS.ECHO|TERMIOS.ECHOE|TERMIOS.ECHOK|TERMIOS.ECHONL|
+ TERMIOS.ISIG|TERMIOS.IEXTEN) #|TERMIOS.ECHOPRT
+ for flag in ('ECHOCTL', 'ECHOKE'): #netbsd workaround for Erk
+ if hasattr(TERMIOS, flag):
+ lflag &= ~getattr(TERMIOS, flag)
+
+ oflag &= ~(TERMIOS.OPOST)
+ iflag &= ~(TERMIOS.INLCR|TERMIOS.IGNCR|TERMIOS.ICRNL|TERMIOS.IGNBRK)
+ if hasattr(TERMIOS, 'IUCLC'):
+ iflag &= ~TERMIOS.IUCLC
+ if hasattr(TERMIOS, 'PARMRK'):
+ iflag &= ~TERMIOS.PARMRK
+
+ #setup baudrate
+ try:
+ ispeed = ospeed = getattr(TERMIOS,'B%s' % (self._baudrate))
+ except AttributeError:
+ try:
+ ispeed = ospeed = baudrate_constants[self._baudrate]
+ except KeyError:
+ #~ raise ValueError('Invalid baud rate: %r' % self._baudrate)
+ # may need custom baud rate, it isnt in our list.
+ ispeed = ospeed = getattr(TERMIOS, 'B38400')
+ custom_baud = int(self._baudrate) # store for later
+
+ #setup char len
+ cflag &= ~TERMIOS.CSIZE
+ if self._bytesize == 8:
+ cflag |= TERMIOS.CS8
+ elif self._bytesize == 7:
+ cflag |= TERMIOS.CS7
+ elif self._bytesize == 6:
+ cflag |= TERMIOS.CS6
+ elif self._bytesize == 5:
+ cflag |= TERMIOS.CS5
+ else:
+ raise ValueError('Invalid char len: %r' % self._bytesize)
+ #setup stopbits
+ if self._stopbits == STOPBITS_ONE:
+ cflag &= ~(TERMIOS.CSTOPB)
+ elif self._stopbits == STOPBITS_TWO:
+ cflag |= (TERMIOS.CSTOPB)
+ else:
+ raise ValueError('Invalid stopit specification: %r' % self._stopbits)
+ #setup parity
+ iflag &= ~(TERMIOS.INPCK|TERMIOS.ISTRIP)
+ if self._parity == PARITY_NONE:
+ cflag &= ~(TERMIOS.PARENB|TERMIOS.PARODD)
+ elif self._parity == PARITY_EVEN:
+ cflag &= ~(TERMIOS.PARODD)
+ cflag |= (TERMIOS.PARENB)
+ elif self._parity == PARITY_ODD:
+ cflag |= (TERMIOS.PARENB|TERMIOS.PARODD)
+ else:
+ raise ValueError('Invalid parity: %r' % self._parity)
+ #setup flow control
+ #xonxoff
+ if hasattr(TERMIOS, 'IXANY'):
+ if self._xonxoff:
+ iflag |= (TERMIOS.IXON|TERMIOS.IXOFF) #|TERMIOS.IXANY)
+ else:
+ iflag &= ~(TERMIOS.IXON|TERMIOS.IXOFF|TERMIOS.IXANY)
+ else:
+ if self._xonxoff:
+ iflag |= (TERMIOS.IXON|TERMIOS.IXOFF)
+ else:
+ iflag &= ~(TERMIOS.IXON|TERMIOS.IXOFF)
+ #rtscts
+ if hasattr(TERMIOS, 'CRTSCTS'):
+ if self._rtscts:
+ cflag |= (TERMIOS.CRTSCTS)
+ else:
+ cflag &= ~(TERMIOS.CRTSCTS)
+ elif hasattr(TERMIOS, 'CNEW_RTSCTS'): #try it with alternate constant name
+ if self._rtscts:
+ cflag |= (TERMIOS.CNEW_RTSCTS)
+ else:
+ cflag &= ~(TERMIOS.CNEW_RTSCTS)
+ #XXX should there be a warning if setting up rtscts (and xonxoff etc) fails??
+
+ #buffer
+ #vmin "minimal number of characters to be read. = for non blocking"
+ if vmin < 0 or vmin > 255:
+ raise ValueError('Invalid vmin: %r ' % vmin)
+ cc[TERMIOS.VMIN] = vmin
+ #vtime
+ if vtime < 0 or vtime > 255:
+ raise ValueError('Invalid vtime: %r' % vtime)
+ cc[TERMIOS.VTIME] = vtime
+ #activate settings
+ termios.tcsetattr(self.fd, TERMIOS.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc])
+
+ # apply custom baud rate, if any
+ if custom_baud is not None:
+ import array
+ buf = array.array('i', [0] * 32)
+
+ # get serial_struct
+ FCNTL.ioctl(self.fd, TERMIOS.TIOCGSERIAL, buf)
+
+ # set custom divisor
+ buf[6] = buf[7] / custom_baud
+
+ # update flags
+ buf[4] &= ~ASYNC_SPD_MASK
+ buf[4] |= ASYNC_SPD_CUST
+
+ # set serial_struct
+ try:
+ res = FCNTL.ioctl(self.fd, TERMIOS.TIOCSSERIAL, buf)
+ except IOError:
+ raise ValueError('Failed to set custom baud rate: %r' % self._baudrate)
+
+ def close(self):
+ """Close port"""
+ if self._isOpen:
+ if self.fd is not None:
+ os.close(self.fd)
+ self.fd = None
+ self._isOpen = False
+
+ def makeDeviceName(self, port):
+ return device(port)
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ def inWaiting(self):
+ """Return the number of characters currently in the input buffer."""
+ #~ s = fcntl.ioctl(self.fd, TERMIOS.FIONREAD, TIOCM_zero_str)
+ s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str)
+ return struct.unpack('I',s)[0]
+
+ def read(self, size=1):
+ """Read size bytes from the serial port. If a timeout is set it may
+ return less characters as requested. With no timeout it will block
+ until the requested number of bytes is read."""
+ if self.fd is None: raise portNotOpenError
+ read = ''
+ inp = None
+ if size > 0:
+ while len(read) < size:
+ #print "\tread(): size",size, "have", len(read) #debug
+ ready,_,_ = select.select([self.fd],[],[], self._timeout)
+ if not ready:
+ break #timeout
+ buf = os.read(self.fd, size-len(read))
+ read = read + buf
+ if (self._timeout >= 0 or self._interCharTimeout > 0) and not buf:
+ break #early abort on timeout
+ return read
+
+ def write(self, data):
+ """Output the given string over the serial port."""
+ if self.fd is None: raise portNotOpenError
+ if not isinstance(data, str):
+ raise TypeError('expected str, got %s' % type(data))
+ t = len(data)
+ d = data
+ while t > 0:
+ try:
+ if self._writeTimeout is not None and self._writeTimeout > 0:
+ _,ready,_ = select.select([],[self.fd],[], self._writeTimeout)
+ if not ready:
+ raise writeTimeoutError
+ n = os.write(self.fd, d)
+ if self._writeTimeout is not None and self._writeTimeout > 0:
+ _,ready,_ = select.select([],[self.fd],[], self._writeTimeout)
+ if not ready:
+ raise writeTimeoutError
+ d = d[n:]
+ t = t - n
+ except OSError,v:
+ if v.errno != errno.EAGAIN:
+ raise
+
+ def flush(self):
+ """Flush of file like objects. In this case, wait until all data
+ is written."""
+ self.drainOutput()
+
+ def flushInput(self):
+ """Clear input buffer, discarding all that is in the buffer."""
+ if self.fd is None:
+ raise portNotOpenError
+ termios.tcflush(self.fd, TERMIOS.TCIFLUSH)
+
+ def flushOutput(self):
+ """Clear output buffer, aborting the current output and
+ discarding all that is in the buffer."""
+ if self.fd is None:
+ raise portNotOpenError
+ termios.tcflush(self.fd, TERMIOS.TCOFLUSH)
+
+ def sendBreak(self, duration=0.25):
+ """Send break condition. Timed, returns to idle state after given duration."""
+ if self.fd is None:
+ raise portNotOpenError
+ termios.tcsendbreak(self.fd, int(duration/0.25))
+
+ def setBreak(self, level=1):
+ """Set break: Controls TXD. When active, to transmitting is possible."""
+ if self.fd is None: raise portNotOpenError
+ if level:
+ fcntl.ioctl(self.fd, TIOCSBRK)
+ else:
+ fcntl.ioctl(self.fd, TIOCCBRK)
+
+ def setRTS(self, level=1):
+ """Set terminal status line: Request To Send"""
+ if self.fd is None: raise portNotOpenError
+ if level:
+ fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_RTS_str)
+ else:
+ fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_RTS_str)
+
+ def setDTR(self, level=1):
+ """Set terminal status line: Data Terminal Ready"""
+ if self.fd is None: raise portNotOpenError
+ if level:
+ fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str)
+ else:
+ fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_DTR_str)
+
+ def getCTS(self):
+ """Read terminal status line: Clear To Send"""
+ if self.fd is None: raise portNotOpenError
+ s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
+ return struct.unpack('I',s)[0] & TIOCM_CTS != 0
+
+ def getDSR(self):
+ """Read terminal status line: Data Set Ready"""
+ if self.fd is None: raise portNotOpenError
+ s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
+ return struct.unpack('I',s)[0] & TIOCM_DSR != 0
+
+ def getRI(self):
+ """Read terminal status line: Ring Indicator"""
+ if self.fd is None: raise portNotOpenError
+ s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
+ return struct.unpack('I',s)[0] & TIOCM_RI != 0
+
+ def getCD(self):
+ """Read terminal status line: Carrier Detect"""
+ if self.fd is None: raise portNotOpenError
+ s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
+ return struct.unpack('I',s)[0] & TIOCM_CD != 0
+
+ # - - platform specific - - - -
+
+ def drainOutput(self):
+ """internal - not portable!"""
+ if self.fd is None: raise portNotOpenError
+ termios.tcdrain(self.fd)
+
+ def nonblocking(self):
+ """internal - not portable!"""
+ if self.fd is None:
+ raise portNotOpenError
+ fcntl.fcntl(self.fd, FCNTL.F_SETFL, FCNTL.O_NONBLOCK)
+
+ def fileno(self):
+ """For easier of the serial port instance with select.
+ WARNING: this function is not portable to different platforms!"""
+ if self.fd is None: raise portNotOpenError
+ return self.fd
+
+if __name__ == '__main__':
+ s = Serial(0,
+ baudrate=19200, #baudrate
+ bytesize=EIGHTBITS, #number of databits
+ parity=PARITY_EVEN, #enable parity checking
+ stopbits=STOPBITS_ONE, #number of stopbits
+ timeout=3, #set a timeout value, None for waiting forever
+ xonxoff=0, #enable software flow control
+ rtscts=0, #enable RTS/CTS flow control
+ )
+ s.setRTS(1)
+ s.setDTR(1)
+ s.flushInput()
+ s.flushOutput()
+ s.write('hello')
+ print repr(s.read(5))
+ print s.inWaiting()
+ del s
+
diff --git a/plugins/rfid/serial/serialutil.py b/plugins/rfid/serial/serialutil.py
new file mode 100644
index 0000000..fd466f2
--- /dev/null
+++ b/plugins/rfid/serial/serialutil.py
@@ -0,0 +1,400 @@
+#! python
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# see __init__.py
+#
+# (C) 2001-2008 Chris Liechti <cliechti@gmx.net>
+# this is distributed under a free software license, see license.txt
+
+PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S'
+STOPBITS_ONE, STOPBITS_TWO = (1, 2)
+FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5,6,7,8)
+
+PARITY_NAMES = {
+ PARITY_NONE: 'None',
+ PARITY_EVEN: 'Even',
+ PARITY_ODD: 'Odd',
+ PARITY_MARK: 'Mark',
+ PARITY_SPACE:'Space',
+}
+
+XON = chr(17)
+XOFF = chr(19)
+
+#Python < 2.2.3 compatibility
+try:
+ True
+except:
+ True = 1
+ False = not True
+
+class SerialException(Exception):
+ """Base class for serial port related exceptions."""
+
+portNotOpenError = SerialException('Port not open')
+
+class SerialTimeoutException(SerialException):
+ """Write timeouts give an exception"""
+
+writeTimeoutError = SerialTimeoutException("Write timeout")
+
+class FileLike(object):
+ """An abstract file like class.
+
+ This class implements readline and readlines based on read and
+ writelines based on write.
+ This class is used to provide the above functions for to Serial
+ port objects.
+
+ Note that when the serial port was opened with _NO_ timeout that
+ readline blocks until it sees a newline (or the specified size is
+ reached) and that readlines would never return and therefore
+ refuses to work (it raises an exception in this case)!
+ """
+
+ def read(self, size): raise NotImplementedError
+ def write(self, s): raise NotImplementedError
+
+ def readline(self, size=None, eol='\n'):
+ """read a line which is terminated with end-of-line (eol) character
+ ('\n' by default) or until timeout"""
+ line = ''
+ while 1:
+ c = self.read(1)
+ if c:
+ line += c #not very efficient but lines are usually not that long
+ if c == eol:
+ break
+ if size is not None and len(line) >= size:
+ break
+ else:
+ break
+ return line
+
+ def readlines(self, sizehint=None, eol='\n'):
+ """read a list of lines, until timeout
+ sizehint is ignored"""
+ if self.timeout is None:
+ raise ValueError, "Serial port MUST have enabled timeout for this function!"
+ lines = []
+ while 1:
+ line = self.readline(eol=eol)
+ if line:
+ lines.append(line)
+ if line[-1] != eol: #was the line received with a timeout?
+ break
+ else:
+ break
+ return lines
+
+ def xreadlines(self, sizehint=None):
+ """just call readlines - here for compatibility"""
+ return self.readlines()
+
+ def writelines(self, sequence):
+ for line in sequence:
+ self.write(line)
+
+ def flush(self):
+ """flush of file like objects"""
+ pass
+
+ # iterator for e.g. "for line in Serial(0): ..." usage
+ def next(self):
+ line = self.readline()
+ if not line: raise StopIteration
+ return line
+
+ def __iter__(self):
+ return self
+
+
+class SerialBase(FileLike):
+ """Serial port base class. Provides __init__ function and properties to
+ get/set port settings."""
+
+ #default values, may be overriden in subclasses that do not support all values
+ BAUDRATES = (50,75,110,134,150,200,300,600,1200,1800,2400,4800,9600,
+ 19200,38400,57600,115200,230400,460800,500000,576000,921600,
+ 1000000,1152000,1500000,2000000,2500000,3000000,3500000,4000000)
+ BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS)
+ PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD)
+ STOPBITS = (STOPBITS_ONE, STOPBITS_TWO)
+
+ def __init__(self,
+ port = None, #number of device, numbering starts at
+ #zero. if everything fails, the user
+ #can specify a device string, note
+ #that this isn't portable anymore
+ #port will be opened if one is specified
+ baudrate=9600, #baudrate
+ bytesize=EIGHTBITS, #number of databits
+ parity=PARITY_NONE, #enable parity checking
+ stopbits=STOPBITS_ONE, #number of stopbits
+ timeout=None, #set a timeout value, None to wait forever
+ xonxoff=0, #enable software flow control
+ rtscts=0, #enable RTS/CTS flow control
+ writeTimeout=None, #set a timeout for writes
+ dsrdtr=None, #None: use rtscts setting, dsrdtr override if true or false
+ interCharTimeout=None #Inter-character timeout, None to disable
+ ):
+ """Initialize comm port object. If a port is given, then the port will be
+ opened immediately. Otherwise a Serial port object in closed state
+ is returned."""
+
+ self._isOpen = False
+ self._port = None #correct value is assigned below trough properties
+ self._baudrate = None #correct value is assigned below trough properties
+ self._bytesize = None #correct value is assigned below trough properties
+ self._parity = None #correct value is assigned below trough properties
+ self._stopbits = None #correct value is assigned below trough properties
+ self._timeout = None #correct value is assigned below trough properties
+ self._writeTimeout = None #correct value is assigned below trough properties
+ self._xonxoff = None #correct value is assigned below trough properties
+ self._rtscts = None #correct value is assigned below trough properties
+ self._dsrdtr = None #correct value is assigned below trough properties
+ self._interCharTimeout = None #correct value is assigned below trough properties
+
+ #assign values using get/set methods using the properties feature
+ self.port = port
+ self.baudrate = baudrate
+ self.bytesize = bytesize
+ self.parity = parity
+ self.stopbits = stopbits
+ self.timeout = timeout
+ self.writeTimeout = writeTimeout
+ self.xonxoff = xonxoff
+ self.rtscts = rtscts
+ self.dsrdtr = dsrdtr
+ self.interCharTimeout = interCharTimeout
+
+ if port is not None:
+ self.open()
+
+ def isOpen(self):
+ """Check if the port is opened."""
+ return self._isOpen
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ #TODO: these are not realy needed as the is the BAUDRATES etc attribute...
+ #maybe i remove them before the final release...
+
+ def getSupportedBaudrates(self):
+ return [(str(b), b) for b in self.BAUDRATES]
+
+ def getSupportedByteSizes(self):
+ return [(str(b), b) for b in self.BYTESIZES]
+
+ def getSupportedStopbits(self):
+ return [(str(b), b) for b in self.STOPBITS]
+
+ def getSupportedParities(self):
+ return [(PARITY_NAMES[b], b) for b in self.PARITIES]
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ def setPort(self, port):
+ """Change the port. The attribute portstr is set to a string that
+ contains the name of the port."""
+
+ was_open = self._isOpen
+ if was_open: self.close()
+ if port is not None:
+ if type(port) in [type(''), type(u'')]: #strings are taken directly
+ self.portstr = port
+ else:
+ self.portstr = self.makeDeviceName(port)
+ else:
+ self.portstr = None
+ self._port = port
+ if was_open: self.open()
+
+ def getPort(self):
+ """Get the current port setting. The value that was passed on init or using
+ setPort() is passed back. See also the attribute portstr which contains
+ the name of the port as a string."""
+ return self._port
+
+ port = property(getPort, setPort, doc="Port setting")
+
+
+ def setBaudrate(self, baudrate):
+ """Change baudrate. It raises a ValueError if the port is open and the
+ baudrate is not possible. If the port is closed, then tha value is
+ accepted and the exception is raised when the port is opened."""
+ #~ if baudrate not in self.BAUDRATES: raise ValueError("Not a valid baudrate: %r" % baudrate)
+ try:
+ self._baudrate = int(baudrate)
+ except TypeError:
+ raise ValueError("Not a valid baudrate: %r" % (baudrate,))
+ else:
+ if self._isOpen: self._reconfigurePort()
+
+ def getBaudrate(self):
+ """Get the current baudrate setting."""
+ return self._baudrate
+
+ baudrate = property(getBaudrate, setBaudrate, doc="Baudrate setting")
+
+
+ def setByteSize(self, bytesize):
+ """Change byte size."""
+ if bytesize not in self.BYTESIZES: raise ValueError("Not a valid byte size: %r" % (bytesize,))
+ self._bytesize = bytesize
+ if self._isOpen: self._reconfigurePort()
+
+ def getByteSize(self):
+ """Get the current byte size setting."""
+ return self._bytesize
+
+ bytesize = property(getByteSize, setByteSize, doc="Byte size setting")
+
+
+ def setParity(self, parity):
+ """Change parity setting."""
+ if parity not in self.PARITIES: raise ValueError("Not a valid parity: %r" % (parity,))
+ self._parity = parity
+ if self._isOpen: self._reconfigurePort()
+
+ def getParity(self):
+ """Get the current parity setting."""
+ return self._parity
+
+ parity = property(getParity, setParity, doc="Parity setting")
+
+
+ def setStopbits(self, stopbits):
+ """Change stopbits size."""
+ if stopbits not in self.STOPBITS: raise ValueError("Not a valid stopbit size: %r" % (stopbits,))
+ self._stopbits = stopbits
+ if self._isOpen: self._reconfigurePort()
+
+ def getStopbits(self):
+ """Get the current stopbits setting."""
+ return self._stopbits
+
+ stopbits = property(getStopbits, setStopbits, doc="Stopbits setting")
+
+
+ def setTimeout(self, timeout):
+ """Change timeout setting."""
+ if timeout is not None:
+ if timeout < 0: raise ValueError("Not a valid timeout: %r" % (timeout,))
+ try:
+ timeout + 1 #test if it's a number, will throw a TypeError if not...
+ except TypeError:
+ raise ValueError("Not a valid timeout: %r" % (timeout,))
+
+ self._timeout = timeout
+ if self._isOpen: self._reconfigurePort()
+
+ def getTimeout(self):
+ """Get the current timeout setting."""
+ return self._timeout
+
+ timeout = property(getTimeout, setTimeout, doc="Timeout setting for read()")
+
+
+ def setWriteTimeout(self, timeout):
+ """Change timeout setting."""
+ if timeout is not None:
+ if timeout < 0: raise ValueError("Not a valid timeout: %r" % (timeout,))
+ try:
+ timeout + 1 #test if it's a number, will throw a TypeError if not...
+ except TypeError:
+ raise ValueError("Not a valid timeout: %r" % timeout)
+
+ self._writeTimeout = timeout
+ if self._isOpen: self._reconfigurePort()
+
+ def getWriteTimeout(self):
+ """Get the current timeout setting."""
+ return self._writeTimeout
+
+ writeTimeout = property(getWriteTimeout, setWriteTimeout, doc="Timeout setting for write()")
+
+
+ def setXonXoff(self, xonxoff):
+ """Change XonXoff setting."""
+ self._xonxoff = xonxoff
+ if self._isOpen: self._reconfigurePort()
+
+ def getXonXoff(self):
+ """Get the current XonXoff setting."""
+ return self._xonxoff
+
+ xonxoff = property(getXonXoff, setXonXoff, doc="Xon/Xoff setting")
+
+ def setRtsCts(self, rtscts):
+ """Change RtsCts flow control setting."""
+ self._rtscts = rtscts
+ if self._isOpen: self._reconfigurePort()
+
+ def getRtsCts(self):
+ """Get the current RtsCts flow control setting."""
+ return self._rtscts
+
+ rtscts = property(getRtsCts, setRtsCts, doc="RTS/CTS flow control setting")
+
+ def setDsrDtr(self, dsrdtr=None):
+ """Change DsrDtr flow control setting."""
+ if dsrdtr is None:
+ #if not set, keep backwards compatibility and follow rtscts setting
+ self._dsrdtr = self._rtscts
+ else:
+ #if defined independently, follow its value
+ self._dsrdtr = dsrdtr
+ if self._isOpen: self._reconfigurePort()
+
+ def getDsrDtr(self):
+ """Get the current DsrDtr flow control setting."""
+ return self._dsrdtr
+
+ dsrdtr = property(getDsrDtr, setDsrDtr, "DSR/DTR flow control setting")
+
+ def setInterCharTimeout(self, interCharTimeout):
+ """Change inter-character timeout setting."""
+ if interCharTimeout is not None:
+ if interCharTimeout < 0: raise ValueError("Not a valid timeout: %r" % interCharTimeout)
+ try:
+ interCharTimeout + 1 #test if it's a number, will throw a TypeError if not...
+ except TypeError:
+ raise ValueError("Not a valid timeout: %r" % interCharTimeout)
+
+ self._interCharTimeout = interCharTimeout
+ if self._isOpen: self._reconfigurePort()
+
+ def getInterCharTimeout(self):
+ """Get the current inter-character timeout setting."""
+ return self._interCharTimeout
+
+ interCharTimeout = property(getInterCharTimeout, setInterCharTimeout, doc="Inter-character timeout setting for read()")
+
+
+ # - - - - - - - - - - - - - - - - - - - - - - - -
+
+ def __repr__(self):
+ """String representation of the current port settings and its state."""
+ return "%s<id=0x%x, open=%s>(port=%r, baudrate=%r, bytesize=%r, parity=%r, stopbits=%r, timeout=%r, xonxoff=%r, rtscts=%r, dsrdtr=%r)" % (
+ self.__class__.__name__,
+ id(self),
+ self._isOpen,
+ self.portstr,
+ self.baudrate,
+ self.bytesize,
+ self.parity,
+ self.stopbits,
+ self.timeout,
+ self.xonxoff,
+ self.rtscts,
+ self.dsrdtr,
+ )
+
+if __name__ == '__main__':
+ s = SerialBase()
+ print s.portstr
+ print s.getSupportedBaudrates()
+ print s.getSupportedByteSizes()
+ print s.getSupportedParities()
+ print s.getSupportedStopbits()
+ print s
diff --git a/plugins/rfid/tis2000.py b/plugins/rfid/tis2000.py
new file mode 100644
index 0000000..91d1991
--- /dev/null
+++ b/plugins/rfid/tis2000.py
@@ -0,0 +1,252 @@
+from device import RFIDDevice
+from serial import Serial
+import dbus
+from dbus.mainloop.glib import DBusGMainLoop
+import gobject
+import re
+from time import sleep
+
+HAL_SERVICE = 'org.freedesktop.Hal'
+HAL_MGR_PATH = '/org/freedesktop/Hal/Manager'
+HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager'
+HAL_DEV_IFACE = 'org.freedesktop.Hal.Device'
+REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\
+ 'a-z,A-Z,0-9,_]*serial_usb_[0-9]'
+
+STATE_WAITING = 0
+STATE_WAITING2 = 1
+STATE_READING = 2
+
+class RFIDReader(RFIDDevice):
+ """
+ TIS-2000 interface.
+ """
+
+ def __init__(self):
+
+ RFIDDevice.__init__(self)
+ self.last_tag = ""
+ self.ser = Serial()
+ self.device = ''
+ self.device_path = ''
+ self._connected = False
+ self._state = STATE_WAITING
+
+ loop = DBusGMainLoop()
+ self.bus = dbus.SystemBus(mainloop=loop)
+ hmgr_iface = dbus.Interface(self.bus.get_object(HAL_SERVICE,
+ HAL_MGR_PATH), HAL_MGR_IFACE)
+
+ hmgr_iface.connect_to_signal('DeviceRemoved', self._device_removed_cb)
+
+ def get_present(self):
+ """
+ Checks if TI-S2000 device is present.
+ Returns True if so, False otherwise.
+ """
+ hmgr_if = dbus.Interface(self.bus.get_object(HAL_SERVICE, HAL_MGR_PATH),
+ HAL_MGR_IFACE)
+ tiusb_devices = set(hmgr_if.FindDeviceStringMatch('serial.type',
+ 'usb')) & set(hmgr_if.FindDeviceStringMatch(
+ 'info.product', 'TUSB3410 Microcontroller'))
+ for i in tiusb_devices:
+ tiusb_if = dbus.Interface(self.bus.get_object(HAL_SERVICE, i),
+ HAL_DEV_IFACE)
+ if tiusb_if.PropertyExists('linux.device_file'):
+ self.device = str(tiusb_if.GetProperty('linux.device_file'))
+ self.device_path = i
+ return True
+ return False
+
+ def do_connect(self):
+ """
+ Connects to the device.
+ Returns True if successfull, False otherwise.
+ """
+ retval = False
+ if self.get_present():
+ try:
+ self.ser = Serial(self.device, 9600, timeout=0.1)
+ self._connected = True
+ self._escape()
+ self._clear()
+ self._format()
+ gobject.idle_add(self._loop)
+ retval = True
+ except:
+ self._connected = False
+ return retval
+
+ def do_disconnect(self):
+ """
+ Disconnect from the device.
+ """
+ self.ser.close()
+ self._connected = False
+
+ def read_tag(self):
+ """
+ Returns the last read value.
+ """
+ return self.last_tag
+
+ def write_tag(self, hexval):
+ """
+ Usage: write_tag(hexval)
+
+ Writes the hexadecimal string "hexval" into the tag.
+ Returns True if successfull, False otherwise.
+ """
+ #self.ser.flushInput()
+ reg = re.compile('([^0-9A-F]+)')
+ if not (hexval.__len__() == 16 and reg.findall(hexval) == []):
+ return False
+ self.ser.read(100)
+ self.ser.write('P')
+ for i in hexval:
+ self.ser.write(i)
+ sleep(1)
+ resp = self.ser.read(64)
+ resp = resp.split()[0]
+ if resp == "P0":
+ return True
+ else:
+ return False
+
+ def _escape(self):
+ """
+ Sends the scape command to the TIS-2000 device.
+ """
+ try:
+ #self.ser.flushInput()
+ self.ser.read(100)
+ self.ser.write('\x1B')
+ resp = self.ser.read()
+ if resp == 'E':
+ return True
+ else:
+ return False
+ except:
+ return False
+
+ def _format(self):
+ """
+ Sends the format command to the TIS-2000 device.
+ """
+ try:
+ #self.ser.flushInput()
+ self.ser.read(100)
+ self.ser.write('F')
+ resp = self.ser.read()
+ if resp == 'F':
+ return True
+ else:
+ return False
+ except:
+ return False
+
+ def _clear(self):
+ """
+ Sends the clear command to the TIS-2000 device.
+ """
+ try:
+ #self.ser.flushInput()
+ self.ser.read(100)
+ self.ser.write('C')
+ resp = self.ser.read()
+ if resp == 'C':
+ return True
+ else:
+ return False
+ except:
+ return False
+
+ def get_version(self):
+ """
+ Sends the version command to the TIS-2000 device and returns
+ a string with the device version.
+ """
+ #self.ser.flushInput()
+ self.ser.read(100)
+ self.ser.write('V')
+ version = []
+ tver = ""
+ while 1:
+ resp = self.ser.read()
+ if resp == '\x0A' or resp == '':
+ break
+ if resp != '\n' and resp != '\r':
+ version.append(resp)
+ for i in version:
+ tver = tver + i
+ if tver != "":
+ return tver
+ return "Unknown"
+
+ def _device_removed_cb(self, path):
+ """
+ Called when a device is removed.
+ Checks if the removed device is itself and emits the "disconnected"
+ signal if so.
+ """
+ if path == self.device_path:
+ self.device_path = ''
+ self.ser.close()
+ self._connected = False
+ self.emit("disconnected","TIS-2000")
+
+ def _loop(self):
+ """
+ Threaded loop for reading data sent from the TIS-2000.
+ """
+ if not self._connected:
+ return False
+
+ if self._state is STATE_WAITING:
+ data = self.ser.read()
+ if data in ['W', 'R']:
+ self._state = STATE_WAITING2
+ return True
+
+ elif self._state is STATE_WAITING2:
+ data = self.ser.read()
+ if data.isspace():
+ self._state = STATE_READING
+ else:
+ self._clear()
+ self._state = STATE_WAITING
+ return True
+
+ elif self._state is STATE_READING:
+ data = self.ser.read(16)
+ if data.__len__() < 16:
+ self._clear()
+ self._state = STATE_WAITING
+ else:
+ reg = re.compile('([^0-9A-F]+)')
+ if reg.findall(data) == []:
+ self.emit("tag-read", data)
+ self.last_tag = data
+ self._clear()
+ self._state = STATE_WAITING
+ return True
+ return True
+
+# Testing
+#if __name__ == '__main__':
+# def handler(device, idhex):
+# """
+# Handler for "tag-read" signal.
+# Prints the tag id.
+# """
+# print "ID: ", idhex
+#
+# dev = RFIDReader()
+# if dev.get_present():
+# dev.do_connect()
+# dev.connect('tag-read', handler)
+# else:
+# print "Not connected"
+#
+# mloop = gobject.MainLoop()
+# mloop.run()
diff --git a/plugins/rfid/utils.py b/plugins/rfid/utils.py
new file mode 100644
index 0000000..94e5540
--- /dev/null
+++ b/plugins/rfid/utils.py
@@ -0,0 +1,98 @@
+# utils.py - Helper functions for tis2000.py
+# Copyright (C) 2010 Emiliano Pastorino <epastorino@plan.ceibal.edu.uy>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+def strhex2bin(strhex):
+ """
+ Convert a string representing an hex value into a
+ string representing the same value in binary format.
+ """
+ dic = { '0':"0000",
+ '1':"0001",
+ '2':"0010",
+ '3':"0011",
+ '4':"0100",
+ '5':"0101",
+ '6':"0110",
+ '7':"0111",
+ '8':"1000",
+ '9':"1001",
+ 'A':"1010",
+ 'B':"1011",
+ 'C':"1100",
+ 'D':"1101",
+ 'E':"1110",
+ 'F':"1111"
+ }
+ binstr = ""
+ for i in strhex:
+ binstr = binstr + dic[i.upper()]
+ return binstr
+
+def strbin2dec(strbin):
+ """
+ Convert a string representing a binary value into a
+ string representing the same value in decimal format.
+ """
+ strdec = "0"
+ for i in range(1, strbin.__len__()+1):
+ strdec = str(int(strdec)+int(strbin[-i])*int(pow(2, i-1)))
+ return strdec
+
+def dec2bin(ndec):
+ """
+ Convert a decimal number into a string representing
+ the same value in binary format.
+ """
+ if ndec < 1:
+ return "0"
+ binary = []
+ while ndec != 0:
+ binary.append(ndec%2)
+ ndec = ndec/2
+ strbin = ""
+ binary.reverse()
+ for i in binary:
+ strbin = strbin+str(i)
+ return strbin
+
+def bin2hex(strbin):
+ """
+ Convert a string representing a binary number into a string
+ representing the same value in hexadecimal format.
+ """
+ dic = { "0000":"0",
+ "0001":"1",
+ "0010":"2",
+ "0011":"3",
+ "0100":"4",
+ "0101":"5",
+ "0110":"6",
+ "0111":"7",
+ "1000":"8",
+ "1001":"9",
+ "1010":"A",
+ "1011":"B",
+ "1100":"C",
+ "1101":"D",
+ "1110":"E",
+ "1111":"F"
+ }
+ while strbin.__len__()%4 != 0:
+ strbin = '0' + strbin
+ strh = ""
+ for i in range(0, strbin.__len__()/4):
+ strh = strh + dic[str(strbin[i*4:i*4+4])]
+ return strh
diff --git a/plugins/turtle_blocks_extras/turtle_blocks_extras.py b/plugins/turtle_blocks_extras/turtle_blocks_extras.py
index ddeb63d..66f3b70 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,36 +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,
+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.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.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):
@@ -61,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
@@ -90,45 +71,68 @@ class Turtle_blocks_extras(Plugin):
colors=["#FFC000", "#A08000"],
help_string=_('Palette of flow operators'))
- # internally expanded macro
palette.add_block('while',
- hidden=True,
style='clamp-style-boolean',
label=_('while'),
prim_name='while',
- default=[None, None, None],
+ default=[None, None],
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',
- hidden=True,
- style='clamp-style-boolean',
+ style='clamp-style-until',
label=_('until'),
prim_name='until',
- default=[None, None, None],
+ default=[None, None],
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',
- hidden=True,
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',
@@ -136,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')
@@ -150,7 +154,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')
@@ -163,7 +167,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')
@@ -176,7 +180,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')
@@ -188,7 +192,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'),
@@ -198,8 +201,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,
@@ -212,10 +216,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'),
@@ -223,13 +227,14 @@ 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',
- hidden=True,
style='basic-style-1arg',
label=_('save picture'),
prim_name='savepix',
@@ -237,11 +242,10 @@ 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',
- hidden=True,
style='basic-style-1arg',
label=_('save SVG'),
prim_name='savesvg',
@@ -249,7 +253,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',
@@ -258,7 +264,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',
hidden=True,
@@ -293,7 +301,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'),
@@ -301,9 +308,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',
hidden=True,
style='basic-style-3arg',
@@ -315,23 +322,27 @@ 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('extras',
colors=["#FF0000", "#A00000"],
help_string=_('Palette of extra options'),
- position=8)
+ position=8,
+ translation=_('extras'))
+
'''
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',
@@ -341,9 +352,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',
hidden=True,
style='boolean-block-style',
@@ -353,8 +364,8 @@ 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))
palette.add_block('mousex',
hidden=True,
@@ -364,8 +375,9 @@ pressed'))
value_block=True,
help_string=_('returns mouse x coordinate'))
self.tw.lc.def_prim('mousex', 0,
- lambda self:
- self.tw.mouse_x - (self.tw.canvas.width / 2))
+ Primitive(self.tw.get_mouse_x,
+ return_type=TYPE_NUMBER,
+ call_afterwards=self.after_mouse_x))
palette.add_block('mousey',
hidden=True,
@@ -375,10 +387,10 @@ pressed'))
value_block=True,
help_string=_('returns mouse y coordinate'))
self.tw.lc.def_prim('mousey', 0,
- lambda self:
- (self.tw.canvas.height / 2) - self.tw.mouse_y)
+ 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',
hidden=True,
style='basic-style-extended-vertical',
@@ -387,7 +399,8 @@ 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))
palette.add_block('keyboard',
hidden=True,
@@ -398,34 +411,10 @@ stored in keyboard block)'))
logo_command='make "keyboard readchar',
help_string=_('holds results of query-keyboard \
block as ASCII'))
- self.tw.lc.def_prim('keyboard', 0, lambda self: self.tw.lc.keyboard)
-
- '''
- palette.add_block('keyboard_chr',
- style='box-style',
- label='chr(%s)' % (_('keyboard')),
- prim_name='keyboard_chr',
- value_block=True,
- logo_command='make "keyboard readchar',
- help_string=_('holds results of query-keyboard \
-block as character'))
- self.tw.lc.def_prim('keyboard_chr', 0,
- lambda self: chr(self.tw.lc.keyboard))
-
- primitive_dictionary['keyboardnum'] = self._prim_keyboard_num
- palette.add_block('keyboard_num',
- style='box-style',
- label='num(%s)' % (_('keyboard')),
- prim_name='keyboard_num',
- value_block=True,
- logo_command='make "keyboard readchar',
- help_string=_('holds results of query-keyboard \
-block as number'))
- self.tw.lc.def_prim('keyboard_num', 0,
- lambda self: primitive_dictionary['keyboardnum']())
- '''
+ self.tw.lc.def_prim('keyboard', 0,
+ Primitive(self.tw.get_keyboard,
+ return_type=TYPE_NUMBER))
- primitive_dictionary['readpixel'] = self._prim_readpixel
palette.add_block('readpixel',
hidden=True,
style='basic-style-extended-vertical',
@@ -435,20 +424,21 @@ block as number'))
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',
hidden=True,
style='box-style',
label=_('turtle sees'),
+ value_block=True,
prim_name='see',
help_string=_('returns the color that the turtle \
"sees"'))
self.tw.lc.def_prim('see', 0,
- lambda self: primitive_dictionary['see']())
+ Primitive(Turtle.get_color_index,
+ return_type=TYPE_NUMBER,
+ call_afterwards=self.after_see))
- primitive_dictionary['time'] = self._prim_time
palette.add_block('time',
hidden=True,
style='box-style',
@@ -457,18 +447,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',
hidden=True,
style='basic-style-1arg',
@@ -478,12 +483,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',
hidden=True,
style='basic-style-extended-vertical',
@@ -492,12 +499,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',
hidden=True,
style='basic-style-extended-vertical',
@@ -506,12 +515,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',
hidden=True,
style='box-style',
@@ -522,12 +531,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',
@@ -535,10 +545,59 @@ 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))]))]))
+
+ palette.add_block('saveheap',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('save heap to file'),
+ default=_('filename'),
+ prim_name='saveheap',
+ help_string=_('saves FILO (first-in \
+last-out heap) to a file'))
+
+ self.tw.lc.def_prim('saveheap', 1,
+ Primitive(self.tw.lc.save_heap,
+ arg_descs=[ArgSlot(TYPE_OBJECT)]))
+
+ if self.tw.running_sugar:
+ palette.add_block('loadheap',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('load heap from file'),
+ default=_('filename'),
+ prim_name='loadheap',
+ help_string=_('loads FILO (first-in \
+last-out heap) from a file'))
+ # macro
+ palette.add_block('loadheapfromjournal',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('load heap from file'),
+ help_string=_('loads FILO (first-in \
+last-out heap) from a file'))
+ else:
+ palette.add_block('loadheap',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('load heap from file'),
+ default=_('filename'),
+ prim_name='loadheap',
+ help_string=_('loads FILO (first-in \
+last-out heap) from a file'))
+
+ self.tw.lc.def_prim('loadheap', 1,
+ Primitive(self.tw.lc.load_heap,
+ arg_descs=[ArgSlot(TYPE_OBJECT)],
+ call_afterwards=self.after_push))
- primitive_dictionary['isheapempty2'] = self._prim_is_heap_empty_bool
palette.add_block('isheapempty2',
hidden=True,
style='boolean-block-style',
@@ -546,11 +605,15 @@ make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n')
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',
hidden=True,
style='basic-style-1arg',
@@ -559,9 +622,9 @@ make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n')
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',
@@ -571,31 +634,44 @@ make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n')
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',
hidden=True,
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',
hidden=True,
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',
+ hidden=True,
+ style='basic-style-extended-vertical',
+ label=_('polar'),
+ prim_name='polar',
+ help_string=_('displays polar coordinates'))
+ self.tw.lc.def_prim('polar', 0,
+ lambda self: self.tw.set_polar(True))
- primitive_dictionary['myfunction'] = self._prim_myfunction
palette.add_block('myfunc1arg',
hidden=True,
style='number-style-var-arg',
@@ -605,9 +681,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,
@@ -619,9 +696,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,
@@ -633,11 +712,21 @@ 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',
+ hidden=True,
+ style='basic-style-extended-vertical',
+ label=_('Cartesian'),
+ prim_name='cartesian',
+ help_string=_('displays Cartesian coordinates'))
+ self.tw.lc.def_prim('cartesian', 0,
+ lambda self: self.tw.set_cartesian(True))
- primitive_dictionary['userdefined'] = self._prim_myblock
palette.add_block('userdefined',
hidden=True,
style='basic-style-var-arg',
@@ -649,8 +738,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')
@@ -666,8 +755,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')
@@ -683,31 +773,48 @@ 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')
- palette.add_block('cartesian',
+ palette.add_block('getfromurl',
hidden=True,
- style='basic-style-extended-vertical',
- label=_('Cartesian'),
- prim_name='cartesian',
- help_string=_('displays Cartesian coordinates'))
- self.tw.lc.def_prim('cartesian', 0,
- lambda self: self.tw.set_cartesian(True))
+ style='number-style-1arg',
+ #TRANS: URL is universal resource locator
+ label=_('URL'),
+ default=\
+'http://wiki.sugarlabs.org/images/2/2c/Logo_alt_3.svg',
+ prim_name='getfromurl',
+ help_string=\
+_('gets a text string or an image from a URL'))
+ self.tw.lc.def_prim('getfromurl', 1,
+ Primitive(self.tw.lc.get_from_url,
+ arg_descs=[ArgSlot(TYPE_STRING)]))
- palette.add_block('polar',
+
+ palette.add_block('skin',
hidden=True,
- style='basic-style-extended-vertical',
- label=_('polar'),
- prim_name='polar',
- help_string=_('displays polar coordinates'))
- self.tw.lc.def_prim('polar', 0,
- lambda self: self.tw.set_polar(True))
+ colors=["#FF0000", "#A00000"],
+ style='basic-style-1arg',
+ label=_('turtle shell'),
+ prim_name='skin',
+ help_string=_("put a custom 'shell' on the turtle"))
+ self.tw.lc.def_prim('skin', 1,
+ Primitive(self.tw.lc.reskin,
+ arg_descs=[ArgSlot(TYPE_OBJECT)]))
+
+ # macro
+ palette.add_block('reskin',
+ hidden=True,
+ style='basic-style-1arg',
+ label=_('turtle shell'),
+ help_string=_("put a custom 'shell' on the turtle"))
palette.add_block('addturtle',
hidden=True,
@@ -718,10 +825,9 @@ 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',
hidden=True,
style='number-style-1arg',
@@ -729,10 +835,12 @@ module found in the Journal'))
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',
hidden=True,
style='number-style-1arg',
@@ -740,23 +848,24 @@ module found in the Journal'))
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',
hidden=True,
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',
hidden=True,
style='number-style-1arg',
@@ -764,27 +873,11 @@ module found in the Journal'))
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))
-
- primitive_dictionary['skin'] = self._prim_reskin
- palette.add_block('skin',
- hidden=True,
- colors=["#FF0000", "#A00000"],
- style='basic-style-1arg',
- label=_('turtle shell'),
- prim_name='skin',
- help_string=_("put a custom 'shell' on the turtle"))
- self.tw.lc.def_prim('skin', 1,
- lambda self, x:
- primitive_dictionary['skin'](x))
-
- # macro
- palette.add_block('reskin',
- hidden=True,
- style='basic-style-1arg',
- label=_('turtle shell'),
- help_string=_("put a custom 'shell' on the turtle"))
+ self.tw.lc.def_prim(
+ 'turtleh', 1,
+ Primitive(self.tw.turtles.get_turtle_heading,
+ arg_descs=[ArgSlot(TYPE_OBJECT)],
+ return_type=TYPE_BOX))
palette.add_block('sandwichclampcollapsed',
hidden=True,
@@ -794,7 +887,19 @@ module found in the Journal'))
special_name=_('top'),
help_string=_('top of a collapsed stack'))
- primitive_dictionary['loadblock'] = self._prim_load_block
+ palette.add_block('loadpalette',
+ hidden=True,
+ style='basic-style-1arg',
+ string_or_number=True,
+ label=_('select palette'),
+ prim_name='loadpalette',
+ default=_('turtle'),
+ help_string=_('selects a palette'))
+ self.tw.lc.def_prim('loadpalette', 1,
+ Primitive(self.tw.prim_load_palette,
+ export_me=False,
+ arg_descs=[ArgSlot(TYPE_STRING)]))
+
palette.add_block('loadblock',
hidden=True,
style='basic-style-var-arg',
@@ -803,8 +908,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',
@@ -815,8 +921,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',
@@ -827,56 +935,47 @@ 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_dictionary['loadpalette'] = self._prim_load_palette
- palette.add_block('loadpalette',
- hidden=True,
- style='basic-style-1arg',
- string_or_number=True,
- label=_('select palette'),
- prim_name='loadpalette',
- 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_block,
+ export_me=False,
+ arg_descs=[ArgSlot(TYPE_STRING),
+ ArgSlot(TYPE_OBJECT),
+ ArgSlot(TYPE_OBJECT)]))
def _portfolio_palette(self):
- debug_output('creating %s palette' % _('portfolio'),
- self.tw.running_sugar)
+
palette = make_palette('extras',
colors=["#FF0000", "#A00000"],
help_string=_('Palette of extra options'),
- position=8)
+ position=8,
+ translation=_('extras'))
'''
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',
hidden=True,
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',
hidden=True,
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',
hidden=True,
@@ -884,8 +983,9 @@ templates'),
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',
@@ -956,7 +1056,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',
@@ -964,7 +1067,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',
@@ -972,7 +1078,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',
@@ -980,7 +1089,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',
@@ -988,7 +1100,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',
@@ -996,7 +1111,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,
@@ -1005,7 +1123,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,
@@ -1014,7 +1135,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,
@@ -1023,7 +1147,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,
@@ -1032,7 +1159,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,
@@ -1041,7 +1171,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,
@@ -1050,7 +1183,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;
@@ -1060,12 +1196,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)
@@ -1078,235 +1213,36 @@ Journal objects'))
# Block primitives
- def _prim_emptyheap(self):
- """ Empty FILO """
- self.tw.lc.heap = []
-
- def _prim_kbinput(self):
- """ Query keyboard """
- if len(self.tw.keypress) == 1:
- self.tw.lc.keyboard = ord(self.tw.keypress[0])
- else:
- try:
- self.tw.lc.keyboard = {
- 'Escape': 27, 'space': 32, ' ': 32,
- 'Return': 13, 'KP_Up': 2, 'KP_Down': 4, 'KP_Left': 1,
- 'KP_Right': 3}[self.tw.keypress]
- except KeyError:
- self.tw.lc.keyboard = 0
+ def after_keypress(self):
if self.tw.lc.update_values:
- self.tw.lc.update_label_value('keyboard', self.tw.lc.keyboard)
- self.tw.keypress = ''
-
- def _prim_list(self, blklist):
- """ Expandable list block """
- self._prim_showlist(blklist)
- self.tw.lc.ireturn()
- yield True
-
- def _prim_myblock(self, x):
- """ Run Python code imported from Journal """
- if self.tw.lc.bindex is not None and \
- self.tw.lc.bindex in self.tw.myblock:
- try:
- if len(x) == 1:
- myfunc_import(self, self.tw.myblock[self.tw.lc.bindex],
- x[0])
+ 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_KEY_DICT[
+ KEY_DICT[self.tw.keypress]])
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
+ 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.keyboard))
+ self.tw.keypress = ''
- 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))
+ 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)
@@ -1326,7 +1262,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 = []
@@ -1400,125 +1336,34 @@ Journal objects'))
csd.write("\n</CsoundSynthesizer>")
csd.close()
- 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 after_mouse_x(self):
+ """ Show mouse x coordinate """
+ if self.tw.lc.update_values:
+ self.tw.lc.update_label_value('mousex', self.tw.get_mouse_x())
- 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_mouse_y(self):
+ """ Show mouse y coordinate """
if self.tw.lc.update_values:
- self.tw.lc.update_label_value('see', color_index)
- return color_index
+ self.tw.lc.update_label_value('mousey', self.tw.get_mouse_y())
- def _prim_setscale(self, scale):
- """ Set the scale used by the show block """
- self.tw.lc.scale = scale
+ def after_see(self):
+ """ Show color under turtle """
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)
+ self.tw.lc.update_label_value(
+ 'see',
+ self.tw.turtles.get_active_turtle().get_color_index())
+
+ 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 """
@@ -1538,137 +1383,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/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..babada1
--- /dev/null
+++ b/util/codegen.py
@@ -0,0 +1,602 @@
+# -*- 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):
+ op = self.BINOP_SYMBOLS[node.op]
+ # if op in ['+', '-']:
+ self.write('(')
+ self.visit(node.left)
+ self.write(' %s ' % op)
+ self.visit(node.right)
+ # if op in ['+', '-']:
+ self.write(')')
+
+ 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)