From 86e2111461fab98d8c188d50ec8bd0c72c527f73 Mon Sep 17 00:00:00 2001 From: Pootle daemon Date: Mon, 13 Jun 2011 14:23:40 +0000 Subject: Merge branch 'master' of git.sugarlabs.org:turtleart/mainline --- diff --git a/.gitignore b/.gitignore index bea5755..737cf64 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -TAGS +*.pyc +*~ +*.pyo diff --git a/COPYING b/COPYING index d2954ad..ff7d256 100644 --- a/COPYING +++ b/COPYING @@ -1,6 +1,6 @@ Copyright (c) 2007, Playful Invention Company -Copyright (c) 2008-10, Walter Bender -Copyright (c) 2009-10 Raúl Gutiérrez Segalés +Copyright (c) 2008-11, Walter Bender +Copyright (c) 2009-11 Raúl Gutiérrez Segalés Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/NEWS b/NEWS index e317257..92b5534 100644 --- a/NEWS +++ b/NEWS @@ -1,20 +1,110 @@ -106 +109 + +ENHANCEMENTS + +* CM coordinate grid overlay (XO hardware only) BUG FIXES +* Restored missing localization files +* Only initialize camera if camera block is being used +* Suppress unnecessary debug output from talogo +* Fix hardware detection on some old builds -* Exposed see as an external method (#2542) -* Media type tests on file suffix use lower() -* Uninitialized variables in Show block for numeric arguments (#2543) -* Catch potential zero-divide in set_gray method (#2545) -* Fixed regression with show Journal object thumbnails +108 + +ENHANCEMENTS + +* vspace, hspace run stack on mouse click (#2790) +* added clear all button to Trash Palette +* added language support to speak.py sample code +* added 'scratch' and 'hoops' examples + +BUG FIXES + +* Save to SVG was not working for setxy +* Save to SVG was not working for fill +* Save to SVG was broken for arc + +107 ENHANCEMENTS -* Code clean up by RGS -* Added 'brightness' block for reading camera luminance level -* Added 'camera sees' block for reading average camera RGB value +* Refactoring of block and palette generation to use the plugin + classes for all blocks. (The goal is to make it easier for end-user + modifications.) +* Added the ability to add new blocks on the fly (See uturn.py example) +* Added basic-style-3arg block (used by NXT plugin) +* Moved plugin libraries to ./plugins +* Shared turtles are updated after mouse drag (#2687) +* Shared turtles cannot be repositioned remotely (#2687) +* Shared turtles have labels +* Turtles are synchronized when joining share (#2687) +* Comments on usage included in the Python sample code (#2709) +* Using standard Sugar icons for colors and camera +* Refactoring of export Logo code to enable plugins to define new functions +* Value blocks updated properly on first appearance +* Reduced the header size in Python sample code for easier reading (#2748) +* Extend clearscreen to entire canvas (#2745) +* Cleaned up some issues with .es translations (Guzman Trinidad) +* More robust handling of broken projects and unknown block types +* Added hidden blocks when plugin devices are unavailable +* Added new Python example for recording audio + +BUG FIXES + +* Fixed regression with help messages when running Sugar 0.84 (#2676) +* Fixed problem with selected turtle reverting back to default turtle (#2687) +* Fixed problem with camera block graphic in trash (#2678) +* Fixed regression with camera_plugin after refactoring (#2689) +* Fixed problem with saving/loading extra turtles when nick changes (#2441) +* Fixed problem with displaying large FILO heap (#2751) +* Fixed a problem with tags causing malformed HTML export +* Fixed problem with first sample when using sensors +* Fixed problem with method-name conflict with gtk.widget + +PYTHON CODE CHANGE + +* Userdefined code gets TurtleWindow instance rather than LogoCode + instance as first argument. This change was made in order to better + accommodate and better parallel the new plugin mechanism. Most + likely, you had been referencing tw, the TurtleWindow instance from + lc, the LogoCode instance, e.g., lc.tw. Now you can reference tw + directly, e.g., tw. To reference lc, use tw.lc. Under most + circumstances, Turtle Art will detect when myblock is looking for lc + rather than tw and it will pass the correct argument. + +106 + +ENHANCEMENTS + +* Added sharing to draw_text, fill_polygon, draw_pixbuf (#2461) +* Added sharing (EXPERIMENTAL) between Gnome and Sugar versions (with Raul + Gutierrez Segales) +* Added 'time' block for measuring elapsed time (in seconds) +* Added 'brightness' block for reading average camera luminance value + (with help from Tony Forster and Guzman Trinidad) * Added camera media block for grabbing images from the camera +* New speak.py sample code for doing text to speech (Tony Forster) +* New load_block.py sample code for programatically creating TA projects * New psuedo-color.ta example (Tony Forster) +* New love-speaks-volumes.ta example -- use mic input to scale shapes +* New spiralaterals.ta example inspired by Spiralaterals activity +* Added a Media Palette for all media-related blocks and reorganized palettes + (#2633) for more clarity (Claudia Urrea and Bill Kerr) +* Added offset for second argument in Boolean compare blocks +* More complete translations in Spanish (es) and Italian (it) +* Added plugin support for non-standard devices (camera, sensors, RFID) + +BUG FIXES + +* Exposed see as an external method (#2542) +* Media type tests on file suffix use lower() +* Added support for localization to GNOME version (Raul Gutierrez Segales) +* Work around for situations where gst is not available +* Fixed problem with displaying Journal preview images in portfolio +* Restore overlay grids on clear +* Fixed problem with help-string wrap width (#2633) +* Added missing dependency to RFID plugin (Emiliano Pastorino) 105 @@ -110,7 +200,7 @@ ENHANCEMENTS * rgs fixed resume problem in GNOME version (#2293) * checking alpha value to block 'hit' detection (fixes problem with selecting the wrong block when blocks wrap around each other). -* trap and display math errors in python block (#2313) +* trap and display math errors in Python block (#2313) 96 @@ -386,7 +476,7 @@ ENHANCEMENTS * first pass at hover help support (thanks Raul) * put samples button, keep button on project toolbar -* fixed journal icons associated with html, python, logo +* fixed journal icons associated with HTML, Python, Logo * improved compatibility with old Sugar builds * images centered under turtle * text vertically centered under turtle diff --git a/TurtleArt/sprites.py b/TurtleArt/sprites.py index fdcd72f..c633397 100644 --- a/TurtleArt/sprites.py +++ b/TurtleArt/sprites.py @@ -426,12 +426,15 @@ class Sprite: b = ord(array[offset + 2]) return(r, g, b, a) except IndexError: + """ print "Index Error: %d %d (%d, %d) (w: %d, h: %d) (%dx%d)"\ % (len(array), offset, x, y, self.images[i].get_width(), self.images[i].get_height(), self.rect.width, self.rect.height) - return(-1, -1, -1, -1) + """ + pass + return(-1, -1, -1, -1) else: w, h = self.images[i].get_size() if x < 0 or x > (w - 1) or y < 0 or y > (h - 1): diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py new file mode 100644 index 0000000..bfed2d7 --- /dev/null +++ b/TurtleArt/tabasics.py @@ -0,0 +1,1270 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2011, Walter Bender + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +""" +This file contains the constants that by-in-large determine the +behavior of Turtle Art. Notably, the block palettes are defined +below. If you want to add a new block to Turtle Art, you could +simply add a block of code to this file or to turtle_block_plugin.py, +which contains additional blocks. (Even better, write your own plugin!!) + + +Adding a new palette is simply a matter of: + palette = make_palette('mypalette', # the name of your palette + colors=["#00FF00", "#00A000"], + help_string=_('Palette of my custom commands')) + +For example, if we want to add a new turtle command, 'uturn', we'd use the +add_block method in the Palette class. + palette.add_block('uturn', # the name of your block + style='basic-style', # the block style + label=_('u turn'), # the label for the block + prim_name='uturn', # code reference (see below) + help_string=_('turns the turtle 180 degrees')) + + # Next, you need to define what your block will do: + # def_prim takes 3 arguments: the primitive name, the number of + # arguments -- 0 in this case -- and the function to call -- in this + # case, the canvas.seth function to set the heading. + self.tw.lc.def_prim('uturn', 0, + lambda self: self.tw.canvas.seth(self.tw.canvas.heading + 180)) + +That's it. When you next run Turtle Art, you will have a 'uturn' block +on the 'mypalette' palette. + +You will have to create icons for the palette-selector buttons. These +are kept in the icons subdirectory. You need two icons: +mypaletteoff.svg and mypaletteon.svg, where 'mypalette' is the same +string as the entry you used in instantiating the Palette class. Note +that the icons should be the same size (55x55) as the others. (This is +the default icon size for Sugar toolbars.) +""" + +from time import time +from math import sqrt +from random import uniform + +from gettext import gettext as _ + +from tapalette import make_palette, define_logo_function +from talogo import primitive_dictionary, logoerror +from tautils import convert, chr_to_ord, round_int, strtype +from taconstants import BLACK, WHITE, CONSTANTS + +def _num_type(x): + """ Is x a number type? """ + if type(x) == int: + return True + if type(x) == float: + return True + if type(x) == ord: + return True + return False + + +def _millisecond(): + """ Current time in milliseconds """ + return time() * 1000 + + +class Palettes(): + """ a class for creating the palettes of blocks """ + + def __init__(self, parent): + self.tw = parent + + self._turtle_palette() + + self._pen_palette() + + self._color_palette() + + self._numbers_palette() + + self._flow_palette() + + self._blocks_palette() + + self._trash_palette() + + # Palette definitions + + def _turtle_palette(self): + """ The basic Turtle Art turtle palette """ + + palette = make_palette('turtle', + colors=["#00FF00", "#00A000"], + help_string=_('Palette of turtle commands')) + + primitive_dictionary['move'] = self._prim_move + palette.add_block('forward', + style='basic-style-1arg', + label=_('forward'), + prim_name='forward', + default=100, + logo_command='forward', + help_string=_('moves turtle forward')) + self.tw.lc.def_prim('forward', 1, + lambda self, x: primitive_dictionary['move']( + self.tw.canvas.forward, x)) + + palette.add_block('back', + style='basic-style-1arg', + label=_('back'), + prim_name='back', + default=100, + logo_command='back', + help_string=_('moves turtle backward')) + self.tw.lc.def_prim('back', 1, + lambda self, x: primitive_dictionary['move']( + self.tw.canvas.forward, -x)) + + primitive_dictionary['clean'] = self.tw.lc.prim_clear + palette.add_block('clean', + style='basic-style-extended-vertical', + label=_('clean'), + prim_name='clean', + logo_command='clean', + help_string=_('clears the screen and reset the \ +turtle')) + self.tw.lc.def_prim('clean', 0, + lambda self: primitive_dictionary['clean']()) + + primitive_dictionary['right'] = self._prim_right + palette.add_block('left', + style='basic-style-1arg', + label=_('left'), + prim_name='left', + default=90, + logo_command='left', + help_string=_('turns turtle counterclockwise (angle \ +in degrees)')) + self.tw.lc.def_prim('left', 1, + lambda self, x: primitive_dictionary['right'](-x)) + + palette.add_block('right', + style='basic-style-1arg', + label=_('right'), + prim_name='right', + default=90, + logo_command='right', + help_string=_('turns turtle clockwise (angle in \ +degrees)')) + self.tw.lc.def_prim('right', 1, + lambda self, x: primitive_dictionary['right'](x)) + + primitive_dictionary['arc'] = self._prim_arc + palette.add_block('arc', + style='basic-style-2arg', + label=[_('arc'), _('angle'), _('radius')], + prim_name='arc', + default=[90, 100], + logo_command='taarc', + help_string=_('moves turtle along an arc')) + self.tw.lc.def_prim('arc', 2, + lambda self, x, y: primitive_dictionary['arc']( + self.tw.canvas.arc, x, y)) + define_logo_function('taarc', 'to taarc :a :r\rrepeat round :a \ +[right 1 forward (0.0175 * :r)]\rend\r') + + palette.add_block('setxy2', + style='basic-style-2arg', + label=[_('set xy'), _('x'), _('y')], + prim_name='setxy2', + logo_command='tasetxy', + default=[0, 0], + help_string=_('moves turtle to position xcor, ycor; \ +(0, 0) is in the center of the screen.')) + self.tw.lc.def_prim('setxy2', 2, + lambda self, x, y: primitive_dictionary['move']( + self.tw.canvas.setxy, x, y)) + define_logo_function('tasetxy', 'to tasetxy :x :y\rsetxy :x :y\rend\r') + + primitive_dictionary['set'] = self._prim_set + palette.add_block('seth', + style='basic-style-1arg', + label=_('set heading'), + prim_name='seth', + default=0, + logo_command='seth', + help_string=_('sets the heading of the turtle (0 is \ +towards the top of the screen.)')) + self.tw.lc.def_prim('seth', 1, + lambda self, x: primitive_dictionary['set']( + 'heading', self.tw.canvas.seth, x)) + + palette.add_block('xcor', + style='box-style', + label=_('xcor'), + help_string=_('holds current x-coordinate value of \ +the turtle (can be used in place of a number block)'), + value_block=True, + prim_name='xcor', + logo_command='xcor') + self.tw.lc.def_prim( + 'xcor', 0, lambda self: self.tw.canvas.xcor / self.tw.coord_scale) + + palette.add_block('ycor', + style='box-style', + label=_('ycor'), + help_string=_('holds current y-coordinate value of \ +the turtle (can be used in place of a number block)'), + value_block=True, + prim_name='ycor', + logo_command='ycor') + self.tw.lc.def_prim( + 'ycor', 0, lambda self: self.tw.canvas.ycor / self.tw.coord_scale) + + palette.add_block('heading', + style='box-style', + label=_('heading'), + help_string=_('holds current heading value of the \ +turtle (can be used in place of a number block)'), + value_block=True, + prim_name='heading', + logo_command='heading') + self.tw.lc.def_prim( + 'heading', 0, lambda self: self.tw.canvas.heading) + + palette.add_block('turtle-label', + hidden=True, + style='blank-style', + label=['turtle']) + + # Deprecated + palette.add_block('setxy', + hidden=True, + style='basic-style-2arg', + label=[_('set xy'), _('x'), _('y')], + prim_name='setxy', + default=[0, 0], + logo_command='tasetxypenup', + help_string=_('moves turtle to position xcor, ycor; \ +(0, 0) is in the center of the screen.')) + self.tw.lc.def_prim('setxy', 2, + lambda self, x, y: primitive_dictionary['move']( + self.tw.canvas.setxy, x, y, pendown=False)) + define_logo_function('tasetxypenup', 'to tasetxypenup :x :y\rpenup\r\ +setxy :x :y\rpendown\rend\r') + + def _pen_palette(self): + """ The basic Turtle Art pen palette """ + + palette = make_palette('pen', + colors=["#00FFFF", "#00A0A0"], + help_string=_('Palette of pen commands')) + + palette.add_block('penup', + style='basic-style-extended-vertical', + label=_('pen up'), + prim_name='penup', + logo_command='penup', + help_string=_('Turtle will not draw when moved.')) + self.tw.lc.def_prim('penup', 0, + lambda self: self.tw.canvas.setpen(False)) + + palette.add_block('pendown', + style='basic-style-extended-vertical', + label=_('pen down'), + prim_name='pendown', + logo_command='pendown', + help_string=_('Turtle will draw when moved.')) + self.tw.lc.def_prim('pendown', 0, + lambda self: self.tw.canvas.setpen(True)) + + palette.add_block('setpensize', + style='basic-style-1arg', + label=_('set pen size'), + prim_name='setpensize', + default=5, + logo_command='setpensize', + help_string=_('sets size of the line drawn by the \ +turtle')) + self.tw.lc.def_prim('setpensize', 1, + lambda self, x: primitive_dictionary['set']( + 'pensize', self.tw.canvas.setpensize, x)) + define_logo_function('tasetpensize', 'to tasetpensize :a\rsetpensize \ +round :a\rend\r') + + palette.add_block('fillscreen', + style='basic-style-2arg', + label=[_('fill screen'), _('color'), _('shade')], + prim_name='fillscreen', + default=[60, 80], + logo_command='tasetbackground', + help_string=_('fills the background with (color, \ +shade)')) + self.tw.lc.def_prim('fillscreen', 2, + lambda self, x, y: self.tw.canvas.fillscreen(x, y)) + define_logo_function('tasetbackground', 'to tasetbackground :color \ +:shade\rtasetshade :shade\rsetbackground :color\rend\r') + + palette.add_block('pensize', + style='box-style', + label=_('pen size'), + help_string=_('holds current pen size (can be used \ +in place of a number block)'), + value_block=True, + prim_name='pensize', + logo_command='pensize') + self.tw.lc.def_prim('pensize', 0, lambda self: self.tw.canvas.pensize) + define_logo_function('tapensize', 'to tapensize\routput first round \ +pensize\rend\r') + + palette.add_block('startfill', + style='basic-style-extended-vertical', + label=_('start fill'), + prim_name='startfill', + help_string=_('starts filled polygon (used with end \ +fill block)')) + self.tw.lc.def_prim('startfill', 0, + lambda self: self.tw.canvas.start_fill()) + + palette.add_block('stopfill', + style='basic-style-extended-vertical', + label=_('end fill'), + prim_name='stopfill', + help_string=_('completes filled polygon (used with \ +start fill block)')) + self.tw.lc.def_prim('stopfill', 0, + lambda self: self.tw.canvas.stop_fill()) + + def _color_palette(self): + """ The basic Turtle Art color palette """ + + palette = make_palette('colors', + colors=["#00FFFF", "#00A0A0"], + help_string=_('Palette of pen colors')) + + palette.add_block('setcolor', + style='basic-style-1arg', + label=_('set color'), + prim_name='setcolor', + default=0, + logo_command='tasetpencolor', + help_string=_('sets color of the line drawn by the \ +turtle')) + self.tw.lc.def_prim('setcolor', 1, + lambda self, x: primitive_dictionary['set']( + 'color', self.tw.canvas.setcolor, x)) + + palette.add_block('setshade', + style='basic-style-1arg', + label=_('set shade'), + prim_name='setshade', + default=50, + logo_command='tasetshade', + help_string=_('sets shade of the line drawn by the \ +turtle')) + self.tw.lc.def_prim('setshade', 1, + lambda self, x: primitive_dictionary['set']( + 'shade', self.tw.canvas.setshade, x)) + + palette.add_block('setgray', + style='basic-style-1arg', + label=_('set gray'), + prim_name='setgray', + default=100, + help_string=_('sets gray level of the line drawn by \ +the turtle')) + self.tw.lc.def_prim('setgray', 1, + lambda self, x: primitive_dictionary['set']( + 'gray', self.tw.canvas.setgray, x)) + + palette.add_block('color', + style='box-style', + label=_('color'), + help_string=_('holds current pen color (can be used \ +in place of a number block)'), + value_block=True, + prim_name='color', + logo_command='pencolor') + self.tw.lc.def_prim('color', 0, lambda self: self.tw.canvas.color) + + palette.add_block('shade', + style='box-style', + label=_('shade'), + help_string=_('holds current pen shade'), + value_block=True, + prim_name='shade', + logo_command=':shade') + self.tw.lc.def_prim('shade', 0, lambda self: self.tw.canvas.shade) + + palette.add_block('gray', + style='box-style', + label=_('gray'), + help_string=_('holds current gray level (can be used \ +in place of a number block)'), + value_block=True, + prim_name='gray') + self.tw.lc.def_prim('gray', 0, lambda self: self.tw.canvas.gray) + + self._make_constant(palette, 'red', CONSTANTS['red']) + self._make_constant(palette, 'orange', CONSTANTS['orange']) + self._make_constant(palette, 'yellow', CONSTANTS['yellow']) + self._make_constant(palette, 'green', CONSTANTS['green']) + self._make_constant(palette, 'cyan', CONSTANTS['cyan']) + self._make_constant(palette, 'blue', CONSTANTS['blue']) + self._make_constant(palette, 'purple', CONSTANTS['purple']) + self._make_constant(palette, 'white', WHITE) + self._make_constant(palette, 'black', BLACK) + + # deprecated blocks + palette.add_block('settextcolor', + hidden=True, + style='basic-style-1arg', + label=_('set text color'), + prim_name='settextcolor', + default=0, + help_string=_('sets color of text drawn by the \ +turtle')) + self.tw.lc.def_prim('settextcolor', 1, + lambda self, x: self.tw.canvas.settextcolor(x)) + + palette.add_block('settextsize', + hidden=True, + style='basic-style-1arg', + label=_('set text size'), + prim_name='settextsize', + default=0, + help_string=_('sets size of text drawn by the \ +turtle')) + self.tw.lc.def_prim('settextsize', 1, + lambda self, x: self.tw.canvas.settextsize(x)) + + # In order to map Turtle Art colors to the standard UCB Logo palette, + # we need to define a somewhat complex set of functions. + define_logo_function('tacolor', '\ +to tasetpalette :i :r :g :b :myshade \r\ +make "s ((:myshade - 50) / 50) \r\ +ifelse lessp :s 0 [ \r\ +make "s (1 + (:s *0.8)) \r\ +make "r (:r * :s) \r\ +make "g (:g * :s) \r\ +make "b (:b * :s) \r\ +] [ \ +make "s (:s * 0.9) \r\ +make "r (:r + ((99-:r) * :s)) \r\ +make "g (:g + ((99-:g) * :s)) \r\ +make "b (:b + ((99-:b) * :s)) \r\ +] \ +setpalette :i (list :r :g :b) \r\ +end \r\ +\ +to rgb :myi :mycolors :myshade \r\ +make "myr first :mycolors \r\ +make "mycolors butfirst :mycolors \r\ +make "myg first :mycolors \r\ +make "mycolors butfirst :mycolors \r\ +make "myb first :mycolors \r\ +make "mycolors butfirst :mycolors \r\ +tasetpalette :myi :myr :myg :myb :myshade \r\ +output :mycolors \r\ +end \r\ +\ +to processcolor :mycolors :myshade \r\ +if emptyp :mycolors [stop] \r\ +make "i :i + 1 \r\ +processcolor (rgb :i :mycolors :myshade) :myshade \r\ +end \r\ +\ +to tasetshade :shade \r\ +make "myshade modulo :shade 200 \r\ +if greaterp :myshade 99 [make "myshade (199-:myshade)] \r\ +make "i 7 \r\ +make "mycolors :colors \r\ +processcolor :mycolors :myshade \r\ +end \r\ +\ +to tasetpencolor :c \r\ +make "color (modulo (round :c) 100) \r\ +setpencolor :color + 8 \r\ +end \r\ +\ +make "colors [ \ +99 0 0 99 5 0 99 10 0 99 15 0 99 20 0 \ +99 25 0 99 30 0 99 35 0 99 40 0 99 45 0 \ +99 50 0 99 55 0 99 60 0 99 65 0 99 70 0 \ +99 75 0 99 80 0 99 85 0 99 90 0 99 95 0 \ +99 99 0 90 99 0 80 99 0 70 99 0 60 99 0 \ +50 99 0 40 99 0 30 99 0 20 99 0 10 99 0 \ + 0 99 0 0 99 5 0 99 10 0 99 15 0 99 20 \ + 0 99 25 0 99 30 0 99 35 0 99 40 0 99 45 \ + 0 99 50 0 99 55 0 99 60 0 99 65 0 99 70 \ + 0 99 75 0 99 80 0 99 85 0 99 90 0 99 95 \ + 0 99 99 0 95 99 0 90 99 0 85 99 0 80 99 \ + 0 75 99 0 70 99 0 65 99 0 60 99 0 55 99 \ + 0 50 99 0 45 99 0 40 99 0 35 99 0 30 99 \ + 0 25 99 0 20 99 0 15 99 0 10 99 0 5 99 \ + 0 0 99 5 0 99 10 0 99 15 0 99 20 0 99 \ +25 0 99 30 0 99 35 0 99 40 0 99 45 0 99 \ +50 0 99 55 0 99 60 0 99 65 0 99 70 0 99 \ +75 0 99 80 0 99 85 0 99 90 0 99 95 0 99 \ +99 0 99 99 0 90 99 0 80 99 0 70 99 0 60 \ +99 0 50 99 0 40 99 0 30 99 0 20 99 0 10] \r\ +make "shade 50 \r\ +tasetshade :shade \r') + + def _numbers_palette(self): + """ The basic Turtle Art numbers palette """ + + palette = make_palette('numbers', + colors=["#FF00FF", "#A000A0"], + help_string=_('Palette of numeric operators')) + + primitive_dictionary['plus'] = self._prim_plus + palette.add_block('plus2', + style='number-style', + label='+', + special_name=_('plus'), + prim_name='plus', + logo_command='sum', + help_string=_('adds two alphanumeric inputs')) + self.tw.lc.def_prim( + 'plus', 2, lambda self, x, y: primitive_dictionary['plus'](x, y)) + + primitive_dictionary['minus'] = self._prim_minus + palette.add_block('minus2', + style='number-style-porch', + label='–', + special_name=_('minus'), + prim_name='minus', + logo_command='taminus', + help_string=_('subtracts bottom numeric input from \ +top numeric input')) + self.tw.lc.def_prim( + 'minus', 2, lambda self, x, y: primitive_dictionary['minus'](x, y)) + define_logo_function('taminus', 'to taminus :y :x\routput sum :x minus \ +:y\rend\r') + + primitive_dictionary['product'] = self._prim_product + palette.add_block('product2', + style='number-style', + label='×', + special_name=_('multiply'), + prim_name='product', + logo_command='product', + help_string=_('multiplies two numeric inputs')) + self.tw.lc.def_prim( + 'product', 2, + lambda self, x, y: primitive_dictionary['product'](x, y)) + + primitive_dictionary['division'] = self._prim_careful_divide + palette.add_block('division2', + style='number-style-porch', + label='/', + special_name=_('divide'), + prim_name='division', + logo_command='quotient', + help_string=_('divides top numeric input (numerator) \ +by bottom numeric input (denominator)')) + self.tw.lc.def_prim( + 'division', 2, + lambda self, x, y: primitive_dictionary['division'](x, y)) + + primitive_dictionary['id'] = self._prim_identity + palette.add_block('identity2', + style='number-style-1arg', + label='←', + special_name=_('identity'), + prim_name='id', + help_string=_('identity operator used for extending \ +blocks')) + self.tw.lc.def_prim('id', 1, + lambda self, x: primitive_dictionary['id'](x)) + + primitive_dictionary['remainder'] = self._prim_mod + palette.add_block('remainder2', + style='number-style-porch', + label=_('mod'), + special_name=_('mod'), + prim_name='remainder', + logo_command='remainder', + help_string=_('modular (remainder) operator')) + self.tw.lc.def_prim('remainder', 2, + lambda self, x, y: primitive_dictionary['remainder'](x, y)) + + primitive_dictionary['sqrt'] = self._prim_sqrt + palette.add_block('sqrt', + style='number-style-1arg', + label=_('√'), + special_name=_('square root'), + prim_name='sqrt', + logo_command='tasqrt', + help_string=_('calculates square root')) + self.tw.lc.def_prim('sqrt', 1, + lambda self, x: primitive_dictionary['sqrt'](x)) + + primitive_dictionary['random'] = self._prim_random + palette.add_block('random', + style='number-style-block', + label=[_('random'), _('min'), _('max')], + default=[0, 100], + prim_name='random', + logo_command='tarandom', + help_string=_('returns random number between minimum \ +(top) and maximum (bottom) values')) + self.tw.lc.def_prim( + 'random', 2, lambda self, x, y: primitive_dictionary['random']( + x, y)) + define_logo_function('tarandom', 'to tarandom :min :max\r \ +output (random (:max - :min)) + :min\rend\r') + + palette.add_block('number', + style='box-style', + label='100', + default=100, + special_name=_('number'), + help_string=_('used as numeric input in mathematic \ +operators')) + + primitive_dictionary['more'] = self._prim_more + palette.add_block('greater2', + style='compare-porch-style', + label='>', + special_name=_('greater than'), + prim_name='greater?', + logo_command='greater?', + help_string=_('logical greater-than operator')) + self.tw.lc.def_prim( + 'greater?', 2, lambda self, x, y: primitive_dictionary['more'](x, y)) + + primitive_dictionary['less'] = self._prim_less + palette.add_block('less2', + style='compare-porch-style', + label='<', + special_name=_('less than'), + prim_name='less?', + logo_command='less?', + help_string=_('logical less-than operator')) + self.tw.lc.def_prim( + 'less?', 2, lambda self, x, y: primitive_dictionary['less'](x, y)) + + primitive_dictionary['equal'] = self._prim_equal + palette.add_block('equal2', + style='compare-style', + label='=', + special_name=_('equal'), + prim_name='equal?', + logo_command='equal?', + help_string=_('logical equal-to operator')) + self.tw.lc.def_prim( + 'equal?', 2, lambda self, x, y: primitive_dictionary['equal'](x, y)) + + palette.add_block('not', + style='not-style', + label=_('not'), + prim_name='not', + logo_command='not', + help_string=_('logical NOT operator')) + self.tw.lc.def_prim('not', 1, lambda self, x: not x) + + primitive_dictionary['and'] = self._prim_and + palette.add_block('and2', + style='boolean-style', + label=_('and'), + prim_name='and', + logo_command='and', + special_name=_('and'), + help_string=_('logical AND operator')) + self.tw.lc.def_prim( + 'and', 2, lambda self, x, y: primitive_dictionary['and'](x, y)) + + primitive_dictionary['or'] = self._prim_or + palette.add_block('or2', + style='boolean-style', + label=_('or'), + prim_name='or', + logo_command='or', + special_name=_('or'), + help_string=_('logical OR operator')) + self.tw.lc.def_prim( + 'or', 2, lambda self, x, y: primitive_dictionary['or'](x, y)) + + def _flow_palette(self): + """ The basic Turtle Art flow palette """ + + palette = make_palette('flow', + colors=["#FFC000", "#A08000"], + help_string=_('Palette of flow operators')) + + primitive_dictionary['wait'] = self._prim_wait + palette.add_block('wait', + style='basic-style-1arg', + label=_('wait'), + prim_name='wait', + default=1, + logo_command='wait', + help_string=_('pauses program execution a specified \ +number of seconds')) + self.tw.lc.def_prim('wait', 1, primitive_dictionary['wait'], True) + + primitive_dictionary['forever'] = self._prim_forever + palette.add_block('forever', + style='flow-style', + label=_('forever'), + prim_name='forever', + default=[None, 'vspace'], + logo_command='forever', + help_string=_('loops forever')) + self.tw.lc.def_prim('forever', 1, primitive_dictionary['forever'], True) + + primitive_dictionary['repeat'] = self._prim_repeat + palette.add_block('repeat', + style='flow-style-1arg', + label=[' ', _('repeat')], + prim_name='repeat', + default=[4, None, 'vspace'], + logo_command='repeat', + special_name=_('repeat'), + help_string=_('loops specified number of times')) + self.tw.lc.def_prim('repeat', 2, primitive_dictionary['repeat'], True) + + primitive_dictionary['if'] = self._prim_if + palette.add_block('if', + style='flow-style-boolean', + label=[' ', _('if'), _('then')], + prim_name='if', + default=[None, None, 'vspace'], + special_name=_('if then'), + logo_command='if', + help_string=_('if-then operator that uses boolean \ +operators from Numbers palette')) + self.tw.lc.def_prim('if', 2, primitive_dictionary['if'], True) + + primitive_dictionary['ifelse'] = self._prim_ifelse + palette.add_block('ifelse', + style='flow-style-else', + label=[' ', _('if'), _('then else')], + prim_name='ifelse', + default=[None, 'vspace', None, 'vspace'], + logo_command='ifelse', + special_name=_('if then else'), + help_string=_('if-then-else operator that uses \ +boolean operators from Numbers palette')) + self.tw.lc.def_prim('ifelse', 3, primitive_dictionary['ifelse'], True) + + palette.add_block('hspace', + style='flow-style-tail', + label=' ', + prim_name='nop', + special_name=_('horizontal space'), + help_string=_('jogs stack right')) + self.tw.lc.def_prim('nop', 0, lambda self: None) + + palette.add_block('vspace', + style='basic-style-extended-vertical', + label=' ', + prim_name='nop', + special_name=_('vertical space'), + help_string=_('jogs stack down')) + self.tw.lc.def_prim('nop', 0, lambda self: None) + + primitive_dictionary['stopstack'] = self._prim_stopstack + palette.add_block('stopstack', + style='basic-style-tail', + label=_('stop action'), + prim_name='stopstack', + logo_command='stop', + help_string=_('stops current action')) + self.tw.lc.def_prim('stopstack', 0, + lambda self: primitive_dictionary['stopstack']()) + + def _blocks_palette(self): + """ The basic Turtle Art blocks palette """ + + palette = make_palette('blocks', + colors=["#FFFF00", "#A0A000"], + help_string=_('Palette of variable blocks')) + + primitive_dictionary['start'] = self._prim_start + palette.add_block('start', + style='basic-style-head', + label=_('start'), + prim_name='start', + logo_command='to start\r', + help_string=_('connects action to toolbar run \ +buttons')) + self.tw.lc.def_prim('start', 0, + lambda self: primitive_dictionary['start']()) + + primitive_dictionary['setbox'] = self._prim_setbox + palette.add_block('storeinbox1', + style='basic-style-1arg', + label=_('store in box 1'), + prim_name='storeinbox1', + default=100, + logo_command='make "box1', + help_string=_('stores numeric value in Variable 1')) + self.tw.lc.def_prim('storeinbox1', 1, + lambda self, x: primitive_dictionary['setbox']( + 'box1', None, x)) + + palette.add_block('storeinbox2', + style='basic-style-1arg', + label=_('store in box 2'), + prim_name='storeinbox2', + default=100, + logo_command='make "box2', + help_string=_('stores numeric value in Variable 2')) + self.tw.lc.def_prim('storeinbox2', 1, + lambda self, x: primitive_dictionary['setbox']( + 'box2', None, x)) + + palette.add_block('string', + style='box-style', + label=_('text'), + default=_('text'), + special_name=_('text'), + help_string=_('string value')) + + palette.add_block('box1', + style='box-style', + label=_('box 1'), + prim_name='box1', + logo_command=':box1', + help_string=_('Variable 1 (numeric value)'), + value_block=True) + self.tw.lc.def_prim('box1', 0, lambda self: self.tw.lc.boxes['box1']) + + palette.add_block('box2', + style='box-style', + label=_('box 2'), + prim_name='box2', + logo_command=':box2', + help_string=_('Variable 2 (numeric value)'), + value_block=True) + self.tw.lc.def_prim('box2', 0, lambda self: self.tw.lc.boxes['box2']) + + primitive_dictionary['box'] = self._prim_box + palette.add_block('box', + style='number-style-1strarg', + label=_('box'), + prim_name='box', + default=_('my box'), + logo_command='box', + help_string=_('named variable (numeric value)')) + self.tw.lc.def_prim('box', 1, + lambda self, x: primitive_dictionary['box'](x)) + + palette.add_block('storein', + style='basic-style-2arg', + label=[_('store in'), _('box'), _('value')], + prim_name='storeinbox', + logo_command='storeinbox', + default=[_('my box'), 100], + help_string=_('stores numeric value in named \ +variable')) + self.tw.lc.def_prim('storeinbox', 2, + lambda self, x, y: primitive_dictionary['setbox']( + 'box3', x, y)) + + palette.add_block('hat', + style='basic-style-head-1arg', + label=_('action'), + prim_name='nop3', + default=_('stack'), + logo_command='to action', + help_string=_('top of nameable action stack')) + self.tw.lc.def_prim('nop3', 1, lambda self, x: None) + + palette.add_block('hat1', + style='basic-style-head', + label=_('action 1'), + prim_name='nop1', + logo_command='to stack1\r', + help_string=_('top of Action 1 stack')) + self.tw.lc.def_prim('nop1', 0, lambda self: None) + + palette.add_block('hat2', + style='basic-style-head', + label=_('action 2'), + prim_name='nop2', + logo_command='to stack2\r', + help_string=_('top of Action 2 stack')) + self.tw.lc.def_prim('nop2', 0, lambda self: None) + + primitive_dictionary['stack'] = self._prim_stack + palette.add_block('stack', + style='basic-style-1arg', + label=_('action'), + prim_name='stack', + logo_command='action', + default=_('action'), + help_string=_('invokes named action stack')) + self.tw.lc.def_prim('stack', 1, primitive_dictionary['stack'], True) + + primitive_dictionary['stack1'] = self._prim_stack1 + palette.add_block('stack1', + style='basic-style-extended-vertical', + label=_('action 1'), + prim_name='stack1', + logo_command='stack1', + help_string=_('invokes Action 1 stack')) + self.tw.lc.def_prim('stack1', 0, primitive_dictionary['stack1'], True) + + primitive_dictionary['stack2'] = self._prim_stack2 + palette.add_block('stack2', + style='basic-style-extended-vertical', + label=_('action 2'), + prim_name='stack2', + logo_command='stack2', + help_string=_('invokes Action 2 stack')) + self.tw.lc.def_prim('stack2', 0, primitive_dictionary['stack2'], True) + + def _trash_palette(self): + """ The basic Turtle Art turtle palette """ + + palette = make_palette('trash', + colors=["#FFFF00", "#A0A000"], + help_string=_('trash')) + + palette.add_block('empty', + style='basic-style-tail', + label=_('empty trash'), + help_string=_('permanently deletes items in trash')) + + palette.add_block('restoreall', + style='basic-style-head', + label=_('restore all'), + help_string=_('restore all blocks from trash')) + + palette.add_block('trashall', + style='basic-style-tail', + label=_('clear all'), + help_string=_('move all blocks to trash')) + + # Block primitives + + def _prim_and(self, x, y): + """ Logical and """ + return x & y + + def _prim_arc(self, cmd, value1, value2): + """ Turtle draws an arc of degree, radius """ + cmd(float(value1), float(value2)) + self.tw.lc.update_label_value( + 'xcor', self.tw.canvas.xcor / self.tw.coord_scale) + self.tw.lc.update_label_value( + 'ycor', self.tw.canvas.ycor / self.tw.coord_scale) + self.tw.lc.update_label_value('heading', self.tw.canvas.heading) + + def _prim_box(self, x): + """ Retrieve value from named box """ + if type(convert(x, float, False)) == float: + if int(float(x)) == x: + x = int(x) + try: + return self.tw.lc.boxes['box3' + str(x)] + except KeyError: + raise logoerror("#emptybox") + + def _prim_forever(self, blklist): + """ Do list forever """ + while True: + self.tw.lc.icall(self.tw.lc.evline, blklist[:]) + yield True + if self.tw.lc.procstop: + break + self.tw.lc.ireturn() + yield True + + def _prim_if(self, boolean, blklist): + """ If bool, do list """ + if boolean: + self.tw.lc.icall(self.tw.lc.evline, blklist[:]) + yield True + self.tw.lc.ireturn() + yield True + + def _prim_ifelse(self, boolean, list1, list2): + """ If bool, do list1, else do list2 """ + if boolean: + self.tw.lc.ijmp(self.tw.lc.evline, list1[:]) + yield True + else: + self.tw.lc.ijmp(self.tw.lc.evline, list2[:]) + yield True + + def _prim_move(self, cmd, value1, value2=None, pendown=True): + """ Turtle moves by method specified in value1 """ + if value2 is None: + cmd(value1) + else: + cmd(float(value1), float(value2), pendown=pendown) + self.tw.lc.update_label_value('xcor', + self.tw.canvas.xcor / self.tw.coord_scale) + self.tw.lc.update_label_value('ycor', + self.tw.canvas.ycor / self.tw.coord_scale) + + def _prim_or(self, x, y): + """ Logical or """ + return x | y + + def _prim_repeat(self, num, blklist): + """ Repeat list num times. """ + num = self.tw.lc.int(num) + for i in range(num): + self.tw.lc.icall(self.tw.lc.evline, blklist[:]) + yield True + if self.tw.lc.procstop: + break + self.tw.lc.ireturn() + yield True + + def _prim_right(self, value): + """ Turtle rotates clockwise """ + self.tw.canvas.right(float(value)) + self.tw.lc.update_label_value('heading', self.tw.canvas.heading) + + def _prim_set(self, name, cmd, value=None): + """ Set a value and update the associated value blocks """ + if value is not None: + cmd(value) + self.tw.lc.update_label_value(name, value) + + def _prim_setbox(self, name, x, val): + """ Define value of named box """ + if x is not None: + if type(convert(x, float, False)) == float: + if int(float(x)) == x: + x = int(x) + self.tw.lc.boxes[name + str(x)] = val + return + + self.tw.lc.boxes[name] = val + self.tw.lc.update_label_value(name, val) + + def _prim_stack(self, x): + """ Process a named stack """ + if type(convert(x, float, False)) == float: + if int(float(x)) == x: + x = int(x) + if 'stack3' + str(x) not in self.tw.lc.stacks or \ + self.tw.lc.stacks['stack3' + str(x)] is None: + raise logoerror("#nostack") + self.tw.lc.icall(self.tw.lc.evline, + self.tw.lc.stacks['stack3' + str(x)][:]) + yield True + self.tw.lc.procstop = False + self.tw.lc.ireturn() + yield True + + def _prim_stack1(self): + """ Process Stack 1 """ + if self.tw.lc.stacks['stack1'] is None: + raise logoerror("#nostack") + self.tw.lc.icall(self.tw.lc.evline, + self.tw.lc.stacks['stack1'][:]) + yield True + self.tw.lc.procstop = False + self.tw.lc.ireturn() + yield True + + def _prim_stack2(self): + """ Process Stack 2 """ + if self.tw.lc.stacks['stack2'] is None: + raise logoerror("#nostack") + self.tw.lc.icall(self.tw.lc.evline, self.tw.lc.stacks['stack2'][:]) + yield True + self.tw.lc.procstop = False + self.tw.lc.ireturn() + yield True + + def _prim_start(self): + """ Start block: recenter """ + if self.tw.running_sugar: + self.tw.activity.recenter() + + def _prim_stopstack(self): + """ Stop execution of a stack """ + self.tw.lc.procstop = True + + def _prim_wait(self, wait_time): + """ Show the turtle while we wait """ + self.tw.active_turtle.show() + endtime = _millisecond() + wait_time * 1000. + while _millisecond() < endtime: + yield True + self.tw.active_turtle.hide() + self.tw.lc.ireturn() + yield True + + # Math primitivies + + def _prim_careful_divide(self, x, y): + """ Raise error on divide by zero """ + try: + return x / y + except ZeroDivisionError: + raise logoerror("#zerodivide") + except TypeError: + try: + return self._string_to_num(x) / self._string_to_num(y) + except ZeroDivisionError: + raise logoerror("#zerodivide") + except ValueError: + raise logoerror("#syntaxerror") + except TypeError: + raise logoerror("#notanumber") + + def _prim_equal(self, x, y): + """ Numeric and logical equal """ + try: + return float(x) == float(y) + except TypeError: + typex, typey = False, False + if strtype(x): + typex = True + if strtype(y): + typey = True + if typex and typey: + return x == y + try: + return self._string_to_num(x) == self._string_to_num(y) + except ValueError: + raise logoerror("#syntaxerror") + + def _prim_less(self, x, y): + """ Compare numbers and strings """ + try: + return float(x) < float(y) + except ValueError: + typex, typey = False, False + if strtype(x): + typex = True + if strtype(y): + typey = True + if typex and typey: + return x < y + try: + return self._string_to_num(x) < self._string_to_num(y) + except TypeError: + raise logoerror("#notanumber") + + def _prim_more(self, x, y): + """ Compare numbers and strings """ + return self._prim_less(y, x) + + def _prim_plus(self, x, y): + """ Add numbers, concat strings """ + if _num_type(x) and _num_type(y): + return(x + y) + else: + if _num_type(x): + xx = str(round_int(x)) + else: + xx = str(x) + if _num_type(y): + yy = str(round_int(y)) + else: + yy = str(y) + return(xx + yy) + + def _prim_minus(self, x, y): + """ Numerical subtraction """ + if _num_type(x) and _num_type(y): + return(x - y) + try: + return self._string_to_num(x) - self._string_to_num(y) + except TypeError: + raise logoerror("#notanumber") + + def _prim_product(self, x, y): + """ Numerical multiplication """ + if _num_type(x) and _num_type(y): + return(x * y) + try: + return self._string_to_num(x) * self._string_to_num(y) + except TypeError: + raise logoerror("#notanumber") + + def _prim_mod(self, x, y): + """ Numerical mod """ + if _num_type(x) and _num_type(y): + return(x % y) + try: + return self._string_to_num(x) % self._string_to_num(y) + except TypeError: + raise logoerror("#notanumber") + except ValueError: + raise logoerror("#syntaxerror") + + def _prim_sqrt(self, x): + """ Square root """ + if _num_type(x): + if x < 0: + raise logoerror("#negroot") + return sqrt(x) + try: + return sqrt(self._string_to_num(x)) + except ValueError: + raise logoerror("#negroot") + except TypeError: + raise logoerror("#notanumber") + + def _prim_random(self, x, y): + """ Random integer """ + if _num_type(x) and _num_type(y): + return(int(round(uniform(x, y), 0))) + xx, xflag = chr_to_ord(x) + yy, yflag = chr_to_ord(y) + if xflag and yflag: + return chr(int(round(uniform(xx, yy), 0))) + if not xflag: + xx = self._string_to_num(x) + if not yflag: + yy = self._string_to_num(y) + try: + return(int(round(uniform(xx, yy), 0))) + except TypeError: + raise logoerror("#notanumber") + + def _prim_identity(self, x): + """ Identity function """ + return(x) + + # Utilities + + def _string_to_num(self, x): + """ Try to comvert a string to a number """ + if type(x) is float: + return(x) + if type(x) is int: + return(x) + if type(x) is ord: + return(int(x)) + xx = convert(x.replace(self.tw.decimal_point, '.'), float) + if type(xx) is float: + return xx + else: + xx, xflag = chr_to_ord(x) + if xflag: + return xx + else: + raise logoerror("#syntaxerror") + + def _make_constant(self, palette, block_name, constant): + """ Factory for constant blocks """ + palette.add_block(block_name, style='box-style', + label=_(block_name), prim_name=block_name, + logo_command=block_name) + self.tw.lc.def_prim(block_name, 0, lambda self: constant) diff --git a/TurtleArt/tablock.py b/TurtleArt/tablock.py index 05b95ae..bcca2cd 100644 --- a/TurtleArt/tablock.py +++ b/TurtleArt/tablock.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -#Copyright (c) 2010 Walter Bender +#Copyright (c) 2010,11 Walter Bender #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal @@ -20,31 +20,21 @@ #THE SOFTWARE. import gtk - from gettext import gettext as _ -from taconstants import EXPANDABLE, EXPANDABLE_BLOCKS, EXPANDABLE_ARGS, \ - PRIMITIVES, OLD_NAMES, BLOCK_SCALE, BLOCK_NAMES, CONTENT_BLOCKS, \ - PALETTES, COLORS, BASIC_STYLE_HEAD, BASIC_STYLE_HEAD_1ARG, \ - BASIC_STYLE_TAIL, BASIC_STYLE, BASIC_STYLE_EXTENDED, BASIC_STYLE_1ARG, \ - BASIC_STYLE_VAR_ARG, BULLET_STYLE, BASIC_STYLE_2ARG, BOX_STYLE, \ - BOX_STYLE_MEDIA, NUMBER_STYLE, NUMBER_STYLE_VAR_ARG, NUMBER_STYLE_BLOCK, \ - NUMBER_STYLE_PORCH, NUMBER_STYLE_1ARG, NUMBER_STYLE_1STRARG, \ - COMPARE_STYLE, BOOLEAN_STYLE, NOT_STYLE, FLOW_STYLE, FLOW_STYLE_TAIL, \ - FLOW_STYLE_1ARG, FLOW_STYLE_BOOLEAN, FLOW_STYLE_WHILE, FLOW_STYLE_ELSE, \ - COLLAPSIBLE_TOP, COLLAPSIBLE_TOP_NO_ARM, COLLAPSIBLE_TOP_NO_LABEL, \ - COLLAPSIBLE_TOP_NO_ARM_NO_LABEL, COLLAPSIBLE_BOTTOM, PORTFOLIO_STYLE_2x2, \ - PORTFOLIO_STYLE_1x1, PORTFOLIO_STYLE_2x1, PORTFOLIO_STYLE_1x2, \ - STANDARD_STROKE_WIDTH, BOX_COLORS, GRADIENT_COLOR, \ - BASIC_STYLE_EXTENDED_VERTICAL, CONSTANTS, INVISIBLE +from taconstants import EXPANDABLE, EXPANDABLE_ARGS, OLD_NAMES, CONSTANTS, \ + STANDARD_STROKE_WIDTH, BLOCK_SCALE, BOX_COLORS, GRADIENT_COLOR +from tapalette import palette_blocks, block_colors, expandable_blocks, \ + content_blocks, block_names, block_primitives, block_styles, \ + special_block_colors from tasprite_factory import SVG, svg_str_to_pixbuf import sprites -import logging -_logger = logging.getLogger('turtleart-activity') +from tautils import debug_output, error_output class Blocks: + """ A class for the list of blocks and everything they share in common """ def __init__(self, font_scale_factor=1, decimal_point='.'): @@ -130,7 +120,9 @@ class Block: """ A class for the individual blocks """ def __init__(self, block_list, sprite_list, name, x, y, type='block', - values=[], scale=BLOCK_SCALE, colors=['#FF0000', '#A00000']): + values=[], scale=BLOCK_SCALE[0], + colors=['#A0A0A0', '#808080']): + self.block_list = block_list self.spr = None self.shapes = [None, None] @@ -150,6 +142,51 @@ class Block: self._font_size = [6.0, 4.5] self._image = None + self.block_methods = { + 'basic-style': self._make_basic_style, + 'blank-style': self._make_blank_style, + 'basic-style-head': self._make_basic_style_head, + 'basic-style-head-1arg': self._make_basic_style_head_1arg, + 'basic-style-tail': self._make_basic_style_tail, + 'basic-style-extended': [self._make_basic_style, 16, 16], + 'basic-style-extended-vertical': [self._make_basic_style, 0, 4], + 'basic-style-1arg': self._make_basic_style_1arg, + 'basic-style-2arg': self._make_basic_style_2arg, + 'basic-style-3arg': self._make_basic_style_3arg, + 'basic-style-var-arg': self._make_basic_style_var_arg, + 'invisible': self._make_invisible_style, + 'bullet-style': self._make_bullet_style, + 'box-style': self._make_box_style, + 'box-style-media': self._make_media_style, + 'number-style': self._make_number_style, + 'number-style-block': self._make_number_style_block, + 'number-style-porch': self._make_number_style_porch, + 'number-style-1arg': self._make_number_style_1arg, + 'number-style-1strarg': self._make_number_style_1strarg, + 'number-style-var-arg': self._make_number_style_var_arg, + 'compare-style': self._make_compare_style, + 'compare-porch-style': self._make_compare_porch_style, + 'boolean-style': self._make_boolean_style, + 'not-style': self._make_not_style, + 'flow-style': self._make_flow_style, + 'flow-style-tail': self._make_flow_style_tail, + 'flow-style-1arg': self._make_flow_style_1arg, + 'flow-style-boolean': self._make_flow_style_boolean, + 'flow-style-while': self._make_flow_style_while, + 'flow-style-else': self._make_flow_style_else, + 'collapsible-top': [self._make_collapsible_style_top, True, True], + 'collapsible-top-no-arm': [self._make_collapsible_style_top, + False, True], + 'collapsible-top-no-label': [self._make_collapsible_style_top, + True, False], + 'collapsible-top-no-arm-no-label': [ + self._make_collapsible_style_top, False, False], + 'collapsible-bottom': self._make_collapsible_style_bottom, + 'portfolio-style-2x2': self._make_portfolio_style_2x2, + 'portfolio-style-1x1': self._make_portfolio_style_1x1, + 'portfolio-style-2x1': self._make_portfolio_style_2x1, + 'portfolio-style-1x2': self._make_portfolio_style_1x2} + if self.name in OLD_NAMES: self.name = OLD_NAMES[self.name] @@ -162,22 +199,38 @@ class Block: # If there is already a block with the same name, reuse it copy_block = None - if self.name not in EXPANDABLE and \ - self.name not in EXPANDABLE_BLOCKS and \ - self.name not in EXPANDABLE_ARGS and \ - self.name not in BOX_STYLE and \ - self.name not in ['sandwichtop', 'sandwichtop_no_label']: + if self.cloneable(): for b in self.block_list.list: if b.scale == self.scale and b.name == self.name: copy_block = b break self._new_block_from_factory(sprite_list, x, y, copy_block) - if name in PRIMITIVES: - self.primitive = PRIMITIVES[self.name] + if name in block_primitives: + self.primitive = block_primitives[self.name] self.block_list.append_to_list(self) + def expandable(self): + """ Can this block be expanded? """ + if self.name in EXPANDABLE: + return True + if self.name in expandable_blocks: + return True + if self.name in EXPANDABLE_ARGS: + return True + return False + + def cloneable(self): + """ Is it safe to clone this block? """ + if self.expandable(): + return False + if self.name in block_styles['box-style']: + return False + if self.name in ['sandwichtop', 'sandwichtop_no_label']: + return False + return True + def highlight(self): """ We may want to highlight a block... """ if self.spr is not None: @@ -310,7 +363,6 @@ class Block: return (self.ex, self.ey) def _new_block_from_factory(self, sprite_list, x, y, copy_block=None): - self.svg = SVG() self.svg.set_scale(self.scale) self.svg.set_innie([False]) @@ -335,10 +387,7 @@ class Block: self.shapes[1] = copy_block.shapes[1] self.docks = copy_block.docks[:] else: - if (self.name in EXPANDABLE or \ - self.name in EXPANDABLE_BLOCKS or \ - self.name in EXPANDABLE_ARGS) and \ - self.type == 'block': + if self.expandable() and self.type == 'block': self.svg.set_show(True) self._make_block(self.svg) @@ -358,11 +407,11 @@ class Block: else: self._set_labels(i, str(v)) elif self.type == 'block' and self.name in CONSTANTS: - self._set_labels(0, BLOCK_NAMES[self.name][0] + ' = ' + \ + self._set_labels(0, block_names[self.name][0] + ' = ' + \ str(CONSTANTS[self.name])) - elif self.name in BLOCK_NAMES: - for i, n in enumerate(BLOCK_NAMES[self.name]): + elif self.name in block_names: + for i, n in enumerate(block_names[self.name]): self._set_labels(i, n) if copy_block is None: @@ -374,18 +423,24 @@ class Block: self.svg.margins[2], self.svg.margins[3]) def _set_label_attributes(self): - if self.name in CONTENT_BLOCKS: + if self.name in content_blocks: n = len(self.values) if n == 0: n = 1 # Force a scale to be set, even if there is no value. else: - if self.name in BLOCK_NAMES: - n = len(BLOCK_NAMES[self.name]) + if self.name in block_names: + n = len(block_names[self.name]) else: - _logger.debug('WARNING: unknown block name %s' % (self.name)) + debug_output('WARNING: unknown block name %s' % (self.name)) n = 0 for i in range(n): - if i == 1: # top + if self.name in block_styles['compare-porch-style']: + self.spr.set_label_attributes(int(self._font_size[0] + 0.5), + True, 'center', 'bottom', i) + elif self.name in block_styles['number-style-porch']: + self.spr.set_label_attributes(int(self._font_size[0] + 0.5), + True, 'right', 'bottom', i) + elif i == 1: # top self.spr.set_label_attributes(int(self._font_size[1] + 0.5), True, 'right', 'top', i) elif i == 2: # bottom @@ -405,92 +460,26 @@ class Block: self._bottom = 0 self.svg.set_stroke_width(STANDARD_STROKE_WIDTH) self.svg.clear_docks() - if self.name in BASIC_STYLE: - self._make_basic_style(svg) - elif self.name in BASIC_STYLE_HEAD: - self._make_basic_style_head(svg) - elif self.name in BASIC_STYLE_EXTENDED: - self._make_basic_style(svg, 16, 16) - elif self.name in BASIC_STYLE_EXTENDED_VERTICAL: - self._make_basic_style(svg, 0, 4) - elif self.name in BASIC_STYLE_HEAD_1ARG: - self._make_basic_style_head_1arg(svg) - elif self.name in BASIC_STYLE_TAIL: - self._make_basic_style_tail(svg) - elif self.name in BASIC_STYLE_1ARG: - self._make_basic_style_1arg(svg) - elif self.name in BASIC_STYLE_2ARG: - self._make_basic_style_2arg(svg) - elif self.name in BASIC_STYLE_VAR_ARG: - self._make_basic_style_var_arg(svg) - elif self.name in BULLET_STYLE: - self._make_bullet_style(svg) - elif self.name in BOX_STYLE: - self._make_box_style(svg) - elif self.name in BOX_STYLE_MEDIA: - self._make_media_style(svg) - elif self.name in NUMBER_STYLE: - self._make_number_style(svg) - elif self.name in NUMBER_STYLE_BLOCK: - self._make_number_style_block(svg) - elif self.name in NUMBER_STYLE_VAR_ARG: - self._make_number_style_var_arg(svg) - elif self.name in NUMBER_STYLE_1ARG: - self._make_number_style_1arg(svg) - elif self.name in NUMBER_STYLE_1STRARG: - self._make_number_style_1strarg(svg) - elif self.name in NUMBER_STYLE_PORCH: - self._make_number_style_porch(svg) - elif self.name in COMPARE_STYLE: - self._make_compare_style(svg) - elif self.name in BOOLEAN_STYLE: - self._make_boolean_style(svg) - elif self.name in NOT_STYLE: - self._make_not_style(svg) - elif self.name in FLOW_STYLE: - self._make_flow_style(svg) - elif self.name in FLOW_STYLE_TAIL: - self._make_flow_style_tail(svg) - elif self.name in FLOW_STYLE_1ARG: - self._make_flow_style_1arg(svg) - elif self.name in FLOW_STYLE_BOOLEAN: - self._make_flow_style_boolean(svg) - elif self.name in FLOW_STYLE_WHILE: - self._make_flow_style_while(svg) - elif self.name in FLOW_STYLE_ELSE: - self._make_flow_style_else(svg) - elif self.name in COLLAPSIBLE_TOP: - self._make_collapsible_style_top(svg, arm=True, label=True) - elif self.name in COLLAPSIBLE_TOP_NO_ARM: - self._make_collapsible_style_top(svg, arm=False, label=True) - elif self.name in COLLAPSIBLE_TOP_NO_LABEL: - self._make_collapsible_style_top(svg, arm=True, label=False) - elif self.name in COLLAPSIBLE_TOP_NO_ARM_NO_LABEL: - self._make_collapsible_style_top(svg, arm=False, label=False) - elif self.name in COLLAPSIBLE_BOTTOM: - self._make_collapsible_style_bottom(svg) - elif self.name in INVISIBLE: - self._make_invisible_style(svg) - elif self.name in PORTFOLIO_STYLE_2x2: - self._make_portfolio_style_2x2(svg) - elif self.name in PORTFOLIO_STYLE_2x1: - self._make_portfolio_style_2x1(svg) - elif self.name in PORTFOLIO_STYLE_1x1: - self._make_portfolio_style_1x1(svg) - elif self.name in PORTFOLIO_STYLE_1x2: - self._make_portfolio_style_1x2(svg) - else: - self._make_basic_style(svg) - _logger.debug("WARNING: I don't know how to create a %s block" % \ - (self.name)) + for k in block_styles.keys(): + if self.name in block_styles[k]: + if type(self.block_methods[k]) == type([]): + self.block_methods[k][0](svg, self.block_methods[k][1], + self.block_methods[k][2]) + else: + self.block_methods[k](svg) + return + error_output('block type not found %s' % (self.name)) + self.block_methods['basic-style'](svg) def _set_colors(self, svg): if self.name in BOX_COLORS: self.colors = BOX_COLORS[self.name] + elif self.name in special_block_colors: + self.colors = special_block_colors[self.name] else: - for p in range(len(PALETTES)): - if self.name in PALETTES[p]: - self.colors = COLORS[p] + for p in range(len(palette_blocks)): + if self.name in palette_blocks[p]: + self.colors = block_colors[p] self.svg.set_colors(self.colors) def _make_basic_style(self, svg, extend_x=0, extend_y=0): @@ -500,6 +489,13 @@ class Block: self.svg.docks[0][1]], ['flow', False, self.svg.docks[1][0], self.svg.docks[1][1]]] + def _make_blank_style(self, svg, extend_x=0, extend_y=0): + self.svg.expand(self.dx + self.ex + extend_x, self.ey + extend_y) + self.svg.set_slot(False) + self.svg.set_tab(False) + self._make_block_graphics(svg, self.svg.basic_block) + self.docks = [] + def _make_basic_style_head(self, svg): self.svg.expand(10 + self.dx + self.ex, self.ey) self.svg.set_slot(False) @@ -553,6 +549,21 @@ class Block: ['flow', False, self.svg.docks[3][0], self.svg.docks[3][1]]] + def _make_basic_style_3arg(self, svg): + self.svg.expand(10 + self.dx + self.ex, self.ey) + self.svg.set_innie([True, True, True]) + self._make_block_graphics(svg, self.svg.basic_block) + self.docks = [['flow', True, self.svg.docks[0][0], + self.svg.docks[0][1]], + ['number', False, self.svg.docks[1][0], + self.svg.docks[1][1]], + ['number', False, self.svg.docks[2][0], + self.svg.docks[2][1]], + ['number', False, self.svg.docks[3][0], + self.svg.docks[3][1]], + ['flow', False, self.svg.docks[4][0], + self.svg.docks[4][1]]] + def _make_basic_style_var_arg(self, svg): self.svg.expand(10 + self.dx + self.ex, self.ey) innie = [True] @@ -706,6 +717,10 @@ class Block: self.svg.docks[2][1]], ['unavailable', False, 0, 0, ')']] + def _make_compare_porch_style(self, svg): + self.svg.set_porch(True) + self._make_compare_style(svg) + def _make_boolean_style(self, svg): self.svg.expand(10 + self.dx + self.ex, self.ey) self._make_block_graphics(svg, self.svg.boolean_and_or) diff --git a/TurtleArt/tacanvas.py b/TurtleArt/tacanvas.py index d4395a2..bf866fb 100644 --- a/TurtleArt/tacanvas.py +++ b/TurtleArt/tacanvas.py @@ -1,5 +1,5 @@ #Copyright (c) 2007-8, Playful Invention Company. -#Copyright (c) 2008-10, Walter Bender +#Copyright (c) 2008-11, Walter Bender #Copyright (c) 2011 Collabora Ltd. #Permission is hereby granted, free of charge, to any person obtaining a copy @@ -24,15 +24,15 @@ import gtk from math import sin, cos, pi import pango import cairo +import base64 +from gettext import gettext as _ from sprites import Sprite from tasprite_factory import SVG -from tautils import image_to_base64, data_to_string, round_int +from tautils import image_to_base64, get_path, data_to_string, round_int, \ + debug_output from taconstants import CANVAS_LAYER, BLACK, WHITE -import logging -_logger = logging.getLogger('turtleart-activity') - def wrap100(n): """ A variant on mod... 101 -> 99; 199 -> 1 """ @@ -43,6 +43,23 @@ def wrap100(n): return n +def calc_poly_bounds(poly_points): + """ Calculate the minx, miny, width, height of polygon """ + minx = poly_points[0][0] + miny = poly_points[0][1] + maxx, maxy = minx, miny + for p in poly_points: + if p[0] < minx: + minx = p[0] + elif p[0] > maxx: + maxx = p[0] + if p[1] < miny: + miny = p[1] + elif p[1] > maxy: + maxy = p[1] + return(minx, miny, maxx - minx, maxy - miny) + + def calc_shade(c, s, invert=False): """ Convert a color to the current shade (lightness/darkness). """ # Assumes 16 bit input values @@ -119,8 +136,8 @@ class TurtleGraphics: self.fgcolor = self.cm.alloc_color('red') self.bgrgb = [255, 248, 222] self.bgcolor = self.cm.alloc_color('#fff8de') - self.textsize = 48 # depreciated - self.textcolor = self.cm.alloc_color('blue') + self.textsize = 48 # deprecated + self.textcolor = self.cm.alloc_color('red') # deprecated self.tw.active_turtle.show() self.shade = 0 self.pendown = False @@ -128,7 +145,6 @@ class TurtleGraphics: self.ycor = 0 self.heading = 0 self.pensize = 5 - self.tcolor = 0 self.color = 0 self.gray = 100 self.fill = False @@ -142,57 +158,72 @@ class TurtleGraphics: """ Start accumulating points of a polygon to fill. """ self.fill = True self.poly_points = [] + if self.tw.saving_svg: + self.tw.svg_string += '' def stop_fill(self): """ Fill the polygon. """ self.fill = False if len(self.poly_points) == 0: return - minx = self.poly_points[0][0] - miny = self.poly_points[0][1] - maxx = minx - maxy = miny - for p in self.poly_points: - if p[0] < minx: - minx = p[0] - elif p[0] > maxx: - maxx = p[0] - if p[1] < miny: - miny = p[1] - elif p[1] > maxy: - maxy = p[1] - w = maxx - minx - h = maxy - miny - self.canvas.images[0].draw_polygon(self.gc, True, self.poly_points) + self.fill_polygon(self.poly_points) + if self.tw.sharing(): + shared_poly_points = [] + for p in self.poly_points: + shared_poly_points.append((self.screen_to_turtle_coordinates( + p[0], p[1]))) + event = "F|%s" % (data_to_string([self._get_my_nick(), + shared_poly_points])) + self.tw.send_event(event) + self.poly_points = [] + if self.tw.saving_svg: + self.tw.svg_string += '' + + def fill_polygon(self, poly_points): + minx, miny, w, h = calc_poly_bounds(poly_points) + self.canvas.images[0].draw_polygon(self.gc, True, poly_points) self.invalt(minx - self.pensize * self.tw.coord_scale / 2 - 3, miny - self.pensize * self.tw.coord_scale / 2 - 3, w + self.pensize * self.tw.coord_scale + 6, h + self.pensize * self.tw.coord_scale + 6) - self.poly_points = [] + if self.tw.saving_svg and self.pendown: + self.svg.set_fill_color("#%02x%02x%02x" % (self.fgrgb[0], + self.fgrgb[1], + self.fgrgb[2])) + self.tw.svg_string += self.svg.new_path(poly_points[0][0], + poly_points[0][1]) + for p in range(len(poly_points)): + if p > 0: + self.tw.svg_string += self.svg.line_to(poly_points[p][0], + poly_points[p][1]) + self.tw.svg_string += "\"\n" + self.tw.svg_string += self.svg.style() + self.svg.set_fill_color('none') def clearscreen(self, share=True): """Clear the canvas and reset most graphics attributes to defaults.""" - rect = gtk.gdk.Rectangle(0, 0, self.width, self.height) + rect = gtk.gdk.Rectangle(0, 0, self.width * 2, self.height * 2) self.gc.set_foreground(self.bgcolor) self.canvas.images[0].draw_rectangle(self.gc, True, *rect) self.invalt(0, 0, self.width, self.height) self.setpensize(5, share) self.setgray(100, share) self.setcolor(0, share) - self.settextcolor(70) self.setshade(50, share) for turtle_key in iter(self.tw.turtles.dict): - self.set_turtle(turtle_key) - self.tw.active_turtle.set_color(0) - self.tw.active_turtle.set_shade(50) - self.tw.active_turtle.set_gray(100) - self.tw.active_turtle.set_pen_size(5) - self.tw.active_turtle.reset_shapes() - self.seth(0, share) - self.setpen(False, share) - self.setxy(0, 0, share) - self.setpen(True, share) - self.tw.active_turtle.hide() + # Don't reset remote turtles + if not self.tw.remote_turtle(turtle_key): + self.set_turtle(turtle_key) + self.tw.active_turtle.set_color(0) + self.tw.active_turtle.set_shade(50) + self.tw.active_turtle.set_gray(100) + self.tw.active_turtle.set_pen_size(5) + self.tw.active_turtle.reset_shapes() + self.seth(0, share) + self.setpen(False, share) + self.setxy(0, 0, share) + self.setpen(True, share) + self.tw.active_turtle.hide() self.set_turtle(self.tw.default_turtle_name) self.tw.svg_string = '' self.svg.reset_min_max() @@ -208,44 +239,46 @@ class TurtleGraphics: self.xcor += nn * sin(self.heading * DEGTOR) self.ycor += nn * cos(self.heading * DEGTOR) except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) + debug_output("bad value sent to %s" % (__name__), + self.tw.running_sugar) return if self.pendown: self.draw_line(oldx, oldy, self.xcor, self.ycor) self.move_turtle() - if self.tw.saving_svg and self.pendown: - self.tw.svg_string += self.svg.new_path(oldx, - self.height / 2 - oldy) - self.tw.svg_string += self.svg.line_to(self.xcor, - self.height / 2 - self.ycor) - self.tw.svg_string += "\"\n" - self.tw.svg_string += self.svg.style() - event = "f|%s" % (data_to_string([self._get_my_nick(), int(n)])) - self._send_event(event, share) + + if self.tw.sharing() and share: + event = "f|%s" % (data_to_string([self._get_my_nick(), int(n)])) + self.tw.send_event(event) def seth(self, n, share=True): """ Set the turtle heading. """ try: self.heading = n except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) + debug_output("bad value sent to %s" % (__name__), + self.tw.running_sugar) return self.heading %= 360 self.turn_turtle() - event = "r|%s" % (data_to_string([self._get_my_nick(), round_int(self.heading)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "r|%s" % (data_to_string([self._get_my_nick(), + round_int(self.heading)])) + self.tw.send_event(event) def right(self, n, share=True): """ Rotate turtle clockwise """ try: self.heading += n except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) + debug_output("bad value sent to %s" % (__name__), + self.tw.running_sugar) return self.heading %= 360 self.turn_turtle() - event = "r|%s" % (data_to_string([self._get_my_nick(), round_int(self.heading)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "r|%s" % (data_to_string([self._get_my_nick(), + round_int(self.heading)])) + self.tw.send_event(event) def arc(self, a, r, share=True): """ Draw an arc """ @@ -257,11 +290,14 @@ class TurtleGraphics: else: self.rarc(a, rr) except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) + debug_output("bad value sent to %s" % (__name__), + self.tw.running_sugar) return self.move_turtle() - event = "a|%s" % (data_to_string([self._get_my_nick(), [round_int(a), round_int(r)]])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "a|%s" % (data_to_string([self._get_my_nick(), + [round_int(a), round_int(r)]])) + self.tw.send_event(event) def rarc(self, a, r): """ draw a clockwise arc """ @@ -274,8 +310,7 @@ class TurtleGraphics: oldx, oldy = self.xcor, self.ycor cx = self.xcor + r * cos(self.heading * DEGTOR) cy = self.ycor - r * sin(self.heading * DEGTOR) - x = self.width / 2 + int(cx - r) - y = self.height / 2 - int(cy + r) + x, y = self.turtle_to_screen_coordinates(int(cx - r), int(cy + r)) w = int(2 * r) h = w if self.pendown: @@ -289,10 +324,10 @@ class TurtleGraphics: self.xcor = cx - r * cos(self.heading * DEGTOR) self.ycor = cy + r * sin(self.heading * DEGTOR) if self.tw.saving_svg and self.pendown: - self.tw.svg_string += self.svg.new_path(oldx, - self.height / 2 - oldy) - self.tw.svg_string += self.svg.arc_to(self.xcor, - self.height / 2 - self.ycor, r, a, 0, s) + x, y = self.turtle_to_screen_coordinates(oldx, oldy) + self.tw.svg_string += self.svg.new_path(x, y) + x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor) + self.tw.svg_string += self.svg.arc_to(x, y, r, a, 0, s) self.tw.svg_string += "\"\n" self.tw.svg_string += self.svg.style() @@ -307,8 +342,7 @@ class TurtleGraphics: oldx, oldy = self.xcor, self.ycor cx = self.xcor - r * cos(self.heading * DEGTOR) cy = self.ycor + r * sin(self.heading * DEGTOR) - x = self.width / 2 + int(cx - r) - y = self.height / 2 - int(cy + r) + x, y = self.turtle_to_screen_coordinates(int(cx - r), int(cy + r)) w = int(2 * r) h = w if self.pendown: @@ -323,11 +357,10 @@ class TurtleGraphics: self.xcor = cx + r * cos(self.heading * DEGTOR) self.ycor = cy - r * sin(self.heading * DEGTOR) if self.tw.saving_svg and self.pendown: - self.tw.svg_string += self.svg.new_path(oldx, - self.height / 2 - oldy) - self.tw.svg_string += self.svg.arc_to(self.xcor, - self.height / 2 - self.ycor, - r, a, 0, s) + x, y = self.turtle_to_screen_coordinates(oldx, oldy) + self.tw.svg_string += self.svg.new_path(x, y) + x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor) + self.tw.svg_string += self.svg.arc_to(x, y, r, a, 0, s) self.tw.svg_string += "\"\n" self.tw.svg_string += self.svg.style() @@ -339,16 +372,19 @@ class TurtleGraphics: try: self.xcor, self.ycor = x, y except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) + debug_output("bad value sent to %s" % (__name__), + self.tw.running_sugar) return if self.pendown and pendown: self.gc.set_foreground(self.fgcolor) self.draw_line(oldx, oldy, self.xcor, self.ycor) - self.move_turtle() - event = "x|%s" % (data_to_string([self._get_my_nick(), [round_int(x), round_int(y)]])) - self._send_event(event, share) + + if self.tw.sharing() and share: + event = "x|%s" % (data_to_string([self._get_my_nick(), + [round_int(x), round_int(y)]])) + self.tw.send_event(event) def setpensize(self, ps, share=True): """ Set the pen size """ @@ -357,81 +393,85 @@ class TurtleGraphics: ps = 0 self.pensize = ps except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) + debug_output("bad value sent to %s" % (__name__), + self.tw.running_sugar) return self.tw.active_turtle.set_pen_size(ps) self.gc.set_line_attributes(int(self.pensize * self.tw.coord_scale), - gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_MITER) + gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_MITER) self.svg.set_stroke_width(self.pensize) - event = "w|%s" % (data_to_string([self._get_my_nick(), round_int(ps)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "w|%s" % (data_to_string([self._get_my_nick(), + round_int(ps)])) + self.tw.send_event(event) def setcolor(self, c, share=True): """ Set the pen color """ try: self.color = c - self.tcolor = c except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) + debug_output("bad value sent to %s" % (__name__), + self.tw.running_sugar) return self.tw.active_turtle.set_color(c) self.set_fgcolor() - self.set_textcolor() - event = "c|%s" % (data_to_string([self._get_my_nick(), round_int(c)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "c|%s" % (data_to_string([self._get_my_nick(), + round_int(c)])) + self.tw.send_event(event) def setgray(self, g, share=True): """ Set the gray level """ try: self.gray = g except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) + debug_output("bad value sent to %s" % (__name__), + self.tw.running_sugar) return if self.gray < 0: self.gray = 0 if self.gray > 100: self.gray = 100 self.set_fgcolor() - self.set_textcolor() self.tw.active_turtle.set_gray(self.gray) - event = "g|%s" % (data_to_string([self._get_my_nick(), round_int(self.gray)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "g|%s" % (data_to_string([self._get_my_nick(), + round_int(self.gray)])) + self.tw.send_event(event) - def settextcolor(self, c): + def settextcolor(self, c): # deprecated """ Set the text color """ - try: - self.tcolor = c - except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) - return - self.set_textcolor() + return - def settextsize(self, c): # depreciated + def settextsize(self, c): # deprecated """ Set the text size """ try: self.tw.textsize = c except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) + debug_output("bad value sent to %s" % (__name__), + self.tw.running_sugar) def setshade(self, s, share=True): """ Set the color shade """ try: self.shade = s except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) + debug_output("bad value sent to %s" % (__name__), + self.tw.running_sugar) return self.tw.active_turtle.set_shade(s) self.set_fgcolor() - self.set_textcolor() - event = "s|%s" % (data_to_string([self._get_my_nick(), round_int(s)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "s|%s" % (data_to_string([self._get_my_nick(), + round_int(s)])) + self.tw.send_event(event) def fillscreen(self, c, s): """ Fill screen with color/shade and reset to defaults """ oldc, olds = self.color, self.shade self.setcolor(c, False) self.setshade(s, False) - rect = gtk.gdk.Rectangle(0, 0, self.width, self.height) + rect = gtk.gdk.Rectangle(0, 0, self.width * 2, self.height * 2) self.gc.set_foreground(self.fgcolor) self.bgrgb = self.fgrgb[:] self.canvas.images[0].draw_rectangle(self.gc, True, *rect) @@ -473,16 +513,17 @@ class TurtleGraphics: def set_textcolor(self): """ Set the text color to foreground color. """ - self.tw.textcolor = self.fgcolor + return def setpen(self, bool, share=True): """ Lower or raise the pen """ self.pendown = bool self.tw.active_turtle.set_pen_state(bool) - event = "p|%s" % (data_to_string([self._get_my_nick(), bool])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "p|%s" % (data_to_string([self._get_my_nick(), bool])) + self.tw.send_event(event) - def draw_pixbuf(self, pixbuf, a, b, x, y, w, h, path): + def draw_pixbuf(self, pixbuf, a, b, x, y, w, h, path, share=True): """ Draw a pixbuf """ w *= self.tw.coord_scale h *= self.tw.coord_scale @@ -492,20 +533,40 @@ class TurtleGraphics: if self.tw.running_sugar: # In Sugar, we need to embed the images inside the SVG self.tw.svg_string += self.svg.image(x - self.width / 2, - y, w, h, path, image_to_base64(pixbuf, self.tw.activity)) + y, w, h, path, image_to_base64(pixbuf, + get_path(self.tw.activity, 'instance'))) else: + # Outside of Sugar, we save a path self.tw.svg_string += self.svg.image(x - self.width / 2, y, w, h, path) - - def draw_text(self, label, x, y, size, w): + if self.tw.sharing() and share: + if self.tw.running_sugar: + tmp_path = get_path(self.tw.activity, 'instance') + else: + tmp_path = '/tmp' + data = image_to_base64(pixbuf, tmp_path) + height = pixbuf.get_height() + width = pixbuf.get_width() + x, y = self.screen_to_turtle_coordinates(x, y) + event = "P|%s" % (data_to_string([self._get_my_nick(), + [round_int(a), round_int(b), + round_int(x), round_int(y), + round_int(w), round_int(h), + round_int(width), + round_int(height), + data]])) + self.tw.send_event(event) + + def draw_text(self, label, x, y, size, w, share=True): """ Draw text """ w *= self.tw.coord_scale - self.gc.set_foreground(self.tw.textcolor) + self.gc.set_foreground(self.fgcolor) fd = pango.FontDescription('Sans') try: fd.set_size(int(size * self.tw.coord_scale) * pango.SCALE) except TypeError, ValueError: - _logger.debug("bad value sent to %s" % (__name__)) + debug_output("bad value sent to %s" % (__name__), + self.tw.running_sugar) return if self.tw.interactive_mode: if type(label) == str or type(label) == unicode: @@ -529,32 +590,57 @@ class TurtleGraphics: context.move_to(x, y + h) context.show_text(message) - if self.tw.saving_svg and self.pendown: - self.tw.svg_string += self.svg.text(x - self.width / 2, + if self.tw.saving_svg: # and self.pendown: + self.tw.svg_string += self.svg.text(x, # - self.width / 2, y + size, size, w, label) + if self.tw.sharing() and share: + event = "W|%s" % (data_to_string([self._get_my_nick(), + [label, round_int(x), + round_int(y), round_int(size), + round_int(w)]])) + self.tw.send_event(event) + + def turtle_to_screen_coordinates(self, x, y): + """ The origin of turtle coordinates is the center of the screen """ + return self.width / 2 + x, self.invert_y_coordinate(y) + + def screen_to_turtle_coordinates(self, x, y): + """ The origin of the screen coordinates is the upper left corner """ + return x - self.width / 2, self.invert_y_coordinate(y) + + def invert_y_coordinate(self, y): + """ Positive y goes up in turtle coordinates, down in sceeen + coordinates """ + return self.height / 2 - y def draw_line(self, x1, y1, x2, y2): """ Draw a line """ - x1, y1 = self.width / 2 + int(x1), self.height / 2 - int(y1) - x2, y2 = self.width / 2 + int(x2), self.height / 2 - int(y2) + x1, y1 = self.turtle_to_screen_coordinates(x1, y1) + x2, y2 = self.turtle_to_screen_coordinates(x2, y2) if x1 < x2: - minx, maxx = x1, x2 + minx, maxx = int(x1), int(x2) else: - minx, maxx = x2, x1 + minx, maxx = int(x2), int(x1) if y1 < y2: - miny, maxy = y1, y2 + miny, maxy = int(y1), int(y2) else: - miny, maxy = y2, y1 + miny, maxy = int(y2), int(y1) w, h = maxx - minx, maxy - miny - self.canvas.images[0].draw_line(self.gc, x1, y1, x2, y2) + self.canvas.images[0].draw_line(self.gc, int(x1), int(y1), int(x2), + int(y2)) if self.fill and self.poly_points == []: - self.poly_points.append((x1, y1)) + self.poly_points.append((int(x1), int(y1))) if self.fill: - self.poly_points.append((x2, y2)) - self.invalt(minx - self.pensize * self.tw.coord_scale / 2 - 3, - miny - self.pensize * self.tw.coord_scale / 2 - 3, + self.poly_points.append((int(x2), int(y2))) + self.invalt(minx - int(self.pensize * self.tw.coord_scale / 2) - 3, + miny - int(self.pensize * self.tw.coord_scale / 2) - 3, w + self.pensize * self.tw.coord_scale + 6, h + self.pensize * self.tw.coord_scale + 6) + if self.tw.saving_svg and self.pendown: + self.tw.svg_string += self.svg.new_path(x1, y1) + self.tw.svg_string += self.svg.line_to(x2, y2) + self.tw.svg_string += "\"\n" + self.tw.svg_string += self.svg.style() def turn_turtle(self): """ Change the orientation of the turtle """ @@ -562,8 +648,7 @@ class TurtleGraphics: def move_turtle(self): """ Move the turtle """ - x, y = self.width / 2 + int(self.xcor), \ - self.height / 2 - int(self.ycor) + x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor) self.tw.active_turtle.move( (int(self.cx + x - self.tw.active_turtle.spr.rect.width / 2), int(self.cy + y - self.tw.active_turtle.spr.rect.height / 2))) @@ -611,9 +696,9 @@ class TurtleGraphics: def get_pixel(self): """ Read the pixel at x, y """ if self.tw.interactive_mode: - return self.canvas.get_pixel( - (self.width / 2 + int(self.xcor), - self.height / 2 - int(self.ycor)), 0, self.tw.color_mode) + x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor) + return self.canvas.get_pixel((int(x), int(y)), 0, + self.tw.color_mode) else: return(-1, -1, -1, -1) @@ -625,13 +710,16 @@ class TurtleGraphics: self.seth(0, False) self.setxy(0, 0, False, pendown=False) self.tw.active_turtle.set_pen_state(True) - self.tw.active_turtle = self.tw.turtles.get_turtle(k, False) + elif colors is not None: + self.tw.active_turtle = self.tw.turtles.get_turtle(k, False) + self.tw.active_turtle.set_turtle_colors(colors) + else: + self.tw.active_turtle = self.tw.turtles.get_turtle(k, False) self.tw.active_turtle.show() tx, ty = self.tw.active_turtle.get_xy() - self.xcor = -self.width / 2 + tx + \ - self.tw.active_turtle.spr.rect.width / 2 - self.ycor = self.height / 2 - ty - \ - self.tw.active_turtle.spr.rect.height / 2 + self.xcor, self.ycor = self.screen_to_turtle_coordinates(tx, ty) + self.xcor += self.tw.active_turtle.spr.rect.width / 2 + self.ycor -= self.tw.active_turtle.spr.rect.height / 2 self.heading = self.tw.active_turtle.get_heading() self.setcolor(self.tw.active_turtle.get_color(), False) self.setgray(self.tw.active_turtle.get_gray(), False) @@ -651,11 +739,3 @@ class TurtleGraphics: def _get_my_nick(self): return self.tw.nick - - def _send_event(self, entry, share): - if not share: - return - - if self.tw.sharing(): - print "Sending: %s" % entry - self.tw.send_event(entry) diff --git a/TurtleArt/tacollaboration.py b/TurtleArt/tacollaboration.py index 52164e0..e1534a4 100644 --- a/TurtleArt/tacollaboration.py +++ b/TurtleArt/tacollaboration.py @@ -1,9 +1,34 @@ +#Copyright (c) 2011, Walter Bender +#Copyright (c) 2011 Collabora Ltd. + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. from dbus.service import signal from dbus.gobject_service import ExportedGObject -import logging import telepathy -from TurtleArt.tautils import data_to_string, data_from_string + +import gtk +import base64 + +from TurtleArt.tautils import data_to_string, data_from_string, get_path, \ + base64_to_image, debug_output, error_output +from TurtleArt.taconstants import DEFAULT_TURTLE_COLORS try: from sugar import profile @@ -17,43 +42,63 @@ except: SERVICE = 'org.laptop.TurtleArtActivity' IFACE = SERVICE PATH = '/org/laptop/TurtleArtActivity' -_logger = logging.getLogger('turtleart-activity') + class Collaboration(): def __init__(self, tw, activity): """ A simplistic sharing model: the sharer is the master """ self._tw = tw self._tw.send_event = self.send_event + self._tw.remote_turtle_dictionary = {} self._activity = activity + self._setup_dispatch_table() def setup(self): # TODO: hand off role of master is sharer leaves self.pservice = presenceservice.get_instance() - self.initiating = None # sharing (True) or joining (False) + self.initiating = None # sharing (True) or joining (False) # Add my buddy object to the list owner = self.pservice.get_owner() self.owner = owner self._tw.buddies.append(self.owner) - self._share = "" - + self._share = '' self._activity.connect('shared', self._shared_cb) self._activity.connect('joined', self._joined_cb) + def _setup_dispatch_table(self): + self._processing_methods = { + 't': self._turtle_request, + 'T': self._receive_turtle_dict, + 'f': self._move_forward, + 'a': self._move_in_arc, + 'r': self._rotate_turtle, + 'x': self._setxy, + 'W': self._draw_text, + 'c': self._set_pen_color, + 'g': self._set_pen_gray_level, + 's': self._set_pen_shade, + 'w': self._set_pen_width, + 'p': self._set_pen_state, + 'F': self._fill_polygon, + 'P': self._draw_pixbuf + } + def _shared_cb(self, activity): self._shared_activity = self._activity._shared_activity if self._shared_activity is None: - _logger.error("Failed to share or join activity ... \ - _shared_activity is null in _shared_cb()") + debug_output('Failed to share or join activity ... \ + _shared_activity is null in _shared_cb()', + self._tw.running_sugar) return self._tw.set_sharing(True) self.initiating = True self.waiting_for_turtles = False - self.turtle_dictionary = self._get_dictionary() - - _logger.debug('I am sharing...') + self._tw.remote_turtle_dictionary = self._get_dictionary() + + debug_output('I am sharing...', self._tw.running_sugar) self.conn = self._shared_activity.telepathy_conn self.tubes_chan = self._shared_activity.telepathy_tubes_chan @@ -62,7 +107,8 @@ class Collaboration(): self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal( 'NewTube', self._new_tube_cb) - _logger.debug('This is my activity: making a tube...') + debug_output('This is my activity: making a tube...', + self._tw.running_sugar) id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( SERVICE, {}) @@ -70,8 +116,9 @@ class Collaboration(): def _joined_cb(self, activity): self._shared_activity = self._activity._shared_activity if self._shared_activity is None: - _logger.error("Failed to share or join activity ... \ - _shared_activity is null in _shared_cb()") + debug_output('Failed to share or join activity ... \ + _shared_activity is null in _shared_cb()', + self._tw.running_sugar) return self._tw.set_sharing(True) @@ -85,7 +132,8 @@ class Collaboration(): self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal( 'NewTube', self._new_tube_cb) - _logger.debug('I am joining an activity: waiting for a tube...') + debug_output('I am joining an activity: waiting for a tube...', + self._tw.running_sugar) self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( reply_handler=self._list_tubes_reply_cb, error_handler=self._list_tubes_error_cb) @@ -98,13 +146,13 @@ class Collaboration(): self._new_tube_cb(*tube_info) def _list_tubes_error_cb(self, e): - _logger.error('ListTubes() failed: %s', e) + error_output('ListTubes() failed: %s' % (e), self._tw.running_sugar) def _new_tube_cb(self, id, initiator, type, service, params, state): """ Create a new tube. """ - _logger.debug('New tube: ID=%d initator=%d type=%d service=%s ' - 'params=%r state=%d', id, initiator, type, service, - params, state) + debug_output('New tube: ID=%d initator=%d type=%d service=%s \ + params=%r state=%d' % (id, initiator, type, service, + params, state), self._tw.running_sugar) if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE): if state == telepathy.TUBE_STATE_LOCAL_PENDING: @@ -120,106 +168,35 @@ class Collaboration(): self.event_received_cb) # Now that we have the tube, we can ask for the turtle dictionary. - if self.waiting_for_turtles: - _logger.debug("Sending a request for the turtle dictionary") - # we need to send our own nick and colors + if self.waiting_for_turtles: # A joiner must wait for turtles. + debug_output('Sending a request for the turtle dictionary', + self._tw.running_sugar) + # We need to send our own nick, colors, and turtle position colors = self._get_colors() - event = "t|" + data_to_string([self._get_nick(), colors]) - _logger.debug(event) + event = 't|' + data_to_string([self._get_nick(), colors]) + debug_output(event, self._tw.running_sugar) self.send_event(event) - def event_received_cb(self, text): + def event_received_cb(self, event_message): """ Events are sent as a tuple, nick|cmd, where nick is a turle name and cmd is a turtle event. Everyone gets the turtle dictionary from the sharer and watches for 't' events, which indicate that a new turtle has joined. """ - if len(text) == 0: + if len(event_message) == 0: return - # Save active Turtle + + # Save active Turtle save_active_turtle = self._tw.active_turtle - e = text.split("|", 2) - text = e[1] - if e[0] == 't': # request for turtle dictionary - if text > 0: - [nick, colors] = data_from_string(text) - if nick != self._tw.nick: - # There may not be a turtle dictionary. - if hasattr(self, "turtle_dictionary"): - self.turtle_dictionary[nick] = colors - else: - self.turtle_dictionary = {nick: colors} - # Add new turtle for the joiner. - self._tw.canvas.set_turtle(nick, colors) - # Sharer should send turtle dictionary. - if self.initiating: - text = data_to_string(self.turtle_dictionary) - self.send_event("T|" + text) - elif e[0] == 'T': # Receiving the turtle dictionary. - if self.waiting_for_turtles: - if len(text) > 0: - self.turtle_dictionary = data_from_string(text) - for nick in self.turtle_dictionary: - if nick != self._tw.nick: - colors = self.turtle_dictionary[nick] - # add new turtle for the joiner - self._tw.canvas.set_turtle(nick, colors) - self.waiting_for_turtles = False - elif e[0] == 'f': # move a turtle forward - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.forward(x, False) - elif e[0] == 'a': # move a turtle in an arc - if len(text) > 0: - [nick, [a, r]] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.arc(a, r, False) - elif e[0] == 'r': # rotate turtle - if len(text) > 0: - [nick, h] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.seth(h, False) - elif e[0] == 'x': # set turtle xy position - if len(text) > 0: - [nick, [x, y]] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setxy(x, y, False) - elif e[0] == 'c': # set turtle pen color - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setcolor(x, False) - elif e[0] == 'g': # set turtle pen gray level - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setgray(x, False) - elif e[0] == 's': # set turtle pen shade - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setshade(x, False) - elif e[0] == 'w': # set turtle pen width - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setpensize(x, False) - elif e[0] == 'p': # set turtle pen state - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setpen(x, False) + + try: + command, payload = event_message.split('|', 2) + self._processing_methods[command](payload) + except ValueError: + debug_output('Could not split event message.', + self._tw.running_sugar) + # Restore active Turtle self._tw.canvas.set_turtle(self._tw.turtles.get_turtle_key( save_active_turtle)) @@ -229,19 +206,193 @@ class Collaboration(): if hasattr(self, 'chattube') and self.chattube is not None: self.chattube.SendText(entry) + def _turtle_request(self, payload): + ''' incoming turtle from a joiner ''' + if payload > 0: + [nick, colors] = data_from_string(payload) + if nick != self._tw.nick: # It is not me. + # There may not be a turtle dictionary. + if hasattr(self._tw, 'remote_turtle_dictionary'): + # Make sure it is not a "rejoin". + if not nick in self._tw.remote_turtle_dictionary: + # Add new turtle for the joiner. + self._tw.canvas.set_turtle(nick, colors) + self._tw.label_remote_turtle(nick, colors) + self._tw.remote_turtle_dictionary[nick] = colors + else: + self._tw.remote_turtle_dictionary = self._get_dictionary() + # Add new turtle for the joiner. + self._tw.canvas.set_turtle(nick, colors) + self._tw.label_remote_turtle(nick, colors) + + # Sharer should send the updated remote turtle dictionary to everyone. + if self.initiating: + if not self._tw.nick in self._tw.remote_turtle_dictionary: + self._tw.remote_turtle_dictionary[self._tw.nick] = \ + self._get_colors() + event_payload = data_to_string(self._tw.remote_turtle_dictionary) + self.send_event('T|' + event_payload) + self._send_my_xy() # And the sender should report her xy position. + + def _receive_turtle_dict(self, payload): + ''' Any time there is a new joiner, an updated turtle dictionary is + circulated. Everyone must report their turtle positions so that we + are in sync. ''' + if self.waiting_for_turtles: + if len(payload) > 0: + # Grab the new remote turtles dictionary. + remote_turtle_dictionary = data_from_string(payload) + # Add see what is new. + for nick in remote_turtle_dictionary: + if nick == self._tw.nick: + debug_output('skipping my nick %s' \ + % (nick), self._tw.running_sugar) + elif nick != self._tw.remote_turtle_dictionary: + # Add new the turtle. + colors = remote_turtle_dictionary[nick] + self._tw.remote_turtle_dictionary[nick] = colors + self._tw.canvas.set_turtle(nick, colors) + # Label the remote turtle. + self._tw.label_remote_turtle(nick, colors) + debug_output('adding %s to remote turtle dictionary' \ + % (nick), self._tw.running_sugar) + else: + debug_output('%s already in remote turtle dictionary' \ + % (nick), self._tw.running_sugar) + self.waiting_for_turtles = False + self._send_my_xy() + + def _send_my_xy(self): + ''' Set xy location so joiner can sync turtle positions. ''' + self._tw.canvas.set_turtle(self._get_nick()) + if self._tw.canvas.pendown: + self.send_event('p|%s' % (data_to_string([self._get_nick(), + False]))) + put_pen_back_down = True + else: + put_pen_back_down = False + self.send_event('x|%s' % (data_to_string([self._get_nick(), + [int(self._tw.canvas.xcor), int(self._tw.canvas.ycor)]]))) + if put_pen_back_down: + self.send_event('p|%s' % (data_to_string([self._get_nick(), + True]))) + self.send_event('r|%s' % (data_to_string([self._get_nick(), + int(self._tw.canvas.heading)]))) + + def _draw_pixbuf(self, payload): + if len(payload) > 0: + [nick, [a, b, x, y, w, h, width, height, data]] =\ + data_from_string(payload) + if nick != self._tw.nick: + if self._tw.running_sugar: + tmp_path = get_path(self._tw.activity, 'instance') + else: + tmp_path = '/tmp' + file_name = base64_to_image(data, tmp_path) + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(file_name, + width, height) + x, y = self._tw.canvas.turtle_to_screen_coordinates(x, y) + self._tw.canvas.draw_pixbuf(pixbuf, a, b, x, y, w, h, + file_name, False) + + def _move_forward(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.forward(x, False) + + def _move_in_arc(self, payload): + if len(payload) > 0: + [nick, [a, r]] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.arc(a, r, False) + + def _rotate_turtle(self, payload): + if len(payload) > 0: + [nick, h] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.seth(h, False) + + def _setxy(self, payload): + if len(payload) > 0: + [nick, [x, y]] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setxy(x, y, False) + + def _draw_text(self, payload): + if len(payload) > 0: + [nick, [label, x, y, size, w]] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.draw_text(label, x, y, size, w, False) + + def _set_pen_color(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setcolor(x, False) + + def _set_pen_gray_level(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setgray(x, False) + + def _set_pen_shade(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setshade(x, False) + + def _set_pen_width(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setpensize(x, False) + + def _set_pen_state(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setpen(x, False) + + def _fill_polygon(self, payload): + # Check to make sure that the poly_point array is passed properly + if len(payload) > 0: + [nick, poly_points] = data_from_string(payload) + shared_poly_points = [] + for i in range(len(poly_points)): + shared_poly_points.append(( + self._tw.canvas.turtle_to_screen_coordinates( + poly_points[i][0], poly_points[i][1]))) + self._tw.canvas.fill_polygon(shared_poly_points) + def _get_dictionary(self): - d = { self._get_nick(): self._get_colors()} - return d + return {self._get_nick(): self._get_colors()} def _get_nick(self): return self._tw.nick def _get_colors(self): - if profile: - colors = profile.get_color().to_string() + colors = None + if self._tw.running_sugar: + if profile.get_color() is not None: + colors = profile.get_color().to_string() else: colors = self._activity.get_colors() - return colors + if colors is None: + colors = '%s,%s' % (DEFAULT_TURTLE_COLORS[0], + DEFAULT_TURTLE_COLORS[1]) + return colors.split(',') + class ChatTube(ExportedGObject): @@ -249,7 +400,7 @@ class ChatTube(ExportedGObject): """Class for setting up tube for sharing.""" super(ChatTube, self).__init__(tube, PATH) self.tube = tube - self.is_initiator = is_initiator # Are we sharing or joining activity? + self.is_initiator = is_initiator # Are we sharing or joining activity? self.stack_received_cb = stack_received_cb self.stack = '' diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py index 77ebefb..30920d6 100644 --- a/TurtleArt/taconstants.py +++ b/TurtleArt/taconstants.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -#Copyright (c) 2010, Walter Bender +#Copyright (c) 2010-11 Walter Bender #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal @@ -19,79 +19,6 @@ #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. -""" -This file contains the constants that by-in-large determine the -behavior of Turtle Art. Notably, the block palettes are defined -below. If you want to add a new block to Turtle Art, it is generally a -matter of modifying some tables below and then adding the primitive to -talogo.py. For example, if we want to add a new turtle command, -'uturn', we'd make the following changes: - -(1) We'd add 'uturn' to the PALETTES list of lists: - -PALETTES = [['forward', 'back', 'clean', 'left', 'right', 'uturn', 'show', - 'seth', 'setxy', 'heading', 'xcor', 'ycor', 'setscale', - 'arc', 'scale'], - ['penup','pendown', 'setpensize', 'fillscreen', 'pensize',... - -(2) Then we'd add it to one of the block-style definitions. Since it takes -no arguments, we'd add it here: - -BASIC_STYLE = ['clean', 'penup', 'pendown', 'stack1', 'stack2', 'vspace', - 'hideblocks', 'showblocks', 'clearheap', 'printheap', 'kbinput', 'uturn'] - -(3) Then we give it a name (Note the syntax _('string to be -translated') used by the language-internationalization system; also -note that the name is an array, as some blocks contain multiple -strings.): - -BLOCK_NAMES = { -... - 'uturn':[_('u-turn')], -... - } - -(4) and a help-menu entry: - -HELP_STRINGS = { -... - 'uturn':_('change the heading of the turtle 180 degrees'), -... - } - -(5) Next, we need to define it as a primitive for the Logo command -parser (generally just the same name): - -PRIMITIVES = { -... - 'uturn':'uturn', -... - } - -(6) Since there are no default arguments, we don't need to do anything -else here. But we do need to define the actual function in talogo.py - -DEFPRIM = { -... - 'uturn':[0, lambda self: self.tw.canvas.seth(self.tw.canvas.heading+180)], -... - } - -That's it. When you next run Turtle Art, you will have a 'uturn' block -on the Turtle Palette. - -Adding a new palette is simply a matter of: (1) adding an additional -entry to PALETTE_NAMES; (2) new list of blocks to PALETTES; and (3) an -additional entry in COLORS. However you will have to: (4) create icons -for the palette-selector buttons. These are kept in the icons -subdirectory. You need two icons: yourpalettenameoff.svg and -yourpalettenameon.svg, where yourpalettename is the same string as the -entry you added to the PALETTE_NAMES list. Note that the icons should -be the same size (55x55) as the others. This is the default icon size -for Sugar toolbars. - -""" - from gettext import gettext as _ # @@ -108,52 +35,7 @@ TAB_LAYER = 710 STATUS_LAYER = 900 TOP_LAYER = 1000 -# -# Block-palette categories -# - -PALETTE_NAMES = ['turtle', 'pen', 'colors', 'numbers', 'flow', 'blocks', - 'extras', 'sensor', 'portfolio', 'trash'] - -PALETTES = [['clean', 'forward', 'back', 'show', 'left', 'right', - 'seth', 'setxy2', 'heading', 'xcor', 'ycor', 'setscale', - 'arc', 'scale', 'leftpos', 'toppos', 'rightpos', - 'bottompos'], - ['penup', 'pendown', 'setpensize', 'fillscreen', 'pensize', - 'setcolor', 'setshade', 'setgray', 'color', 'shade', - 'gray', 'startfill', 'stopfill'], - ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple', - 'white', 'black'], - ['plus2', 'minus2', 'product2', - 'division2', 'identity2', 'remainder2', 'sqrt', 'random', - 'number', 'greater2', 'less2', 'equal2', 'not', 'and2', 'or2'], - ['wait', 'forever', 'repeat', 'if', 'ifelse', 'while', 'until', - 'hspace', 'vspace', 'stopstack'], - ['start', 'hat1', 'stack1', 'hat', 'hat2', 'stack2', 'stack', - 'storeinbox1', 'storeinbox2', 'string', 'box1', 'box2', 'box', - 'storein'], - ['push', 'printheap', 'clearheap', 'pop', 'comment', 'print', - 'myfunc1arg', 'userdefined', - 'cartesian', 'width', 'height', 'polar', 'addturtle', 'reskin', - 'sandwichtop_no_label', 'sandwichbottom'], - ['kbinput', 'keyboard', 'readpixel', 'see', - 'sound', 'volume', 'pitch'], - ['journal', 'audio', 'video', 'description', 'hideblocks', - 'showblocks', 'fullscreen', 'savepix', 'savesvg', 'mediawait', - 'picturelist', 'picture1x1a', 'picture1x1', 'picture2x2', - 'picture2x1', 'picture1x2'], - ['empty', 'restoreall']] - -# -# Block-style attributes -# - -COLORS = [["#00FF00", "#00A000"], ["#00FFFF", "#00A0A0"], - ["#00FFFF", "#00A0A0"], ["#FF00FF", "#A000A0"], - ["#FFC000", "#A08000"], ["#FFFF00", "#A0A000"], - ["#FF0000", "#A00000"], ["#FF0000", "#A00000"], - ["#0000FF", "#0000A0"], ["#FFFF00", "#A0A000"]] - +# Special-case some block colors BOX_COLORS = {'red': ["#FF0000", "#A00000"], 'orange': ["#FFD000", "#AA8000"], 'yellow': ["#FFFF00", "#A0A000"], @@ -173,9 +55,10 @@ SELECTOR_WIDTH = 55 ICON_SIZE = 55 GRADIENT_COLOR = "#FFFFFF" STANDARD_STROKE_WIDTH = 1.0 -BLOCK_SCALE = 2.0 +BLOCK_SCALE = [0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0] PALETTE_SCALE = 1.5 DEFAULT_TURTLE = 'Yertle' +DEFAULT_TURTLE_COLORS = ['#008000', '#00A000'] HORIZONTAL_PALETTE = 0 VERTICAL_PALETTE = 1 BLACK = -9999 @@ -190,78 +73,23 @@ DEFAULT_SCALE = 33 XO1 = 'xo1' XO15 = 'xo1.5' UNKNOWN = 'unknown' -SENSOR_AC_NO_BIAS = 'external' -SENSOR_AC_BIAS = 'sound' -SENSOR_DC_NO_BIAS = 'voltage' -SENSOR_DC_BIAS = 'resistance' -# -# Block-style definitions -# -BASIC_STYLE_HEAD = ['start', 'hat1', 'hat2', 'restore', 'restoreall'] -BASIC_STYLE_HEAD_1ARG = ['hat'] -BASIC_STYLE_TAIL = ['stopstack', 'empty'] -BASIC_STYLE = [] -BASIC_STYLE_EXTENDED_VERTICAL = ['clean', 'penup', 'pendown', 'stack1', - 'stack2', 'hideblocks', 'showblocks', 'clearheap', 'printheap', 'kbinput', - 'fullscreen', 'cartesian', 'polar', 'startfill', 'mediawait', - 'stopfill', 'readpixel', 'readcamera', 'vspace'] -INVISIBLE = ['sandwichcollapsed'] -BASIC_STYLE_EXTENDED = ['picturelist', 'picture1x1', 'picture2x2', - 'picture2x1', 'picture1x2', 'picture1x1a'] -BASIC_STYLE_1ARG = ['forward', 'back', 'left', 'right', 'seth', 'show', 'image', - 'setscale', 'setpensize', 'setcolor', 'setshade', 'print', 'showaligned', - 'settextsize', 'settextcolor', 'print', 'wait', 'storeinbox1', 'savepix', - 'storeinbox2', 'wait', 'stack', 'push', 'nop', 'addturtle', 'comment', - 'savesvg', 'setgray', 'skin', 'reskin'] -BASIC_STYLE_VAR_ARG = ['userdefined', 'userdefined2args', 'userdefined3args'] -BULLET_STYLE = ['templatelist', 'list'] -BASIC_STYLE_2ARG = ['arc', 'setxy', 'setxy2', 'fillscreen', 'storein', 'write'] -BOX_STYLE = ['number', 'xcor', 'ycor', 'heading', 'pensize', 'color', 'shade', - 'textcolor', 'textsize', 'box1', 'box2', 'string', 'leftpos', 'scale', - 'toppos', 'rightpos', 'bottompos', 'width', 'height', 'pop', 'keyboard', - 'red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple', 'white', - 'black', 'titlex', 'titley', 'leftx', 'topy', 'rightx', 'bottomy', - 'sound', 'volume', 'pitch', 'voltage', 'resistance', 'gray', 'see', 'rfid', - 'luminance'] -BOX_STYLE_MEDIA = ['description', 'audio', 'journal', 'video', 'camera'] -NUMBER_STYLE = ['plus2', 'product2', 'myfunc'] -NUMBER_STYLE_VAR_ARG = ['myfunc1arg', 'myfunc2arg', 'myfunc3arg'] -NUMBER_STYLE_BLOCK = ['random'] -NUMBER_STYLE_PORCH = ['minus2', 'division2', 'remainder2'] -NUMBER_STYLE_1ARG = ['sqrt', 'identity2'] -NUMBER_STYLE_1STRARG = ['box'] -COMPARE_STYLE = ['greater2', 'less2', 'equal2'] -BOOLEAN_STYLE = ['and2', 'or2'] -NOT_STYLE = ['not'] -FLOW_STYLE = ['forever'] -FLOW_STYLE_TAIL = ['hspace'] -FLOW_STYLE_1ARG = ['repeat'] -FLOW_STYLE_BOOLEAN = ['if', 'while', 'until'] -FLOW_STYLE_WHILE = ['while2'] -FLOW_STYLE_ELSE = ['ifelse'] -COLLAPSIBLE_TOP = ['sandwichtop'] -COLLAPSIBLE_TOP_NO_ARM = ['sandwichtop_no_arm'] -COLLAPSIBLE_TOP_NO_LABEL = ['sandwichtop_no_label'] -COLLAPSIBLE_TOP_NO_ARM_NO_LABEL = ['sandwichtop_no_arm_no_label'] -COLLAPSIBLE_BOTTOM = ['sandwichbottom'] - -# Depreciated block styles -PORTFOLIO_STYLE_2x2 = ['template2x2'] -PORTFOLIO_STYLE_1x1 = ['template1x1', 'template1x1a'] -PORTFOLIO_STYLE_2x1 = ['template2x1'] -PORTFOLIO_STYLE_1x2 = ['template1x2'] +CONSTANTS = {'leftpos': None, 'toppos': None, 'rightpos': None, + 'bottompos': None, 'width': None, 'height': None, 'red': 0, + 'orange': 10, 'yellow': 20, 'green': 40, 'cyan': 50, 'blue': 70, + 'purple': 90, 'titlex': None, 'titley': None, 'leftx': None, + 'topy': None, 'rightx': None, 'bottomy': None} # # Blocks that are expandable # -EXPANDABLE = ['vspace', 'hspace', 'identity2'] +EXPANDABLE_STYLE = ['boolean-style', 'compare-porch-style', 'compare-style', + 'number-style-porch', 'number-style', 'basic-style-2arg', + 'number-style-block'] -EXPANDABLE_BLOCKS = ['plus2', 'minus2', 'division2', 'remainder2', 'product2', - 'random', 'equal2', 'greater2', 'less2', 'and2', 'or2', - 'arc', 'setxy', 'setxy2', 'fillscreen', 'storein', 'write'] +EXPANDABLE = ['vspace', 'hspace', 'identity2'] -EXPANDABLE_ARGS = ['templatelist', 'list', 'myfunc1arg', 'myfunc2arg', +EXPANDABLE_ARGS = ['list', 'myfunc1arg', 'myfunc2arg', 'myfunc3arg', 'userdefined', 'userdefined2args', 'userdefined3args'] # @@ -270,418 +98,18 @@ EXPANDABLE_ARGS = ['templatelist', 'list', 'myfunc1arg', 'myfunc2arg', COLLAPSIBLE = ['sandwichbottom', 'sandwichcollapsed'] # -# Depreciated block styles that need dock adjustments +# Deprecated block styles that need dock adjustments # OLD_DOCK = ['and', 'or', 'plus', 'minus', 'division', 'product', 'remainder'] # -# Blocks that contain media -# -CONTENT_BLOCKS = ['number', 'string', 'description', 'audio', 'video', - 'journal', 'camera'] - -# # These blocks get a special skin # BLOCKS_WITH_SKIN = ['journal', 'audio', 'description', 'nop', 'userdefined', - 'video', 'userdefined2args', 'userdefined3args'] + 'video', 'userdefined2args', 'userdefined3args', 'camera'] PYTHON_SKIN = ['nop', 'userdefined', 'userdefined2args', 'userdefined3args'] -# -# These blocks hold constants -# -CONSTANTS = {'leftpos':None, 'toppos':None, 'rightpos':None, 'bottompos':None, - 'width':None, 'height':None, 'red':0, 'orange':10, 'yellow':20, - 'green':40, 'cyan':50, 'blue':70, 'purple':90, 'titlex':None, - 'titley':None, 'leftx':None, 'topy':None, 'rightx':None, - 'bottomy':None} - -# -# Block-name dictionary used for labels -# -BLOCK_NAMES = { - 'addturtle': [_('turtle')], - 'and2': [_('and')], - 'arc': [_('arc'), _('angle'), _('radius')], - 'audio': [' '], - 'back': [_('back')], - 'black': [_('black')], - 'blocks': [_('blocks')], - 'blue': [_('blue')], - 'bottompos': [_('bottom')], - 'bottomy': [_('picture bottom')], - 'box': [_('box')], - 'box1': [_('box 1')], - 'box2': [_('box 2')], - 'camera': [' '], - 'cartesian': [_('Cartesian')], - 'clean': [_(' clean ')], - 'clearheap': [_('empty heap')], - 'color': [_('color')], - 'colors': [_('colors')], - 'comment': [_('comment')], - 'cyan': [_('cyan')], - 'decription': [' '], - 'division2': ['/'], - 'empty': [_('empty trash')], - 'equal2': ['='], - 'extras': [_('extras')], - 'fillscreen': [_('fill screen'), _('color'), _('shade')], - 'flow': [_('flow')], - 'forever': [_('forever')], - 'forward': [_('forward')], - 'fullscreen': [_('full screen')], - 'gray': [_('gray')], - 'greater2': [">"], - 'green': [_('green')], - 'hat': [_('action')], - 'hat1': [_('action 1')], - 'hat2': [_('action 2')], - 'heading': [_('heading')], - 'height': [_('height')], - 'hideblocks': [_('hide blocks')], - 'hspace': [' '], - 'identity2': ['←'], - 'if': [' ', _('if'), _('then')], - 'ifelse': [' ', _('if'), _('then else')], - 'image': [_('show')], - 'journal': [' '], - 'kbinput': [_('query keyboard')], - 'keyboard': [_('keyboard')], - 'left': [_('left')], - 'leftpos': [_('left')], - 'leftx': [_('picture left')], - 'less2': ['<'], - 'list': ['list'], - 'luminance': [_('brightness')], - 'mediawait': [_('media wait')], - 'minus2': ['–'], - 'myfunc': [_('Python'), 'f(x)', 'x'], - 'myfunc1arg': [_('Python'), 'f(x)', 'x'], - 'myfunc2arg': [_('Python'), 'f(x,y)', ' '], - 'myfunc3arg': [_('Python'), 'f(x,y,z)', ' '], - 'nop': [_(' ')], - 'not': [_('not')], - 'number': ['100'], - 'numbers': [_('numbers')], - 'orange': [_('orange')], - 'or2': [_('or')], - 'pen': [_('pen')], - 'pendown': [_('pen down')], - 'pensize': [_('pen size')], - 'penup': [_('pen up')], - 'picturelist': [' '], - 'picture1x1': [' '], - 'picture1x1a': [' '], - 'picture2x2': [' '], - 'picture2x1': [' '], - 'picture1x2': [' '], - 'pitch': [_('pitch')], - 'plus2': [' + '], - 'polar': [_('polar')], - 'pop': [_('pop')], - 'portfolio': [_('portfolio')], - 'printheap': [_('show heap')], - 'print': [_('print')], - 'product2': ['×'], - 'purple': [_('purple')], - 'push': [_('push')], - 'random': [_('random'), _('min'), _('max')], - 'readcamera': [_('read camera')], - 'readpixel': [_('read pixel')], - 'red': [_('red')], - 'remainder2': [_('mod')], - 'repeat': [' ', _('repeat')], - 'reskin': [_('turtle shell')], - 'resistance': [_('resistance')], - 'restore': [_('restore last')], - 'restoreall': [_('restore all')], - 'rfid': [_('RFID')], - 'right': [_('right')], - 'rightpos': [_('right')], - 'rightx': [_('picture right')], - 'savepix': [_('save picture')], - 'savesvg': [_('save SVG')], - 'sandwichbottom': [' ', ' '], - 'sandwichcollapsed': [' '], - 'sandwichtop': [_('top of stack')], - 'sandwichtop_no_arm': [_('top of stack')], - 'sandwichtop_no_label': [' ', ' '], - 'sandwichtop_no_arm_no_label': [' ', _('click to open')], - 'scale': [_('scale')], - 'see': [_('turtle sees')], - 'sensor': [_('sensors')], - 'setcolor': [_('set color')], - 'setgray': [_('set gray')], - 'seth': [_('set heading')], - 'setpensize': [_('set pen size')], - 'setscale': [_('set scale')], - 'setshade': [_('set shade')], - 'settextcolor': [_('set text color')], - 'settextsize': [_('set text size')], - 'setxy': [_('set xy'), _('x'), _('y')], - 'setxy2': [_('set xy'), _('x'), _('y')], - 'shade': [_('shade')], - 'show': [_('show')], - 'showblocks': [_('show blocks')], - 'showaligned': [_('show aligned')], - 'skin': [_('turtle shell')], - 'sound': [_('sound')], - 'sqrt': ['√'], - 'stack': [_('action')], - 'stack1': [_('action 1')], - 'stack2': [_('action 2')], - 'start': [_('start')], - 'startfill': [_('start fill')], - 'stopfill': [_('end fill')], - 'stopstack': [_('stop action')], - 'storein': [_('store in'), _('box'), _('value')], - 'storeinbox1': [_('store in box 1')], - 'storeinbox2': [_('store in box 2')], - 'string': [_('text')], - 'template1x1': [' '], - 'template1x1a': [' '], - 'template1x2': [' '], - 'template2x1': [' '], - 'template2x2': [' '], - 'templatelist': [' '], - 'textsize': [_('text size')], - 'titlex': [_('title x')], - 'titley': [_('title y')], - 'toppos': [_('top')], - 'topy': [_('picture top')], - 'trash': [_('trash')], - 'turtle': [_('turtle')], - 'until': [_('until')], - 'userdefined': [_(' ')], - 'userdefined2args': [_(' ')], - 'userdefined3args': [_(' ')], - 'video': [' '], - 'voltage': [_('voltage')], - 'volume': [_('volume')], - 'vspace': [' '], - 'wait': [_('wait')], - 'while': [_('while')], - 'while2': [_('while')], - 'white': [_('white')], - 'width': [_('width')], - 'write': [_('write')], - 'xcor': [_('xcor')], - 'ycor': [_('ycor')], - 'yellow': [_('yellow')]} - -# -# Logo primitives -# - -PRIMITIVES = { - 'addturtle': 'turtle', - 'and2': 'and', - 'arc': 'arc', - 'back': 'back', - 'black': 'black', - 'blue': 'blue', - 'bottompos': 'bpos', - 'bottomy': 'boty', - 'box1': 'box1', - 'box2': 'box2', - 'box': 'box', - 'cartesian': 'cartesian', - 'clean': 'clean', - 'clearheap': 'clearheap', - 'color': 'color', - 'comment': 'comment', - 'cyan': 'cyan', - 'division2': 'division', - 'equal2': 'equal?', - 'fillscreen': 'fillscreen', - 'forever': 'forever', - 'forward': 'forward', - 'fullscreen': 'fullscreen', - 'gray': 'gray', - 'greater2': 'greater?', - 'green': 'green', - 'hat': 'nop3', - 'hat1': 'nop1', - 'hat2': 'nop2', - 'heading': 'heading', - 'height': 'vres', - 'hideblocks': 'hideblocks', - 'hspace': 'nop', - 'identity2': 'id', - 'if': 'if', - 'ifelse': 'ifelse', - 'image': 'show', - 'kbinput': 'kbinput', - 'keyboard': 'keyboard', - 'left': 'left', - 'leftpos': 'lpos', - 'leftx': 'leftx', - 'less2': 'less?', - 'list': 'bulletlist', - 'luminance': 'luminance', - 'mediawait': 'mediawait', - 'minus2': 'minus', - 'myfunc': 'myfunction', - 'myfunc1arg': 'myfunction', - 'myfunc2arg': 'myfunction2', - 'myfunc3arg': 'myfunction3', - 'nop': 'userdefined', - 'not': 'not', - 'orange': 'orange', - 'or2': 'or', - 'pendown': 'pendown', - 'pensize': 'pensize', - 'penup': 'penup', - 'pitch': 'pitch', - 'plus2': 'plus', - 'polar': 'polar', - 'pop': 'pop', - 'printheap': 'printheap', - 'print': 'print', - 'product2': 'product', - 'purple': 'purple', - 'push': 'push', - 'random': 'random', - 'red': 'red', - 'readcamera': 'readcamera', - 'readpixel': 'readpixel', - 'remainder2': 'mod', - 'repeat': 'repeat', - 'resistance': 'resistance', - 'rfid': 'rfid', - 'right': 'right', - 'rightpos': 'rpos', - 'rightx': 'rightx', - 'sandwichtop': 'comment', - 'sandwichtop_no_arm': 'comment', - 'sandwichtop_no_label': 'nop', - 'sandwichtop_no_arm_no_label': 'nop', - 'sandwichbottom': 'nop', - 'sandwichcollapsed': 'nop', - 'savepix': 'savepix', - 'savesvg': 'savesvg', - 'see': 'see', - 'scale': 'scale', - 'setcolor': 'setcolor', - 'setgray': 'setgray', - 'seth': 'seth', - 'setpensize': 'setpensize', - 'setscale': 'setscale', - 'setshade': 'setshade', - 'settextsize': 'settextsize', - 'settextcolor': 'settextcolor', - 'setxy': 'setxy', - 'setxy2': 'setxy2', - 'shade': 'shade', - 'show': 'show', - 'showblocks': 'showblocks', - 'showaligned': 'showaligned', - 'skin': 'skin', - 'sound': 'sound', - 'sqrt': 'sqrt', - 'stack': 'stack', - 'stack1': 'stack1', - 'stack2': 'stack2', - 'start': 'start', - 'startfill': 'startfill', - 'stopfill': 'stopfill', - 'stopstack': 'stopstack', - 'storein': 'storeinbox', - 'storeinbox1': 'storeinbox1', - 'storeinbox2': 'storeinbox2', - 'template1x1': 't1x1', - 'template1x1a': 't1x1a', - 'template1x2': 't1x2', - 'template2x1': 't2x1', - 'template2x2': 't2x2', - 'templatelist': 'bullet', - 'textsize': 'textsize', - 'titlex': 'titlex', - 'titley': 'titley', - 'toppos': 'tpos', - 'topy': 'topy', - 'userdefined': 'userdefined', - 'userdefined2args': 'userdefined2', - 'userdefined3args': 'userdefined3', - 'voltage': 'voltage', - 'volume': 'volume', - 'vspace': 'nop', - 'wait': 'wait', - 'while2': 'while', - 'white': 'white', - 'width': 'hres', - 'write': 'write', - 'xcor': 'xcor', - 'ycor': 'ycor', - 'yellow': 'yellow'} - -# -# block default values -# - -DEFAULTS = { - 'addturtle': [1], - 'arc': [90, 100], - 'audio': [None], - 'back': [100], - 'box': [_('my box')], - 'camera': ['CAMERA'], - 'comment': [_('comment')], - 'description': [None], - 'fillscreen': [60, 80], - 'forever': [None, 'vspace'], - 'forward': [100], - 'hat': [_('action')], - 'if': [None, None, 'vspace'], - 'ifelse': [None, 'vspace', None, 'vspace'], - 'journal': [None], - 'left': [90], - 'list': ['∙ ', '∙ '], - 'media': [None], - 'myfunc': ['x', 100], - 'myfunc1arg': ['x', 100], - 'myfunc2arg': ['x+y', 100, 100], - 'myfunc3arg': ['x+y+z', 100, 100, 100], - 'nop': [100], - 'number': [100], - 'random': [0, 100], - 'repeat': [4, None, 'vspace'], - 'right': [90], - 'sandwichtop': [_('label')], - 'sandwichtop_no_arm': [_('label')], - 'savepix': [_('picture name')], - 'savesvg': [_('picture name')], - 'setcolor': [0], - 'setgray': [100], - 'seth': [0], - 'setpensize': [5], - 'setscale': [33], - 'setshade': [50], - 'settextsize': [48], - 'settextcolor': [0], - 'setxy': [0, 0], - 'setxy2': [0, 0], - 'show': [_('text')], - 'showaligned': [_('text')], - 'stack': [_('action')], - 'storeinbox1': [100], - 'storeinbox2': [100], - 'storein': [_('my box'), 100], - 'string': [_('text')], - 'template1x1': [_('Title'), 'None'], - 'template1x1a': [_('Title'), 'None'], - 'template1x2': [_('Title'), 'None', 'None'], - 'template2x1': [_('Title'), 'None', 'None'], - 'template2x2': [_('Title'), 'None', 'None', 'None', 'None'], - 'templatelist': [_('Title'), '∙ '], - 'userdefined': [100], - 'userdefined2args': [100, 100], - 'userdefined3args': [100, 100, 100], - 'video': [None], - 'wait': [1], - 'write': [_('text'), 32]} # # Blocks that can interchange strings and numbers for their arguments @@ -697,6 +125,9 @@ STRING_OR_NUMBER_ARGS = ['plus2', 'equal2', 'less2', 'greater2', 'box', CONTENT_ARGS = ['show', 'showaligned', 'push', 'storein', 'storeinbox1', 'storeinbox2'] +PREFIX_DICTIONARY = {'journal': '#smedia_', 'description': '#sdescr_', + 'audio': '#saudio_', 'video': '#svideo_'} + # # Status blocks # @@ -709,7 +140,7 @@ MEDIA_SHAPES = ['audiooff', 'audioon', 'audiosmall', 'pythonoff', 'pythonon', 'pythonsmall', 'list', '1x1', '1x1a', '2x1', '1x2', '2x2'] -OVERLAY_SHAPES = ['Cartesian', 'Cartesian_labeled', 'polar'] +OVERLAY_SHAPES = ['Cartesian', 'Cartesian_labeled', 'polar', 'metric'] STATUS_SHAPES = ['status', 'info', 'nostack', 'dupstack', 'noinput', 'emptyheap', 'emptybox', 'nomedia', 'nocode', 'overflowerror', @@ -735,7 +166,8 @@ OLD_NAMES = {'product': 'product2', 'storeinbox': 'storein', 'minus': 'minus2', 'template1': 'template1x1', 'template2': 'template2x1', 'template6': 'template1x2', 'template7': 'template2x2', 'template4': 'template1x1a', 'hres': 'width', 'vres': 'height', - 'sandwichtop2': 'sandwichtop'} + 'sandwichtop2': 'sandwichtop', 'image': 'show', + 'container': 'indentity2', 'insertimage': 'show'} # # Define the relative size and postion of media objects @@ -744,7 +176,7 @@ OLD_NAMES = {'product': 'product2', 'storeinbox': 'storein', 'minus': 'minus2', TITLEXY = (0.9375, 0.875) # -# Relative placement of portfolio objects (used by depreciated blocks) +# Relative placement of portfolio objects (used by deprecated blocks) # TEMPLATES = {'t1x1': (0.5, 0.5, 0.0625, 0.125, 1.05, 0), 't2z1': (0.5, 0.5, 0.0625, 0.125, 1.05, 1.05), @@ -755,215 +187,21 @@ TEMPLATES = {'t1x1': (0.5, 0.5, 0.0625, 0.125, 1.05, 0), 'insertimage': (0.333, 0.333)} # -# Names for blocks without names for popup help -# -SPECIAL_NAMES = { - 'audio': _('audio'), - 'camera': _('camera'), - 'division2': _('divide'), - 'equal2': _('equal'), - 'greater2': _('greater than'), - 'hspace': _('horizontal space'), - 'identity2': _('identity'), - 'if': _('if then'), - 'ifelse': _('if then else'), - 'journal': _('journal'), - 'less2': _('less than'), - 'minus2': _('minus'), - 'nop': _('Python code'), - 'number': _('number'), - 'plus2': _('plus'), - 'product2': _('multiply'), - 'sqrt': _('square root'), - 'template1x1': _('presentation 1x1'), - 'template1x1a': _('presentation 1x1'), - 'template1x2': _('presentation 1x2'), - 'template2x1': _('presentation 2x1'), - 'template2x2': _('presentation 2x2'), - 'templatelist': _('presentation bulleted list'), - 'textsize': _('text size'), - 'video': _('video'), - 'vspace': _('vertical space')} - -# -# Help messages -# -HELP_STRINGS = { - 'addturtle': _("chooses which turtle to command"), - 'and2': _("logical AND operator"), - 'arc': _("moves turtle along an arc"), - 'audio': _("Sugar Journal audio object"), - 'back': _("moves turtle backward"), - 'blocks': _("Palette of variable blocks"), - 'bottompos': _("ycor of bottom of screen"), - 'box1': _("Variable 1 (numeric value)"), - 'box2': _("Variable 2 (numeric value)"), - 'box': _("named variable (numeric value)"), - 'camera': _('camera output'), - 'cartesian': _("displays Cartesian coordinates"), - 'clean': _("clears the screen and reset the turtle"), - 'clearheap': _("emptys FILO (first-in-last-out heap)"), - 'color': _("holds current pen color (can be used in place of a number block)"), - 'colors': _("Palette of pen colors"), - 'comment': _("places a comment in your code"), - 'debugoff': _("Debug"), - 'description': _("Sugar Journal description field"), - 'division2': _("divides top numeric input (numerator) by bottom numeric input (denominator)"), - 'empty': _("permanently deletes items in trash"), - 'eraseron': _("Clean"), - 'equal2': _("logical equal-to operator"), - 'extras': _("Palette of extra options"), - 'fillscreen': _("fills the background with (color, shade)"), - 'flow': _("Palette of flow operators"), - 'forever': _("loops forever"), - 'forward': _("moves turtle forward"), - 'fullscreen': _("hides the Sugar toolbars"), - 'gray': _("holds current gray level (can be used in place of a number block)"), - 'greater2': _("logical greater-than operator"), - 'hat1': _("top of Action 1 stack"), - 'hat2': _("top of Action 2 stack"), - 'hat': _("top of nameable action stack"), - 'heading': _("holds current heading value of the turtle (can be used in place of a number block)"), - 'height': _("the canvas height"), - 'hideblocks': _("declutters canvas by hiding blocks"), - 'hideshowoff': _("Hide blocks"), - 'hspace': _("jogs stack right"), - 'identity2': _("identity operator used for extending blocks"), - 'ifelse': _("if-then-else operator that uses boolean operators from Numbers palette"), - 'if': _("if-then operator that uses boolean operators from Numbers palette"), - 'journal': _("Sugar Journal media object"), - 'kbinput': _("query for keyboard input (results stored in keyboard block)"), - 'keyboard': _("holds results of query-keyboard block"), - 'leftpos': _("xcor of left of screen"), - 'left': _("turns turtle counterclockwise (angle in degrees)"), - 'less2': _("logical less-than operator"), - 'luminance': _("light level detected by camera"), - 'mediawait': _("wait for current video or audio to complete"), - 'minus2': _("subtracts bottom numeric input from top numeric input"), - 'myfunc': _("a programmable block: used to add advanced math equations, e.g., sin(x)"), - 'myfunc1arg': _("a programmable block: used to add advanced single-variable math equations, e.g., sin(x)"), - 'myfunc2arg': _("a programmable block: used to add advanced multi-variable math equations, e.g., sqrt(x*x+y*y)"), - 'myfunc3arg': _("a programmable block: used to add advanced multi-variable math equations, e.g., sin(x+y+z)"), - 'next': _('displays next palette'), - 'nop': _("runs code found in the tamyblock.py module found in the Journal"), - 'not': _("logical NOT operator"), - 'numbers': _("Palette of numeric operators"), - 'number': _("used as numeric input in mathematic operators"), - 'or': _("logical OR operator"), - 'orientation': _("changes the orientation of the palette of blocks"), - 'pendown': _("Turtle will draw when moved."), - 'pen': _("Palette of pen commands"), - 'pensize': _("holds current pen size (can be used in place of a number block)"), - 'penup': _("Turtle will not draw when moved."), - 'picture1x1': _("presentation template: select Journal object (with description)"), - 'picture1x1a': _("presentation template: select Journal object (no description)"), - 'picture1x2': _("presentation template: select two Journal objects"), - 'picture2x1': _("presentation template: select two Journal objects"), - 'picture2x2': _("presentation template: select four Journal objects"), - 'picturelist': _("presentation template: list of bullets"), - 'pitch': _('microphone input pitch'), - 'plus2': _("adds two alphanumeric inputs"), - 'polar': _("displays polar coordinates"), - 'pop': _("pops value off FILO (first-in last-out heap)"), - 'portfolio': _("Palette of presentation templates"), - 'print': _("prints value in status block at bottom of the screen"), - 'printheap': _("shows values in FILO (first-in last-out heap)"), - 'product2': _("multiplies two numeric inputs"), - 'push': _("pushes value onto FILO (first-in last-out heap)"), - 'random': _("returns random number between minimum (top) and maximum (bottom) values"), - 'readcamera': _("Average RGB color from camera is pushed to the stack"), - 'readpixel': _("RGB color under the turtle is pushed to the stack"), - 'remainder2': _("modular (remainder) operator"), - 'repeat': _("loops specified number of times"), - 'resistance': _("sensor input resistance"), - 'reskin': _("put a custom 'shell' on the turtle"), - 'restore': _("restores most recent blocks from trash"), - 'restoreall': _("restore all blocks from trash"), - 'rfid': _("RFID"), - 'rightpos': _("xcor of right of screen"), - 'right': _("turns turtle clockwise (angle in degrees)"), - 'run-fastoff': _("Run"), - 'run-slowoff': _("Step"), - 'sandwichbottom': _("bottom block in a collapsibe stack: click to collapse"), - 'sandwichcollapsed': _("bottom block in a collapsed stack: click to open"), - 'sandwichtop': _("top of a collapsible stack"), - 'sandwichtop_no_label': _("top of a collapsed stack"), - 'sandwichtop_no_arm': _("top of a collapsible stack"), - 'sandwichtop_no_arm_no_label': _("top of a collapsed stack"), - 'savepix': _("saves a picture to the Sugar Journal"), - 'savesvg': _("saves turtle graphics as an SVG file in the Sugar Journal"), - 'scale': _("holds current scale value"), - 'see': _('returns the color that the turtle "sees"'), - 'sensor': _("Palette of sensor blocks"), - 'setcolor': _("sets color of the line drawn by the turtle"), - 'setgray': _("sets gray level of the line drawn by the turtle"), - 'seth': _("sets the heading of the turtle (0 is towards the top of the screen.)"), - 'setpensize': _("sets size of the line drawn by the turtle"), - 'setscale': _("sets the scale of media"), - 'setshade': _("sets shade of the line drawn by the turtle"), - 'settextcolor': _("sets color of text drawn by the turtle"), - 'settextsize': _("sets size of text drawn by turtle"), - 'setxy': _("moves turtle to position xcor, ycor; (0, 0) is in the center of the screen."), - 'setxy2': _("moves turtle to position xcor, ycor; (0, 0) is in the center of the screen."), - 'shade': _("holds current pen shade"), - 'show': _("draws text or show media from the Journal"), - 'showblocks': _("restores hidden blocks"), - 'skin': _("put a custom 'shell' on the turtle"), - 'sound': _("raw microphone input signal"), - 'sqrt': _("calculates square root"), - 'stack1': _("invokes Action 1 stack"), - 'stack2': _("invokes Action 2 stack"), - 'stack': _("invokes named action stack"), - 'start': _("connects action to toolbar run buttons"), - 'startfill': _("starts filled polygon (used with end fill block)"), - 'stopfill': _("completes filled polygon (used with start fill block)"), - 'stopiton': _("Stop turtle"), - 'stopstack': _("stops current action"), - 'storeinbox1': _("stores numeric value in Variable 1"), - 'storeinbox2': _("stores numeric value in Variable 2"), - 'storein': _("stores numeric value in named variable"), - 'string': _("string value"), - 'template1x1': _("presentation template: select Journal object (with description)"), - 'template1x1a': _("presentation template: select Journal object (no description)"), - 'template1x2': _("presentation template: select two Journal objects"), - 'template2x1': _("presentation template: select two Journal objects"), - 'template2x2': _("presentation template: select four Journal objects"), - 'templatelist': _("presentation template: list of bullets"), - 'textcolor': _("holds current text color (can be used in place of a number block)"), - 'textsize': _("holds current text size (can be used in place of a number block)"), - 'toppos': _("ycor of top of screen"), - 'trash': _("Trashcan"), - 'turtle': _("Palette of turtle commands"), - 'until': _("do-until-True operator that uses boolean operators from Numbers palette"), - 'userdefined': _("runs code found in the tamyblock.py module found in the Journal"), - 'userdefined2args': _("runs code found in the tamyblock.py module found in the Journal"), - 'userdefined3args': _("runs code found in the tamyblock.py module found in the Journal"), - 'video': _("Sugar Journal video object"), - 'voltage': _("sensor voltage"), - 'volume': _("microphone input volume"), - 'vspace': _("jogs stack down"), - 'wait': _("pauses program execution a specified number of seconds"), - 'while': _("do-while-True operator that uses boolean operators from Numbers palette"), - 'width': _("the canvas width"), - 'xcor': _("holds current x-coordinate value of the turtle (can be used in place of a number block)"), - 'ycor': _("holds current y-coordinate value of the turtle (can be used in place of a number block)")} - -# # 'dead key' Unicode dictionaries # DEAD_KEYS = ['grave', 'acute', 'circumflex', 'tilde', 'diaeresis', 'abovering'] -DEAD_DICTS = [{'A':192, 'E':200, 'I':204, 'O':210, 'U':217, 'a':224, 'e':232, - 'i':236, 'o':242, 'u':249}, - {'A':193, 'E':201, 'I':205, 'O':211, 'U':218, 'a':225, 'e':233, - 'i':237, 'o':243, 'u':250}, - {'A':194, 'E':202, 'I':206, 'O':212, 'U':219, 'a':226, 'e':234, - 'i':238, 'o':244, 'u':251}, - {'A':195, 'O':211, 'N':209, 'U':360, 'a':227, 'o':245, 'n':241, - 'u':361}, - {'A':196, 'E':203, 'I':207, 'O':211, 'U':218, 'a':228, 'e':235, - 'i':239, 'o':245, 'u':252}, - {'A':197, 'a':229}] +DEAD_DICTS = [{'A': 192, 'E': 200, 'I': 204, 'O': 210, 'U': 217, 'a': 224, + 'e': 232, 'i': 236, 'o': 242, 'u': 249}, + {'A': 193, 'E': 201, 'I': 205, 'O': 211, 'U': 218, 'a': 225, + 'e': 233, 'i': 237, 'o': 243, 'u': 250}, + {'A': 194, 'E': 202, 'I': 206, 'O': 212, 'U': 219, 'a': 226, + 'e': 234, 'i': 238, 'o': 244, 'u': 251}, + {'A': 195, 'O': 211, 'N': 209, 'U': 360, 'a': 227, 'o': 245, + 'n': 241, 'u': 361}, + {'A': 196, 'E': 203, 'I': 207, 'O': 211, 'U': 218, 'a': 228, + 'e': 235, 'i': 239, 'o': 245, 'u': 252}, + {'A': 197, 'a': 229}] NOISE_KEYS = ['Shift_L', 'Shift_R', 'Control_L', 'Caps_Lock', 'Pause', 'Alt_L', 'Alt_R', 'KP_Enter', 'ISO_Level3_Shift', 'KP_Divide', 'Escape', 'Return', 'KP_Page_Up', 'Up', 'Down', 'Menu', diff --git a/TurtleArt/taexporthtml.py b/TurtleArt/taexporthtml.py index 09042f8..843b8b0 100644 --- a/TurtleArt/taexporthtml.py +++ b/TurtleArt/taexporthtml.py @@ -22,67 +22,68 @@ import pygtk pygtk.require('2.0') import gtk import os.path -from tautils import data_to_string, save_picture, image_to_base64 -from gettext import gettext as _ from cgi import escape +from gettext import gettext as _ +from tautils import data_to_string, save_picture, image_to_base64, get_path -def save_html(self, tw, embed_flag=True): - """ Either: Save canvas and code or pictures to HTML """ - self.embed_images = embed_flag - - # A dictionary to define the HTML wrappers around template elements - self.html_glue = { - 'doctype': '\n', - 'html': ('\n", "\n'), - 'html_svg': ('\n', - '\n'), - 'head': ('\n\n', '\n'), - 'meta': '\n', - 'title': ('', '\n'), - 'style': ('\n'), - 'body': ('\n', '\n\n'), - 'div': ('
\n', '
\n'), - 'slide': ('\n\n'), - 'h1': ('

', '

\n'), - 'table': ('\n', - '\n\n'), - 'img': ('Image\n', + 'html': ('\n', '\n'), + 'html_svg': ('\n', + '\n'), + 'head': ('\n\n', '\n'), + 'meta': '\n', + 'title': ('', '\n'), + 'style': ('\n'), + 'body': ('\n', '\n\n'), + 'div': ('
\n', '
\n'), + 'slide': ('\n\n'), + 'h1': ('

', '

\n'), + 'table': ('
\n', + '\n\n'), + 'img': ('Image\n'), - 'img2': ('Image\n'), - 'img3': ('Image\n'), - 'ul': ('
\n', '
\n'), - 'li': ('', '\n')} + 'img2': ('Image\n'), + 'img3': ('Image\n'), + 'ul': ('\n', '
\n'), + 'li': ('', '\n')} - comment = '\n' - if self.embed_images == True: - self.html_glue['img'] = (' + \
-                                 \n') - self.html_glue['img2'] = ('Image\n') + + +def save_html(self, tw, embed_flag=True): + """ Either save the canvas and code or pictures to HTML """ + + if embed_flag: + HTML_GLUE['img'] = (' + \
+                                 \n') + HTML_GLUE['img2'] = ('Image\n') """ - If there are saved_pictures, put them into a .html; otherwise, save a - screendump and the turtle project code. + If there are saved_pictures, put them into a .html; otherwise, + save a screendump and the turtle project code. """ - code = '' + htmlcode = '' if len(tw.saved_pictures) > 0: for i, p in enumerate(tw.saved_pictures): - code += self.html_glue['slide'][0] + str(i) - code += self.html_glue['slide'][1] + \ - self.html_glue['div'][0] + \ - self.html_glue['h1'][0] - if self.embed_images == True: + htmlcode += HTML_GLUE['slide'][0] + str(i) + htmlcode += HTML_GLUE['slide'][1] + \ + HTML_GLUE['div'][0] + \ + HTML_GLUE['h1'][0] + if embed_flag: f = open(p, 'r') imgdata = f.read() f.close() @@ -90,10 +91,11 @@ def save_html(self, tw, embed_flag=True): tmp = imgdata else: pixbuf = gtk.gdk.pixbuf_new_from_file(p) - imgdata = image_to_base64(pixbuf, tw.activity) - tmp = self.html_glue['img2'][0] + imgdata = image_to_base64(pixbuf, + get_path(tw.activity, 'instance')) + tmp = HTML_GLUE['img2'][0] tmp += imgdata - tmp += self.html_glue['img2'][1] + tmp += HTML_GLUE['img2'][1] else: if p.endswith(('.svg')): f = open(p, 'r') @@ -101,49 +103,49 @@ def save_html(self, tw, embed_flag=True): f.close() tmp = imgdata else: - tmp = self.html_glue['img3'][0] + tmp = HTML_GLUE['img3'][0] tmp += p - tmp += self.html_glue['img3'][1] - code += tmp + \ - self.html_glue['h1'][1] + \ - self.html_glue['div'][1] + tmp += HTML_GLUE['img3'][1] + htmlcode += tmp + \ + HTML_GLUE['h1'][1] + \ + HTML_GLUE['div'][1] else: - if self.embed_images == True: + if embed_flag: imgdata = image_to_base64(save_picture(self.tw.canvas), - tw.activity) + get_path(tw.activity, 'instance')) else: imgdata = os.path.join(self.tw.load_save_folder, 'image') self.tw.save_as_image(imgdata) - code += (self.html_glue['img'][0] + imgdata + \ - self.html_glue['img'][1]) - code += self.html_glue['div'][0] - code += escape(data_to_string(tw.assemble_data_to_save(False, True))) - code += self.html_glue['div'][1] + htmlcode += (HTML_GLUE['img'][0] + imgdata + \ + HTML_GLUE['img'][1]) + htmlcode += HTML_GLUE['div'][0] + htmlcode += escape(data_to_string( + tw.assemble_data_to_save(False, True))) + htmlcode += HTML_GLUE['div'][1] if tw.running_sugar: title = _('Turtle Art') + ' ' + tw.activity.metadata['title'] else: title = _('Turtle Art') - header = self.html_glue['doctype'] + \ - self.html_glue['html'][0] - style = self.html_glue['style'][0] + \ - self.html_glue['style'][1] + header = HTML_GLUE['doctype'] + \ + HTML_GLUE['html'][0] + style = HTML_GLUE['style'][0] + \ + HTML_GLUE['style'][1] if len(tw.saved_pictures) > 0: if tw.saved_pictures[0].endswith(('.svg')): - header = self.html_glue['html_svg'][0] - style = comment + header = HTML_GLUE['html_svg'][0] + style = COMMENT - code = header + \ - self.html_glue['head'][0] + \ - self.html_glue['meta'] + \ - self.html_glue['title'][0] + \ + return header + \ + HTML_GLUE['head'][0] + \ + HTML_GLUE['meta'] + \ + HTML_GLUE['title'][0] + \ title + \ - self.html_glue['title'][1] + \ + HTML_GLUE['title'][1] + \ style + \ - self.html_glue['head'][1] + \ - self.html_glue['body'][0] + \ - code + \ - self.html_glue['body'][1] + \ - self.html_glue['html'][1] - return code + HTML_GLUE['head'][1] + \ + HTML_GLUE['body'][0] + \ + htmlcode + \ + HTML_GLUE['body'][1] + \ + HTML_GLUE['html'][1] diff --git a/TurtleArt/taexportlogo.py b/TurtleArt/taexportlogo.py index bd8a1e6..127689b 100644 --- a/TurtleArt/taexportlogo.py +++ b/TurtleArt/taexportlogo.py @@ -1,4 +1,4 @@ -#Copyright (c) 2008-10, Walter Bender +#Copyright (c) 2008-11, Walter Bender #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal @@ -18,351 +18,255 @@ #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. -IGNORE = ['hideblocks', 'showblocks', 'fullscreen', 'polar', 'cartesian', - 'sandwichbottom', 'id'] +from gettext import gettext as _ -import math try: from sugar.datastore import datastore + HAS_DATASTORE = True except: - pass + HAS_DATASTORE = False + +from TurtleArt.tapalette import logo_commands, logo_functions +from TurtleArt.taconstants import TITLEXY, CONSTANTS def save_logo(tw): """ Set up the Turtle Art color palette and color processing. """ - color_processing = '\ -to tasetpalette :i :r :g :b :myshade \r\ -make "s ((:myshade - 50) / 50) \r\ -ifelse lessp :s 0 [ \r\ -make "s (1 + (:s *0.8)) \r\ -make "r (:r * :s) \r\ -make "g (:g * :s) \r\ -make "b (:b * :s) \r\ -] [ \ -make "s (:s * 0.9) \r\ -make "r (:r + ((99-:r) * :s)) \r\ -make "g (:g + ((99-:g) * :s)) \r\ -make "b (:b + ((99-:b) * :s)) \r\ -] \ -setpalette :i (list :r :g :b) \r\ -end \r\ -\ -to rgb :myi :mycolors :myshade \r\ -make "myr first :mycolors \r\ -make "mycolors butfirst :mycolors \r\ -make "myg first :mycolors \r\ -make "mycolors butfirst :mycolors \r\ -make "myb first :mycolors \r\ -make "mycolors butfirst :mycolors \r\ -tasetpalette :myi :myr :myg :myb :myshade \r\ -output :mycolors \r\ -end \r\ -\ -to processcolor :mycolors :myshade \r\ -if emptyp :mycolors [stop] \r\ -make "i :i + 1 \r\ -processcolor (rgb :i :mycolors :myshade) :myshade \r\ -end \r\ -\ -to tasetshade :shade \r\ -make "myshade modulo :shade 200 \r\ -if greaterp :myshade 99 [make "myshade (199-:myshade)] \r\ -make "i 7 \r\ -make "mycolors :colors \r\ -processcolor :mycolors :myshade \r\ -end \r\ -\ -to tasetpencolor :c \r\ -make "color (modulo (round :c) 100) \r\ -setpencolor :color + 8 \r\ -end \r\ -\ -make "colors [ \ -99 0 0 99 5 0 99 10 0 99 15 0 99 20 0 \ -99 25 0 99 30 0 99 35 0 99 40 0 99 45 0 \ -99 50 0 99 55 0 99 60 0 99 65 0 99 70 0 \ -99 75 0 99 80 0 99 85 0 99 90 0 99 95 0 \ -99 99 0 90 99 0 80 99 0 70 99 0 60 99 0 \ -50 99 0 40 99 0 30 99 0 20 99 0 10 99 0 \ - 0 99 0 0 99 5 0 99 10 0 99 15 0 99 20 \ - 0 99 25 0 99 30 0 99 35 0 99 40 0 99 45 \ - 0 99 50 0 99 55 0 99 60 0 99 65 0 99 70 \ - 0 99 75 0 99 80 0 99 85 0 99 90 0 99 95 \ - 0 99 99 0 95 99 0 90 99 0 85 99 0 80 99 \ - 0 75 99 0 70 99 0 65 99 0 60 99 0 55 99 \ - 0 50 99 0 45 99 0 40 99 0 35 99 0 30 99 \ - 0 25 99 0 20 99 0 15 99 0 10 99 0 5 99 \ - 0 0 99 5 0 99 10 0 99 15 0 99 20 0 99 \ -25 0 99 30 0 99 35 0 99 40 0 99 45 0 99 \ -50 0 99 55 0 99 60 0 99 65 0 99 70 0 99 \ -75 0 99 80 0 99 85 0 99 90 0 99 95 0 99 \ -99 0 99 99 0 90 99 0 80 99 0 70 99 0 60 \ -99 0 50 99 0 40 99 0 30 99 0 20 99 0 10] \r\ -make "shade 50 \r\ -tasetshade :shade \r' - - bs = tw.just_blocks() - code = '' + + # We need to catch several special cases: stacks, boxes, labels, etc. + dispatch_table = { + 'label': _add_label, + 'to action': _add_named_stack, + 'action': _add_reference_to_stack, + 'storeinbox': _add_named_box, + 'box': _add_reference_to_box + } + constants_table = { + 'lpos': _lpos, + 'tpos': _tpos, + 'rpos': _rpos, + 'bpos': _bpos, + 'red': _red, + 'orange': _orange, + 'yellow': _yellow, + 'green': _green, + 'cyan': _cyan, + 'blue': _blue, + 'purple': _purple, + 'white': _white, + 'black': _black, + 'titlex': _titlex, + 'titley': _titley, + 'leftx': _leftx, + 'topy': _topy, + 'rightx': _rightx, + 'bottomy': _bottomy, + 'width': _width, + 'height': _height + } + + stacks_of_blocks = tw.just_blocks() stack_count = 0 - show = 0 - - # These flags are used to trigger the prepending of additional procedures. - random = False - fillscreen = False - setcolor = False - setxy = False - setxy2 = False - pensize = False - setpensize = False - arc = False - heap = False - write = False - minus = False - division = False - image = False + + logocode = '' """ Walk through the code, substituting UCB Logo for Turtle Art primitives. """ - for b in bs: + for stack in stacks_of_blocks: this_stack = '' - data = walk_stack(tw.lc, b, tw.block_list.list) - # We need to catch several special cases: stacks, random, etc. - stack = False - namedstack = False - namedbox = False - refstack = False - refbox = False - myvar = '' - for d in data: - if type(d) == type((1, 2)): - (d, b) = d - if type(d) is float: - if namedbox: - myvar += str(d) - myvar += ' ' - elif write: - this_stack += 'labelsize ' - this_stack += str(d) - write = False - else: - this_stack += str(d) - elif show == 2: - # Use title for Journal objects - if d[0:8] == '#smedia_': - try: - dsobject = datastore.get(d[8:]) - this_stack += dsobject.metadata['title'] - dsobject.destroy() - except: - this_stack += str(d) - else: - this_stack += str(d) - show = 0 + psuedocode = _walk_stack(tw, stack) + if psuedocode == []: + continue + + skip = False + for i in range(len(psuedocode)): + if skip: + skip = False + continue + blk = psuedocode[i] + if type(blk) == type((1, 2)): + (blk, _blk_no) = blk + if blk in logo_commands: + logo_command = logo_commands[blk] else: - # Translate some Turtle Art primitives into UCB Logo - if namedstack: - this_stack += 'to ' - this_stack += d[2:].replace(' ', '_') - this_stack += '\r' - stack = True - namedstack = False - elif namedbox: - if d[0:2] == '#s': - this_stack += 'make "' - this_stack += d[2:].replace(' ', '_') - this_stack += ' ' - this_stack += myvar - namedbox = False - myvar = '' - else: - myvar += d - elif refstack: - this_stack += d[2:].replace(' ', '_') - this_stack += ' ' - refstack = False - elif refbox: - this_stack += ':' - this_stack += d[2:].replace(' ', '_') - refbox = False - elif d == 'stack': - refstack = True - elif d == 'box': - refbox = True - elif d == 'storeinbox': - namedbox = True - elif d == 'storeinbox1': - this_stack += 'make "box1' - elif d == 'box1': - this_stack += ':box1' - elif d == 'storeinbox2': - this_stack += 'make "box2' - elif d == 'box2': - this_stack += ':box2' - elif d == 'shade': - this_stack += ':shade' - elif d == 'setshade': - setcolor = True - this_stack += 'tasetshade' - elif d == 'color': - this_stack += 'pencolor' - elif d == 'nop': - this_stack += ' ' - elif d == 'start': - this_stack += 'to start\r' - stack = True - elif d == 'nop1': - this_stack += 'to stack1\r' - stack = True - elif d == 'nop2': - this_stack += 'to stack2\r' - stack = True - elif d == 'nop3': - namedstack = True - elif d == 'stopstack': - this_stack += 'stop' - elif d == 'clean': - this_stack += 'clearscreen' - elif d == 'setxy': - setxy = True - this_stack += 'tasetxypenup' - elif d == 'setxy2': - setxy2 = True - this_stack += 'tasetxy' - elif d == 'color': - this_stack += ':color' - elif d == 'plus': - this_stack += 'sum' - elif d == 'setcolor': - setcolor = True - this_stack += 'tasetpencolor' - elif d == 'fillscreen': - fillscreen = True - setcolor = True - this_stack += 'tasetbackground' - elif d == 'random': - random = True - this_stack += 'tarandom' - elif d == 'pensize': - pensize = True - this_stack += 'tapensize' - elif d == 'setpensize': - setpensize = True - this_stack += 'tasetpensize' - elif d == 'arc': - arc = True - this_stack += 'taarc' - elif d == 'pop': - heap = True - this_stack += 'tapop' - elif d == 'push': - heap = True - this_stack += 'tapush' - elif d == 'heap': - heap = True - this_stack += 'taprintheap' - elif d == 'emptyheap': - heap = True - this_stack += 'taclearheap' - elif d == 'kbinput': - this_stack += 'make "keyboard readchar' - elif d == 'keyboard': - this_stack += ':keyboard' - elif d == 'insertimage': - image = True - elif image: - # Skip this arg - image = 2 - elif image == 2: - # Skip this arg - image = False - elif d[0:2] == '#s': - # output single characters as a string - if len(d[2:]): - this_stack += '"' - this_stack += d[2:] - # make a sentence out of everything else - else: - this_stack += 'sentence ' - this_stack += d[2:].replace('\s', ' "') - this_stack += '\r' - elif d == 'write': - this_stack += 'label' - write = True - elif d == 'show' or d == 'showaligned': - this_stack += 'label' - show = 1 - elif d == 'minus2': - this_stack += 'taminus' - minus = True - elif d == 'division': - this_stack += 'quotient' - elif d == 'lpos': - this_stack += str(-tw.canvas.width / (tw.coord_scale * 2)) - elif d == 'rpos': - this_stack += str(tw.canvas.width / (tw.coord_scale * 2)) - elif d == 'bpos': - this_stack += str(-tw.canvas.height / (tw.coord_scale * 2)) - elif d == 'tpos': - this_stack += str(tw.canvas.height / (tw.coord_scale * 2)) - elif d in IGNORE: - this_stack += ' ' - elif show == 1 and d[0:2] == '#s': - this_stack += d[2:] - # We don't handle depreciated 'template' blocks + logo_command = None + if i == 0 and not logo_command in ['to stack1\r', 'to stack2\r', + 'to action', 'to start\r']: + this_stack = 'to turtleblocks_%d\r' % (stack_count) + stack_count += 1 + if logo_command in dispatch_table: + if i + 1 < len(psuedocode): + this_stack += dispatch_table[logo_command]( + psuedocode[i + 1]) + skip = True else: - this_stack += d + print 'missing arg to %s' % (logo_command) + elif logo_command in constants_table: + this_stack += str(constants_table[logo_command](tw)) + elif logo_command is not None: + this_stack += logo_command + else: # assume it is an argument + if not blk in ['nop', 'nop1', 'nop2', 'nop3']: + if type(blk) == str and blk[0:2] == '#s': + this_stack += str(blk[2:]).replace(' ', '_') + else: + this_stack += str(blk).replace(' ', '_') this_stack += ' ' - if stack: - stack = False - # if it is not a stack, we need to add a 'to ta#' label - elif len(data) > 0: - this_stack = 'to ta' + str(stack_count) + '\r' + this_stack - stack_count += 1 - if len(data) > 0: - code += this_stack - code += '\rend\r' - - # We need to define some additional procedures. - if minus: # Logo minus only takes one argument. - code = 'to taminus :y :x\routput sum :x minus :y\rend\r' + code - if random: # to avoid negative numbers - code = 'to tarandom :min :max\r' + \ - 'output (random (:max - :min)) + :min\rend\r' + code - if fillscreen: # Set shade than background color - code = 'to tasetbackground :color :shade\r' + \ - 'tasetshade :shade\rsetbackground :color\rend\r' + code - if setcolor: # Load the Turtle Art color palette. - code = color_processing + code - if setpensize: # Set int of pensize - code = 'to tasetpensize :a\rsetpensize round :a\rend\r' + code - if pensize: # Return only the first argument. - code = 'to tapensize\routput first round pensize\rend\r' + code - if setxy2: # Swap and round arguments - code = 'to tasetxy :x :y\rsetxy :x :y\rend\r' + code - if setxy: # Swap and round arguments and add pen up/down - code = 'to tasetxy :x :y\rpenup\rsetxy :x :y\rpendown\rend\r' + code - if arc: # Turtle Art 'arc' needs to be redefined. - c = (2 * math.pi) / 360 - code = 'to taarc :a :r\rrepeat round :a [right 1 forward (' + \ - str(c) + ' * :r)]\rend\r' + code - if heap: # Add psuedo 'push' and 'pop' - code = 'to tapush :foo\rmake "taheap fput :foo :taheap\rend\r' + \ - 'to tapop\rif emptyp :taheap [stop]\rmake \'tmp first :taheap\r' +\ - 'make "taheap butfirst :taheap\routput :tmp\rend\r' + \ - 'to taclearheap\rmake "taheap []\rend\r' + \ - 'to taprintheap \rprint :taheap\rend\r' + \ - 'make "taheap []\r' + code - code = 'window\r' + code - return code - - -def walk_stack(lc, blk, list): + + logocode += this_stack + logocode += '\rend\r' + + # We may need to prepend some additional procedures. + for key in logo_functions.iterkeys(): + if key in logocode: + logocode = logo_functions[key] + logocode + + if 'tasetshade' in logocode or 'tasetpencolor' in logocode or \ + 'tasetbackground' in logocode: + logocode = logo_functions['tacolor'] + logocode + + logocode = 'window\r' + logocode + return logocode + + +def _add_label(string): + if type(string) == str and string[0:8] in ['#smedia_', '#saudio_', + '#svideo_', '#sdescr_']: + string = string[8:] + if HAS_DATASTORE: + dsobject = datastore.get(string[8:]) + if 'title' in dsobject.metadata: + string = dsobject.metadata['title'] + else: + string = str(string) + if string[0:2] == '#s': + string = string[2:] + string = '"' + string + if string.count(' ') > 0: + return 'label sentence %s\r' % (string.replace(' ', ' "')) + else: + return 'label %s' % (string.replace(' ', '_')) + + +def _add_named_stack(action): + if type(action) == str and action[0:2] == '#s': + return 'to %s\r' % (str(action[2:]).replace(' ', '_')) + else: + return 'to %s\r' % (str(action).replace(' ', '_')) + + +def _add_reference_to_stack(action): + if type(action) == str and action[0:2] == '#s': + return '%s' % (str(action[2:]).replace(' ', '_')) + else: + return '%s' % (str(action).replace(' ', '_')) + + +def _add_named_box(box_name): + if type(box_name) == str and box_name[0:2] == '#s': + return 'make "%s' % (str(box_name[2:]).replace(' ', '_')) + else: + return 'make "%s' % (str(box_name).replace(' ', '_')) + + +def _add_reference_to_box(box_name): + if type(box_name) == str and box_name[0:2] == '#s': + return ':%s' % (str(box_name[2:]).replace(' ', '_')) + else: + return ':%s' % (str(box_name).replace(' ', '_')) + + +def _lpos(tw): + return int(-tw.canvas.width / (tw.coord_scale * 2)) + + +def _tpos(tw): + return int(tw.canvas.height / (tw.coord_scale * 2)) + + +def _rpos(tw): + return int(tw.canvas.width / (tw.coord_scale * 2)) + + +def _bpos(tw): + return int(-tw.canvas.height / (tw.coord_scale * 2)) + + +def _width(tw): + return int(tw.canvas.width / tw.coord_scale) + + +def _height(tw): + int(tw.canvas.height / tw.coord_scale) + + +def _titlex(tw): + return int(-(tw.canvas.width * TITLEXY[0]) / (tw.coord_scale * 2)) + + +def _titley(tw): + return int((tw.canvas.height * TITLEXY[1]) / (tw.coord_scale * 2)) + + +def _leftx(tw): + return int(-(tw.canvas.width * TITLEXY[0]) / (tw.coord_scale * 2)) + + +def _topy(tw): + return int((tw.canvas.height * (TITLEXY[1] - 0.125)) / (tw.coord_scale * 2)) + + +def _rightx(tw): + return 0 + + +def _bottomy(tw): + return 0 + + +def _red(tw): + return CONSTANTS['red'] + + +def _orange(tw): + return CONSTANTS['orange'] + + +def _yellow(tw): + return CONSTANTS['yellow'] + + +def _green(tw): + return CONSTANTS['green'] + + +def _cyan(tw): + return CONSTANTS['cyan'] + + +def _blue(tw): + return CONSTANTS['blue'] + + +def _purple(tw): + return CONSTANTS['purple'] + + +def _white(tw): + return 1 + + +def _black(tw): + return 0 + + +def _walk_stack(tw, blk_in_stack): """ Convert blocks to logo psuedocode. """ from tautils import find_top_block - top = find_top_block(blk) - if blk == top: - code = lc.run_blocks(top, list, False) - return code + top = find_top_block(blk_in_stack) + if blk_in_stack == top: + psuedocode = tw.lc.run_blocks(top, tw.block_list.list, False) + return psuedocode else: return [] diff --git a/TurtleArt/tagettext.py b/TurtleArt/tagettext.py new file mode 100644 index 0000000..ebeebb3 --- /dev/null +++ b/TurtleArt/tagettext.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2010-11 Walter Bender, Martin Langhoff +# License: GPLv2 + +# Defines the magic global _() with the right params so all modules +# can use it. +# +# Plugins that want to override MUST use a different technique. See +# the developer notes in the TA wikipage. +# +import gettext +import os + +# In a git checkout, locale is in the root of the project +# so one dir "up" from tagettext.py +localedir = os.path.join(os.path.dirname(os.path.dirname(__file__)), + 'locale' ) + +if os.path.exists(localedir): + # works from a git checkout + gettext.install('org.laptop.TurtleArtActivity', localedir) +else: + # fallback for packaged TA (rpm, xo) + gettext.install('org.laptop.TurtleArtActivity') diff --git a/TurtleArt/tajail.py b/TurtleArt/tajail.py index 081248f..0444dc7 100644 --- a/TurtleArt/tajail.py +++ b/TurtleArt/tajail.py @@ -19,14 +19,14 @@ #THE SOFTWARE. # A naive approach to running myfunc in a jail -import logging -_logger = logging.getLogger('turtleart-activity') import traceback from time import * from math import * +from gettext import gettext as _ def myfunc(f, args): + ''' Run inline Python code ''' # check to make sure no import calls are made if len(args) == 1: myf = 'def f(x): return ' + f.replace('import', '') @@ -45,11 +45,16 @@ def myfunc(f, args): return userdefined.values()[0](args[0], args[1], args[2]) -def myfunc_import(lc, f, x): +def myfunc_import(parent, f, x): + ''' Run Python code imported from Journal ''' + if 'def myblock(lc,' in f: + base_class = parent.tw.lc # pre-v107, we passed lc + else: + base_class = parent.tw # as of v107, we pass tw userdefined = {} try: exec f in globals(), userdefined - return userdefined['myblock'](lc, x) + return userdefined['myblock'](base_class, x) except: traceback.print_exc() return None diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py index e3576fa..12be92f 100644 --- a/TurtleArt/talogo.py +++ b/TurtleArt/talogo.py @@ -22,65 +22,22 @@ #THE SOFTWARE. import gtk - from time import time -from math import sqrt -from numpy import append -from numpy.fft import rfft -from random import uniform -from operator import isNumberType -from fcntl import ioctl - -import os.path +from operator import isNumberType from UserDict import UserDict -try: - from sugar.datastore import datastore -except ImportError: - pass - -from taconstants import TAB_LAYER, BLACK, WHITE, \ - DEFAULT_SCALE, ICON_SIZE, BLOCK_NAMES, CONSTANTS, SENSOR_DC_NO_BIAS, \ - SENSOR_DC_BIAS, XO1, XO15 -from tagplay import play_audio_from_file, play_movie_from_file, stop_media, \ - media_playing -from tacamera import Camera -import v4l2 -from tajail import myfunc, myfunc_import +from taconstants import TAB_LAYER, DEFAULT_SCALE, PREFIX_DICTIONARY +from tapalette import block_names, value_blocks from tautils import get_pixbuf_from_journal, convert, data_from_file, \ - text_media_type, round_int, chr_to_ord, strtype, get_path - -from RtfParser import RtfTextOnly + text_media_type, round_int, debug_output -from ringbuffer import RingBuffer1d +from util.RtfParser import RtfTextOnly from gettext import gettext as _ -VALUE_BLOCKS = ['box1', 'box2', 'color', 'shade', 'gray', 'scale', 'pensize', - 'heading', 'xcor', 'ycor', 'pop', 'see', 'keyboard', - 'sound', 'volume', 'pitch', 'resistance', 'voltage', - 'luminance'] - -import logging -_logger = logging.getLogger('turtleart-activity') - - -def find_device(): - """ Search for RFID devices. Return a device instance or None. """ - device = None - for i in os.listdir(os.path.join('.', 'devices')): - if not os.path.isdir(os.path.join('.', 'devices', i)): - try: - _tempmod = __import__('devices.%s' % i.split('.')[0], - globals(), locals(), ['RFIDReader'], -1) - devtemp = _tempmod.RFIDReader() - if devtemp.get_present() == True: - device = devtemp - except Exception, e: - _logger.error('FIND_DEVICE: %s: %s' % (i, e)) - pass - return device +media_blocks_dictionary = {} # new media blocks get added here +primitive_dictionary = {} # new block primitives get added here class noKeyError(UserDict): @@ -112,213 +69,6 @@ class logoerror(Exception): # Utility functions - -def _num_type(x): - """ Is x a number type? """ - if type(x) == int: - return True - if type(x) == float: - return True - if type(x) == ord: - return True - return False - - -def _string_to_num(x): - """ Try to comvert a string to a number """ - xx = convert(x.replace(self.tw.decimal_point, '.'), float) - if type(xx) is float: - return xx - else: - xx, xflag = chr_to_ord(x) - if xflag: - return xx - else: - raise logoerror("#syntaxerror") - - -def _and(x, y): - """ Logical and """ - return x & y - - -def _or(x, y): - """ Logical or """ - return x | y - - -def _careful_divide(x, y): - """ Raise error on divide by zero """ - try: - return x / y - except ZeroDivisionError: - raise logoerror("#zerodivide") - except TypeError: - try: - return _string_to_num(x) / _string_to_num(y) - except ZeroDivisionError: - raise logoerror("#zerodivide") - except ValueError: - raise logoerror("#syntaxerror") - except TypeError: - raise logoerror("#notanumber") - - -def _equal(x, y): - """ Numeric and logical equal """ - try: - return float(x) == float(y) - except TypeError: - typex, typey = False, False - if strtype(x): - typex = True - if strtype(y): - typey = True - if typex and typey: - return x == y - try: - return _string_to_num(x) == _string_to_num(y) - except ValueError: - raise logoerror("#syntaxerror") - - -def _less(x, y): - """ Compare numbers and strings """ - try: - return float(x) < float(y) - except ValueError: - typex, typey = False, False - if strtype(x): - typex = True - if strtype(y): - typey = True - if typex and typey: - return x < y - try: - return _string_to_num(x) < _string_to_num(y) - except TypeError: - raise logoerror("#notanumber") - - -def _more(x, y): - """ Compare numbers and strings """ - return _less(y, x) - - -def _plus(x, y): - """ Add numbers, concat strings """ - if _num_type(x) and _num_type(y): - return(x + y) - else: - if _num_type(x): - xx = str(round_int(x)) - else: - xx = str(x) - if _num_type(y): - yy = str(round_int(y)) - else: - yy = str(y) - return(xx + yy) - - -def _minus(x, y): - """ Numerical subtraction """ - if _num_type(x) and _num_type(y): - return(x - y) - try: - return _string_to_num(x) - _string_to_num(y) - except TypeError: - raise logoerror("#notanumber") - - -def _product(x, y): - """ Numerical multiplication """ - if _num_type(x) and _num_type(y): - return(x * y) - try: - return _string_to_num(x) * _string_to_num(y) - except TypeError: - raise logoerror("#notanumber") - - -def _mod(x, y): - """ Numerical mod """ - if _num_type(x) and _num_type(y): - return(x % y) - try: - return _string_to_num(x) % _string_to_num(y) - except TypeError: - raise logoerror("#notanumber") - except ValueError: - raise logoerror("#syntaxerror") - - -def _sqrt(x): - """ Square root """ - if _num_type(x): - if x < 0: - raise logoerror("#negroot") - return sqrt(x) - try: - return sqrt(_string_to_num(x)) - except ValueError: - raise logoerror("#negroot") - except TypeError: - raise logoerror("#notanumber") - - -def _random(x, y): - """ Random integer """ - if _num_type(x) and _num_type(y): - return(int(round(uniform(x, y), 0))) - xx, xflag = chr_to_ord(x) - yy, yflag = chr_to_ord(y) - if xflag and yflag: - return chr(int(round(uniform(xx, yy), 0))) - if not xflag: - xx = _string_to_num(x) - if not yflag: - yy = _string_to_num(y) - try: - return(int(round(uniform(xx, yy), 0))) - except TypeError: - raise logoerror("#notanumber") - - -def _identity(x): - """ Identity function """ - return(x) - - -def _avg(array, abs_value=False): - """ Calc. the average value of an array """ - if len(array) == 0: - return 0 - array_sum = 0 - if abs_value: - for a in array: - array_sum += abs(a) - else: - for a in array: - array_sum += a - return float(array_sum) / len(array) - - -def stop_logo(tw): - """ Stop logo is called from the Stop button on the toolbar """ - tw.step_time = 0 - tw.lc.step = _just_stop() - stop_media(tw.lc) - if tw.camera_available: - if tw.lc._video_capture_device is not None: - # restore AG and then close device - tw.lc._set_ag(1) - tw.lc._video_capture_device.close() - tw.lc._video_capture_device = None - tw.lc.camera.stop_camera_input() - tw.active_turtle.show() - - def _just_stop(): """ yield False to stop stack """ yield False @@ -337,164 +87,15 @@ class LogoCode: self.tw = tw self.oblist = {} - DEFPRIM = { - '(': [1, lambda self, x: self._prim_opar(x)], - 'and': [2, lambda self, x, y: _and(x, y)], - 'arc': [2, lambda self, x, y: self._prim_arc(self.tw.canvas.arc, x, - y)], - 'audio': [1, lambda self, x: self._play_sound(x)], - 'back': [1, lambda self, x: self._prim_move(self.tw.canvas.forward, - -x)], - 'black': [0, lambda self: BLACK], - 'blue': [0, lambda self: CONSTANTS['blue']], - 'bpos': [0, lambda self: CONSTANTS['bottompos']], - 'boty': [0, lambda self: CONSTANTS['bottomy']], - 'box1': [0, lambda self: self.boxes['box1']], - 'box': [1, lambda self, x: self._box(x)], - 'box2': [0, lambda self: self.boxes['box2']], - 'bullet': [1, self._prim_bullet, True], - 'bulletlist': [1, self._prim_list, True], - 'cartesian': [0, lambda self: self.tw.set_cartesian(True)], - 'clean': [0, lambda self: self.prim_clear()], - 'clearheap': [0, lambda self: self._empty_heap()], - 'color': [0, lambda self: self.tw.canvas.color], - 'gray': [0, lambda self: self.tw.canvas.gray], - 'comment': [1, lambda self, x: self._prim_print(x, True)], - 'container': [1, lambda self, x: x], - 'cyan': [0, lambda self: CONSTANTS['cyan']], - 'define': [2, self._prim_define], - 'division': [2, lambda self, x, y: _careful_divide(x, y)], - 'equal?': [2, lambda self, x, y: _equal(x, y)], - 'fillscreen': [2, lambda self, x, y: self.tw.canvas.fillscreen(x, y)], - 'forever': [1, self._prim_forever, True], - 'forward': [1, lambda self, x: self._prim_move(self.tw.canvas.forward, - x)], - 'fullscreen': [0, lambda self: self.tw.set_fullscreen()], - 'greater?': [2, lambda self, x, y: _more(x, y)], - 'green': [0, lambda self: CONSTANTS['green']], - 'heading': [0, lambda self: self.tw.canvas.heading], - 'hideblocks': [0, lambda self: self.tw.hideblocks()], - 'hres': [0, lambda self: CONSTANTS['width']], - 'id': [1, lambda self, x: _identity(x)], - 'if': [2, self._prim_if, True], - 'ifelse': [3, self._prim_ifelse, True], - 'insertimage': [1, lambda self, x: self._insert_image(False, - filepath=x)], - 'kbinput': [0, lambda self: self._prim_kbinput()], - 'keyboard': [0, lambda self: self.keyboard], - 'left': [1, lambda self, x: self._prim_right(-x)], - 'leftx': [0, lambda self: CONSTANTS['leftx']], - 'lpos': [0, lambda self: CONSTANTS['leftpos']], - 'less?': [2, lambda self, x, y: _less(x, y)], - 'luminance': [0, lambda self: self._read_camera(True)], - 'mediawait': [0, self._media_wait, True], - 'minus': [2, lambda self, x, y: _minus(x, y)], - 'mod': [2, lambda self, x, y: _mod(x, y)], - 'myfunction': [2, lambda self, f, x: self._myfunction(f, [x])], - 'myfunction2': [3, lambda self, f, x, y: self._myfunction(f, [x, y])], - 'myfunction3': [4, lambda self, f, x, y, z: self._myfunction( - f, [x, y, z])], - 'nop': [0, lambda self: None], - 'nop1': [0, lambda self: None], - 'nop2': [0, lambda self: None], - 'nop3': [1, lambda self, x: None], - 'not': [1, lambda self, x: not x], - 'orange': [0, lambda self: CONSTANTS['orange']], - 'or': [2, lambda self, x, y: _or(x, y)], - 'pendown': [0, lambda self: self.tw.canvas.setpen(True)], - 'pensize': [0, lambda self: self.tw.canvas.pensize], - 'penup': [0, lambda self: self.tw.canvas.setpen(False)], - 'pitch': [0, lambda self: self._get_pitch()], - 'plus': [2, lambda self, x, y: _plus(x, y)], - 'polar': [0, lambda self: self.tw.set_polar(True)], - 'pop': [0, lambda self: self._prim_pop()], - 'print': [1, lambda self, x: self._prim_print(x, False)], - 'printheap': [0, lambda self: self._prim_print_heap()], - 'product': [2, lambda self, x, y: _product(x, y)], - 'purple': [0, lambda self: CONSTANTS['purple']], - 'push': [1, lambda self, x: self._prim_push(x)], - 'random': [2, lambda self, x, y: _random(x, y)], - 'readcamera': [0, lambda self: self._read_camera()], - 'readpixel': [0, lambda self: self._read_pixel()], - 'red': [0, lambda self: CONSTANTS['red']], - 'repeat': [2, self._prim_repeat, True], - 'resistance': [0, lambda self: self._get_resistance()], - 'rfid': [0, lambda self: self.tw.rfid_idn], - 'right': [1, lambda self, x: self._prim_right(x)], - 'rightx': [0, lambda self: CONSTANTS['rightx']], - 'rpos': [0, lambda self: CONSTANTS['rightpos']], - 'savepix': [1, lambda self, x: self._save_picture(x)], - 'savesvg': [1, lambda self, x: self._save_svg(x)], - 'scale': [0, lambda self: self.scale], - 'see': [0, lambda self: self.see()], - 'setcolor': [1, lambda self, x: self._prim_set('color', - self.tw.canvas.setcolor, x)], - 'setgray': [1, lambda self, x: self._prim_set('gray', - self.tw.canvas.setgray, x)], - 'seth': [1, lambda self, x: self._prim_set('heading', - self.tw.canvas.seth, x)], - 'setpensize': [1, lambda self, x: self._prim_set('pensize', - self.tw.canvas.setpensize, x)], - 'setscale': [1, lambda self, x: self._prim_set('scale', - self._set_scale, x)], - 'setshade': [1, lambda self, x: self._prim_set('shade', - self.tw.canvas.setshade, x)], - 'settextcolor': [1, lambda self, x: self.tw.canvas.settextcolor(x)], - 'settextsize': [1, lambda self, x: self.tw.canvas.settextsize(x)], - 'setxy2': [2, lambda self, x, y: self._prim_move(self.tw.canvas.setxy, - x, y)], - 'setxy': [2, lambda self, x, y: self._prim_move(self.tw.canvas.setxy, - x, y, pendown=False)], - 'shade': [0, lambda self: self.tw.canvas.shade], - 'show': [1, lambda self, x: self._show(x, True)], - 'showaligned': [1, lambda self, x: self._show(x, False)], - 'showblocks': [0, lambda self: self.tw.showblocks()], - 'skin': [1, lambda self, x: self._reskin(x)], - 'sound': [0, lambda self: self._get_sound()], - 'sqrt': [1, lambda self, x: _sqrt(x)], - 'stack1': [0, self._prim_stack1, True], - 'stack': [1, self._prim_stack, True], - 'stack2': [0, self._prim_stack2, True], - 'start': [0, lambda self: self._prim_start()], - 'startfill': [0, lambda self: self.tw.canvas.start_fill()], - 'stopfill': [0, lambda self: self.tw.canvas.stop_fill()], - 'stopstack': [0, lambda self: self._prim_stopstack()], - 'storeinbox1': [1, lambda self, x: self._prim_setbox('box1', None, x)], - 'storeinbox2': [1, lambda self, x: self._prim_setbox('box2', None, x)], - 'storeinbox': [2, lambda self, x, y: self._prim_setbox('box3', x, y)], - 't1x1': [2, lambda self, x, y: self._show_template1x1(x, y)], - 't1x1a': [2, lambda self, x, y: self._show_template1x1a(x, y)], - 't1x2': [3, lambda self, x, y, z: self._show_template1x2(x, y, z)], - 't2x1': [3, lambda self, x, y, z: self._show_template2x1(x, y, z)], - 't2x2': [5, lambda self, x, y, z, a, b: self._show_template2x2( - x, y, z, a, b)], - 'textcolor': [0, lambda self: self.tw.canvas.textcolor], - 'textsize': [0, lambda self: self.tw.textsize], - 'titlex': [0, lambda self: CONSTANTS['titlex']], - 'titley': [0, lambda self: CONSTANTS['titley']], - 'topy': [0, lambda self: CONSTANTS['topy']], - 'tpos': [0, lambda self: CONSTANTS['toppos']], - 'turtle': [1, lambda self, x: self.tw.canvas.set_turtle(x)], - 'userdefined': [1, lambda self, x: self._prim_myblock([x])], - 'userdefined2': [2, lambda self, x, y: self._prim_myblock([x, y])], - 'userdefined3': [3, lambda self, x, y, - z: self._prim_myblock([x, y, z])], - 'video': [1, lambda self, x: self._play_video(x)], - 'voltage': [0, lambda self: self._get_voltage()], - 'volume': [0, lambda self: self._get_volume()], - 'vres': [0, lambda self: CONSTANTS['height']], - 'wait': [1, self._prim_wait, True], - 'white': [0, lambda self: WHITE], - 'write': [2, lambda self, x, y: self._write(self, x, y)], - 'xcor': [0, lambda self: self.tw.canvas.xcor / self.tw.coord_scale], - 'ycor': [0, lambda self: self.tw.canvas.ycor / self.tw.coord_scale], - 'yellow': [0, lambda self: CONSTANTS['yellow']]} + DEFPRIM = {'(': [1, lambda self, x: self._prim_opar(x)], + 'define': [2, self._prim_define], + 'nop': [0, lambda self: None]} for p in iter(DEFPRIM): if len(DEFPRIM[p]) == 2: - self._def_prim(p, DEFPRIM[p][0], DEFPRIM[p][1]) + self.def_prim(p, DEFPRIM[p][0], DEFPRIM[p][1]) else: - self._def_prim(p, DEFPRIM[p][0], DEFPRIM[p][1], DEFPRIM[p][2]) + self.def_prim(p, DEFPRIM[p][0], DEFPRIM[p][1], DEFPRIM[p][2]) self.symtype = type(self._intern('print')) self.listtype = type([]) @@ -520,38 +121,26 @@ class LogoCode: self.trace = 0 self.update_values = False self.gplay = None - self.ag = None self.filepath = None self.dsobject = None + self.start_time = None - # Scale factors for depreciated portfolio blocks - self.title_height = int((self.tw.canvas.height / 20) * self.tw.scale) self.body_height = int((self.tw.canvas.height / 40) * self.tw.scale) - self.bullet_height = int((self.tw.canvas.height / 30) * self.tw.scale) self.scale = DEFAULT_SCALE - self.max_samples = 1500 - self.input_step = 1 - - self.ringbuffer = RingBuffer1d(self.max_samples, dtype='int16') - if self.tw.hw == XO1: - self.voltage_gain = 0.00002225 - self.voltage_bias = 1.140 - elif self.tw.hw == XO15: - self.voltage_gain = -0.0001471 - self.voltage_bias = 1.695 - - if self.tw.camera_available: - self._video_capture_device = None - if self.tw.running_sugar: - self.imagepath = get_path(self.tw.activity, - 'data/turtlepic.png') - else: - self.imagepath = '/tmp/turtlepic.png' - self.camera = Camera(self.imagepath) + def stop_logo(self): + """ Stop logo is called from the Stop button on the toolbar """ + self.tw.step_time = 0 + self.step = _just_stop() + for plugin in self.tw._plugins: + plugin.stop() + if self.tw.gst_available: + from tagplay import stop_media + stop_media(self) + self.tw.active_turtle.show() - def _def_prim(self, name, args, fcn, rprim=False): + def def_prim(self, name, args, fcn, rprim=False): """ Define the primitives associated with the blocks """ sym = self._intern(name) sym.nargs, sym.fcn = args, fcn @@ -573,8 +162,6 @@ class LogoCode: self.stacks['stack2'] = None self.tw.saving_svg = False - self.find_value_blocks() - self._update_audio_mode() if self.trace > 0: self.update_values = True else: @@ -592,7 +179,11 @@ class LogoCode: if b.connections is not None and len(b.connections) > 1 and \ b.connections[1] is not None: code = self._blocks_to_code(b) - x = b.connections[1].values[0] + try: + x = b.connections[1].values[0] + except IndexError: + self.tw.showlabel('#nostack') + return None if type(convert(x, float, False)) == float: if int(float(x)) == x: x = int(x) @@ -600,7 +191,8 @@ class LogoCode: code = self._blocks_to_code(blk) if run_flag: - _logger.debug("running code: %s" % (code)) + # debug_output("running code: %s" % (code), self.tw.running_sugar) + self.start_time = time() self._setup_cmd(code) if not self.tw.hide: self.tw.display_coordinates() @@ -625,35 +217,22 @@ class LogoCode: code.append(float(blk.values[0])) except ValueError: code.append(float(ord(blk.values[0][0]))) - elif blk.name == 'string' or blk.name == 'title': + elif blk.name == 'string' or \ + blk.name == 'title': # deprecated block if type(blk.values[0]) == float or type(blk.values[0]) == int: if int(blk.values[0]) == blk.values[0]: blk.values[0] = int(blk.values[0]) code.append('#s' + str(blk.values[0])) else: code.append('#s' + blk.values[0]) - elif blk.name == 'journal': - if blk.values[0] is not None: - code.append('#smedia_' + str(blk.values[0])) - else: - code.append('#smedia_None') - elif blk.name == 'description': + elif blk.name in PREFIX_DICTIONARY: if blk.values[0] is not None: - code.append('#sdescr_' + str(blk.values[0])) + code.append(PREFIX_DICTIONARY[blk.name] + \ + str(blk.values[0])) else: - code.append('#sdescr_None') - elif blk.name == 'audio': - if blk.values[0] is not None: - code.append('#saudio_' + str(blk.values[0])) - else: - code.append('#saudio_None') - elif blk.name == 'video': - if blk.values[0] is not None: - code.append('#svideo_' + str(blk.values[0])) - else: - code.append('#svideo_None') - elif blk.name == 'camera': - code.append('#smedia_CAMERA') + code.append(PREFIX_DICTIONARY[blk.name] + 'None') + elif blk.name in media_blocks_dictionary: + code.append('#smedia_' + blk.name.upper()) else: return ['%nothing%'] else: @@ -718,7 +297,7 @@ class LogoCode: elif self.tw.interactive_mode: self.tw.toolbar_shapes['stopiton'].set_layer(TAB_LAYER) self.running = True - self._icall(self._evline, blklist) + self.icall(self.evline, blklist) yield True if self.tw.running_sugar: self.tw.activity.stop_turtle_button.set_icon("stopitoff") @@ -727,12 +306,12 @@ class LogoCode: yield False self.running = False - def _icall(self, fcn, *args): + def icall(self, fcn, *args): """ Add a function and its arguments to the program stack. """ self.istack.append(self.step) self.step = fcn(*(args)) - def _evline(self, blklist): + def evline(self, blklist): """ Evaluate a line of code from the list. """ oldiline = self.iline self.iline = blklist[:] @@ -762,7 +341,7 @@ class LogoCode: (token, self.bindex) = self.iline[1] # Process the token and any arguments. - self._icall(self._eval) + self.icall(self._eval) yield True # Time to unhighlight the current block. @@ -778,7 +357,7 @@ class LogoCode: self.tw.block_list.list[self.bindex].highlight() raise logoerror(str(self.iresult)) self.iline = oldiline - self._ireturn() + self.ireturn() if not self.tw.hide and self.tw.step_time > 0: self.tw.display_coordinates() yield True @@ -795,7 +374,7 @@ class LogoCode: # We highlight blocks here in case an error occurs... if not self.tw.hide and bindex is not None: self.tw.block_list.list[bindex].highlight() - self._icall(self._evalsym, token) + self.icall(self._evalsym, token) yield True # and unhighlight if everything was OK. if not self.tw.hide and bindex is not None: @@ -804,7 +383,7 @@ class LogoCode: else: res = token - self._ireturn(res) + self.ireturn(res) yield True def _evalsym(self, token): @@ -817,32 +396,31 @@ class LogoCode: raise logoerror("#noinput") for i in range(token.nargs): self._no_args_check() - self._icall(self._eval) + self.icall(self._eval) yield True self.arglist.append(self.iresult) if self.cfun.rprim: if type(self.cfun.fcn) == self.listtype: - _logger.debug("evalsym rprim list: %s" % (str(token))) - self._icall(self._ufuncall, self.cfun.fcn) + # debug_output('evalsym rprim list: %s' % (str(token)), + # self.tw.running_sugar) + self.icall(self._ufuncall, self.cfun.fcn) yield True else: - # print "evalsym rprim: ", token - self._icall(self.cfun.fcn, *self.arglist) + self.icall(self.cfun.fcn, *self.arglist) yield True result = None else: - # print "evalsym: ", token result = self.cfun.fcn(self, *self.arglist) self.cfun, self.arglist = oldcfun, oldarglist if self.arglist is not None and result == None: raise logoerror("%s %s %s" % \ (oldcfun.name, _("did not output to"), self.cfun.name)) - self._ireturn(result) + self.ireturn(result) yield True def _ufuncall(self, body): """ ufuncall """ - self._ijmp(self._evline, body) + self.ijmp(self.evline, body) yield True def doevalstep(self): @@ -869,12 +447,12 @@ class LogoCode: return False return True - def _ireturn(self, res=None): + def ireturn(self, res=None): """ return value """ self.step = self.istack.pop() self.iresult = res - def _ijmp(self, fcn, *args): + def ijmp(self, fcn, *args): """ ijmp """ self.step = fcn(*(args)) @@ -898,123 +476,6 @@ class LogoCode: # Primitives # - def prim_clear(self): - """ Clear screen """ - stop_media(self) - self.tw.canvas.clearscreen() - self.scale = DEFAULT_SCALE - self.tw.set_polar(False) - self.tw.set_cartesian(False) - self.hidden_turtle = None - for name in VALUE_BLOCKS: - self.update_label_value(name) - - def _prim_start(self): - """ Start block: recenter """ - if self.tw.running_sugar: - self.tw.activity.recenter() - - def _prim_wait(self, time): - """ Show the turtle while we wait """ - self.tw.active_turtle.show() - endtime = _millisecond() + time * 1000. - while _millisecond() < endtime: - yield True - self.tw.active_turtle.hide() - self._ireturn() - yield True - - def _prim_repeat(self, num, blklist): - """ Repeat list num times. """ - num = self._int(num) - for i in range(num): - self._icall(self._evline, blklist[:]) - yield True - if self.procstop: - break - self._ireturn() - yield True - - def _prim_bullet(self, blklist): - """ Depreciated bullet-list block style """ - self._show_bullets(blklist) - self._ireturn() - yield True - - def _prim_list(self, blklist): - """ Expandable list block """ - self._show_list(blklist) - self._ireturn() - yield True - - def _myfunction(self, f, x): - """ Programmable block """ - try: - y = myfunc(f, x) - if str(y) == 'nan': - _logger.debug("python function returned nan") - stop_logo(self.tw) - raise logoerror("#notanumber") - else: - return y - except ZeroDivisionError: - stop_logo(self.tw) - raise logoerror("#zerodivide") - except ValueError, e: - stop_logo(self.tw) - raise logoerror('#' + str(e)) - except SyntaxError, e: - stop_logo(self.tw) - raise logoerror('#' + str(e)) - except NameError, e: - stop_logo(self.tw) - raise logoerror('#' + str(e)) - except OverflowError: - stop_logo(self.tw) - raise logoerror("#overflowerror") - except TypeError: - stop_logo(self.tw) - raise logoerror("#notanumber") - - def _prim_forever(self, blklist): - """ Do list forever """ - while True: - self._icall(self._evline, blklist[:]) - yield True - if self.procstop: - break - self._ireturn() - yield True - - ''' - def _prim_while(self, list1, list2): - list = [self._intern('if')] - for i in list1: - list.append(i) - list.append(list2) - while self._icall(self._evline, list[:]): - yield True - self._ireturn() - yield True - ''' - - def _prim_if(self, boolean, blklist): - """ If bool, do list """ - if boolean: - self._icall(self._evline, blklist[:]) - yield True - self._ireturn() - yield True - - def _prim_ifelse(self, boolean, list1, list2): - """ If bool, do list1, else do list2 """ - if boolean: - self._ijmp(self._evline, list1[:]) - yield True - else: - self._ijmp(self._evline, list2[:]) - yield True - def _prim_opar(self, val): self.iline.pop(0) return val @@ -1026,49 +487,19 @@ class LogoCode: name.nargs, name.fcn = 0, body name.rprim = True - def _prim_stack(self, x): - """ Process a named stack """ - if type(convert(x, float, False)) == float: - if int(float(x)) == x: - x = int(x) - if 'stack3' + str(x) not in self.stacks or\ - self.stacks['stack3' + str(x)] is None: - raise logoerror("#nostack") - self._icall(self._evline, self.stacks['stack3' + str(x)][:]) - yield True - self.procstop = False - self._ireturn() - yield True - - def _prim_stack1(self): - """ Process Stack 1 """ - if self.stacks['stack1'] is None: - raise logoerror("#nostack") - self._icall(self._evline, self.stacks['stack1'][:]) - yield True - self.procstop = False - self._ireturn() - yield True - - def _prim_stack2(self): - """ Process Stack 2 """ - if self.stacks['stack2'] is None: - raise logoerror("#nostack") - self._icall(self._evline, self.stacks['stack2'][:]) - yield True - self.procstop = False - self._ireturn() - yield True - - def _prim_stopstack(self): - """ Stop execution of a stack """ - self.procstop = True - - def _prim_print_heap(self): - """ Display contents of heap """ - self.tw.showlabel('status', str(self.heap) + ' ') + def prim_clear(self): + """ Clear screen """ + if self.tw.gst_available: + from tagplay import stop_media + # stop_media(self) # TODO: gplay variable + self.tw.canvas.clearscreen() + self.tw.lc.scale = DEFAULT_SCALE + self.tw.lc.hidden_turtle = None + self.tw.lc.start_time = time() + for name in value_blocks: + self.tw.lc.update_label_value(name) - def _int(self, n): + def int(self, n): """ Raise an error if n doesn't convert to int. """ if type(n) == int: return n @@ -1080,96 +511,20 @@ class LogoCode: raise logoerror("%s %s %s %s" \ % (self.cfun.name, _("doesn't like"), str(n), _("as input"))) - def _box(self, x): - """ Retrieve value from named box """ - if type(convert(x, float, False)) == float: - if int(float(x)) == x: - x = int(x) - try: - return self.boxes['box3' + str(x)] - except KeyError: - raise logoerror("#emptybox") - - def _prim_myblock(self, x): - """ Run Python code imported from Journal """ - if self.bindex is not None and self.bindex in self.tw.myblock: - try: - if len(x) == 1: - myfunc_import(self, self.tw.myblock[self.bindex], x[0]) - else: - myfunc_import(self, self.tw.myblock[self.bindex], x) - except: - raise logoerror("#syntaxerror") - - def _prim_print(self, n, flag): - """ Print object n """ - if flag and (self.tw.hide or self.tw.step_time == 0): - return - if type(n) == str or type(n) == unicode: - if n[0:6] == 'media_' and n[6:] != 'CAMERA': - try: - if self.tw.running_sugar: - try: - dsobject = datastore.get(n[6:]) - except: - _logger.debug("Couldn't open %s" % (n[6:])) - self.tw.showlabel('status', dsobject.metadata['title']) - dsobject.destroy() - else: - self.tw.showlabel('status', n[6:]) - except IOError: - self.tw.showlabel('status', n) - else: - self.tw.showlabel('status', n) - elif type(n) == int: - self.tw.showlabel('status', n) - else: - self.tw.showlabel('status', - str(round_int(n)).replace('.', self.tw.decimal_point)) - - def _prim_kbinput(self): - """ Query keyboard """ - if len(self.tw.keypress) == 1: - self.keyboard = ord(self.tw.keypress[0]) - else: - try: - self.keyboard = {'Escape': 27, 'space': 32, ' ': 32, - 'Return': 13, \ - 'KP_Up': 2, 'KP_Down': 4, 'KP_Left': 1, \ - 'KP_Right': 3}[self.tw.keypress] - except KeyError: - self.keyboard = 0 - self.update_label_value('keyboard', self.keyboard) - self.tw.keypress = '' - def find_value_blocks(self): """ Find any value blocks that may need label updates """ - self.value_blocks = {} - for name in VALUE_BLOCKS: - self.value_blocks[name] = self.tw.block_list.get_similar_blocks( - 'block', name) - - def _update_audio_mode(self): - """ If there are sensor blocks, set the appropriate audio mode """ - for name in ['sound', 'volume', 'pitch']: - if len(self.value_blocks[name]) > 0: - self.tw.audiograb.set_sensor_type() - return - if len(self.value_blocks['resistance']) > 0: - self.tw.audiograb.set_sensor_type(SENSOR_DC_BIAS) - return - elif len(self.value_blocks['voltage']) > 0: - self.tw.audiograb.set_sensor_type(SENSOR_DC_NO_BIAS) - return + self.value_blocks_to_update = {} + for name in value_blocks: + self.value_blocks_to_update[name] = \ + self.tw.block_list.get_similar_blocks('block', name) def update_label_value(self, name, value=None): """ Update the label of value blocks to reflect current value """ - if self.tw.hide or not self.tw.interactive_mode or \ - not hasattr(self, 'value_blocks'): + if self.tw.hide or not self.tw.interactive_mode: return if value is None: - for block in self.value_blocks[name]: - block.spr.set_label(BLOCK_NAMES[name][0]) + for block in self.value_blocks_to_update[name]: + block.spr.set_label(block_names[name][0]) block.resize() elif self.update_values: if type(value) == float: @@ -1177,70 +532,10 @@ class LogoCode: self.tw.decimal_point) else: valstring = str(value) - for block in self.value_blocks[name]: - block.spr.set_label(BLOCK_NAMES[name][0] + ' = ' + valstring) + for block in self.value_blocks_to_update[name]: + block.spr.set_label(block_names[name][0] + ' = ' + valstring) block.resize() - def _prim_set(self, name, cmd, value=None): - """ Set a value and update the associated value blocks """ - if value is not None: - cmd(value) - self.update_label_value(name, value) - - def _prim_right(self, value): - self.tw.canvas.right(float(value)) - self.update_label_value('heading', self.tw.canvas.heading) - - def _prim_move(self, cmd, value1, value2=None, pendown=True): - if value2 is None: - cmd(value1) - else: - cmd(float(value1), float(value2), pendown=pendown) - self.update_label_value('xcor', - self.tw.canvas.xcor / self.tw.coord_scale) - self.update_label_value('ycor', - self.tw.canvas.ycor / self.tw.coord_scale) - if len(self.value_blocks['see']) > 0: - self.see() - - def _prim_arc(self, cmd, value1, value2): - cmd(float(value1), float(value2)) - self.update_label_value('xcor', - self.tw.canvas.xcor / self.tw.coord_scale) - self.update_label_value('ycor', - self.tw.canvas.ycor / self.tw.coord_scale) - self.update_label_value('heading', self.tw.canvas.heading) - if len(self.value_blocks['see']) > 0: - self.see() - - def _prim_setbox(self, name, x, val): - """ Define value of named box """ - if x is not None: - if type(convert(x, float, False)) == float: - if int(float(x)) == x: - x = int(x) - self.boxes[name + str(x)] = val - return - - self.boxes[name] = val - self.update_label_value(name, val) - - def _prim_push(self, val): - """ Push value onto FILO """ - self.heap.append(val) - self.update_label_value('pop', val) - - def _prim_pop(self): - """ Pop value off of FILO """ - if len(self.heap) == 0: - raise logoerror("#emptyheap") - else: - if len(self.heap) == 1: - self.update_label_value('pop') - else: - self.update_label_value('pop', self.heap[-2]) - return self.heap.pop(-1) - def push_file_data_to_heap(self, dsobject): """ push contents of a data store object (assuming json encoding) """ data = data_from_file(dsobject.file_path) @@ -1249,188 +544,59 @@ class LogoCode: self.heap.append(val) self.update_label_value('pop', self.heap[-1]) - def _empty_heap(self): - """ Empty FILO """ - self.heap = [] - - def _save_picture(self, name): - """ Save canvas to file as PNG """ - self.tw.save_as_image(name) - - def _save_svg(self, name): - """ Save SVG to file """ - self.tw.canvas.svg_close() - self.tw.save_as_image(name, True) - - def _show_list(self, sarray): - """ Display list of media objects """ - x = self.tw.canvas.xcor / self.tw.coord_scale - y = self.tw.canvas.ycor / self.tw.coord_scale - for s in sarray: - self.tw.canvas.setxy(x, y, pendown=False) - self._show(s) - y -= int(self.tw.canvas.textsize * self.tw.lead) - - def _set_scale(self, x): - """ Set scale used by media object display """ - self.scale = x - - def _reskin(self, media): - """ Reskin the turtle with an image from a file """ - scale = int(ICON_SIZE * float(self.scale) / DEFAULT_SCALE) - if scale < 1: - return - self.filepath = None - dsobject = None - if os.path.exists(media[6:]): # is it a path? - self.filepath = media[6:] - elif self.tw.running_sugar: # is it a datastore object? - try: - dsobject = datastore.get(media[6:]) - except: - _logger.debug("Couldn't open skin %s" % (media[6:])) - if dsobject is not None: - self.filepath = dsobject.file_path - if self.filepath == None: - self.tw.showlabel('nojournal', self.filepath) - return - pixbuf = None - try: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.filepath, scale, - scale) - except: - self.tw.showlabel('nojournal', self.filepath) - _logger.debug("Couldn't open skin %s" % (self.filepath)) - if pixbuf is not None: - self.tw.active_turtle.set_shapes([pixbuf]) - pen_state = self.tw.active_turtle.get_pen_state() - if pen_state: - self.tw.canvas.setpen(False) - self.tw.canvas.forward(0) - if pen_state: - self.tw.canvas.setpen(True) - - def _x(self): + def x2tx(self): """ Convert screen coordinates to turtle coordinates """ return int(self.tw.canvas.width / 2) + int(self.tw.canvas.xcor) - def _y(self): + def y2ty(self): """ Convert screen coordinates to turtle coordinates """ return int(self.tw.canvas.height / 2) - int(self.tw.canvas.ycor) - def _w(self): - """ Convert screen coordinates to turtle coordinates """ + def wpercent(self): + """ width as a percentage of screen coordinates """ return int((self.tw.canvas.width * self.scale) / 100.) - def _h(self): - """ Convert screen coordinates to turtle coordinates """ + def hpercent(self): + """ height as a percentage of screen coordinates """ return int((self.tw.canvas.height * self.scale) / 100.) - def _show(self, string, center=False): - """ Show is the general-purpose media-rendering block. """ - if type(string) == str or type(string) == unicode: - if string in ['media_', 'descr_', 'audio_', 'video_', - 'media_None', 'descr_None', 'audio_None', - 'video_None']: - pass - elif string[0:6] in ['media_', 'descr_', 'audio_', 'video_']: - self.filepath = None - self.dsobject = None - if string[6:] == 'CAMERA': - if self.tw.camera_available: - self.camera.save_camera_input_to_file() - self.camera.stop_camera_input() - self.filepath = self.imagepath - elif os.path.exists(string[6:]): # is it a path? - self.filepath = string[6:] - elif self.tw.running_sugar: # is it a datastore object? - try: - self.dsobject = datastore.get(string[6:]) - except: - _logger.debug("Couldn't find dsobject %s" % ( - string[6:])) - if self.dsobject is not None: - self.filepath = self.dsobject.file_path - if self.filepath == None: - if self.dsobject is not None: - self.tw.showlabel('nojournal', - self.dsobject.metadata['title']) - else: - self.tw.showlabel('nojournal', string[6:]) - _logger.debug("Couldn't open %s" % (string[6:])) - elif string[0:6] == 'media_': - self._insert_image(center) - elif string[0:6] == 'descr_': - mimetype = None - if self.dsobject is not None and \ - 'mime_type' in self.dsobject.metadata: - mimetype = self.dsobject.metadata['mime_type'] - description = None - if self.dsobject is not None and \ - 'description' in self.dsobject.metadata: - description = self.dsobject.metadata['description'] - self._insert_desc(mimetype, description) - elif string[0:6] == 'audio_': - self._play_sound() - elif string[0:6] == 'video_': - self._play_video() - if self.dsobject is not None: - self.dsobject.destroy() - else: # assume it is text to display - x = self._x() - y = self._y() - if center: - y -= self.tw.canvas.textsize - self.tw.canvas.draw_text(string, x, y, - int(self.tw.canvas.textsize * \ - self.scale / 100.), - self.tw.canvas.width - x) - elif type(string) == float or type(string) == int: - string = round_int(string) - x = self._x() - y = self._y() - if center: - y -= self.tw.canvas.textsize - self.tw.canvas.draw_text(string, x, y, - int(self.tw.canvas.textsize * \ - self.scale / 100.), - self.tw.canvas.width - x) - - def _insert_image(self, center=False, filepath=None): + def insert_image(self, center=False, filepath=None): """ Image only (at current x, y) """ if filepath is not None: self.filepath = filepath pixbuf = None - w = self._w() - h = self._h() + w, h = self.wpercent(), self.hpercent() if w < 1 or h < 1: return - if self.filepath is not None and self.filepath != '': + if self.dsobject is not None: + try: + pixbuf = get_pixbuf_from_journal(self.dsobject, w, h) + except: + debug_output("Couldn't open dsobject %s" % (self.dsobject), + self.tw.running_sugar) + if pixbuf is None and \ + self.filepath is not None and \ + self.filepath != '': try: pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.filepath, w, h) except: self.tw.showlabel('nojournal', self.filepath) - _logger.debug("Couldn't open filepath %s" % (self.filepath)) - elif self.dsobject is not None: - try: - pixbuf = get_pixbuf_from_journal(self.dsobject, w, h) - except: - self.tw.showlabel('nojournal', self.dsobject) - _logger.debug("Couldn't open dsobject %s" % (self.dsobject)) + debug_output("Couldn't open filepath %s" % (self.filepath), + self.tw.running_sugar) if pixbuf is not None: if center: self.tw.canvas.draw_pixbuf(pixbuf, 0, 0, - self._x() - int(w / 2), - self._y() - int(h / 2), w, h, + self.x2tx() - int(w / 2), + self.y2ty() - int(h / 2), w, h, self.filepath) else: - self.tw.canvas.draw_pixbuf(pixbuf, 0, 0, self._x(), self._y(), - w, h, self.filepath) + self.tw.canvas.draw_pixbuf(pixbuf, 0, 0, self.x2tx(), + self.y2ty(), w, h, self.filepath) - def _insert_desc(self, mimetype=None, description=None): + def insert_desc(self, mimetype=None, description=None): """ Description text only (at current x, y) """ - w = self._w() + w = self.wpercent() if w < 1: return text = None @@ -1448,378 +614,38 @@ class LogoCode: f.close() except IOError: self.tw.showlabel('nojournal', self.filepath) - _logger.debug("Couldn't open filepath %s" % \ - (self.filepath)) + debug_output("Couldn't open %s" % (self.filepath), + self.tw.running_sugar) else: if description is not None: text = str(description) else: text = self.filepath if text is not None: - self.tw.canvas.draw_text(text, self._x(), self._y(), + self.tw.canvas.draw_text(text, self.x2tx(), self.y2ty(), self.body_height, w) - def _media_wait(self): + def media_wait(self): """ Wait for media to stop playing """ - while(media_playing(self)): - yield True - self._ireturn() + if self.tw.gst_available: + from tagplay import media_playing + while(media_playing(self)): + yield True + self.ireturn() yield True - def _play_sound(self): + def play_sound(self): """ Sound file from Journal """ - play_audio_from_file(self, self.filepath) + if self.tw.gst_available: + from tagplay import play_audio_from_file + play_audio_from_file(self, self.filepath) - def _play_video(self): + def play_video(self): """ Movie file from Journal """ - w = self._w() - h = self._h() + w, h = self.wpercent(), self.hpercent() if w < 1 or h < 1: return - play_movie_from_file(self, self.filepath, self._x(), self._y(), - self._w(), self._h()) - - def see(self): - """ Read r, g, b from the canvas and return a corresponding palette - color """ - r, g, b, a = self.tw.canvas.get_pixel() - color_index = self.tw.canvas.get_color_index(r, g, b) - self.update_label_value('see', color_index) - return color_index - - def _read_pixel(self): - """ Read r, g, b, a from the canvas and push b, g, r to the stack """ - r, g, b, a = self.tw.canvas.get_pixel() - self.heap.append(b) - self.heap.append(g) - self.heap.append(r) - - def _read_camera(self, luminance_only=False): - """ Read average pixel from camera and push b, g, r to the stack """ - - if not self.tw.camera_available: - if not luminance_only: - self.heap.append(-1) - self.heap.append(-1) - self.heap.append(-1) - return -1 - - pixbuf = None - array = None - w = self._w() - if w < 1: - w = 1 - h = self._h() - if h < 1: - h = 1 - - self._video_capture_device = None - try: - self._video_capture_device = open('/dev/video0', 'rw') - except: - _logger.debug('video capture device not available') - if self._video_capture_device is not None: - self._set_ag(0) # disable autogain - - self.camera.save_camera_input_to_file() - self.camera.stop_camera_input() - - if self._video_capture_device is not None: - self._set_ag(1) # restore autogain and close device - self._video_capture_device.close() - self._video_capture_device = None - - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.imagepath, w, h) - array = pixbuf.get_pixels() - - array_length = len(array) / 3 - r = 0 - g = 0 - b = 0 - i = 0 - for j in range(array_length): - r += ord(array[i]) - i += 1 - g += ord(array[i]) - i += 1 - b += ord(array[i]) - i += 1 - if luminance_only: - lum = int((r * 0.3 + g * 0.6 + b * 0.1) / array_length) - self.update_label_value('luminance', lum) - return lum - else: - self.heap.append(int((b / array_length))) - self.heap.append(int((g / array_length))) - self.heap.append(int((r / array_length))) - - def _set_ag(self, value): - """ set camera autogain (0==off, 1==on) """ - self._ag_control = v4l2.v4l2_control(v4l2.V4L2_CID_AUTOGAIN) - try: - ioctl(self._video_capture_device, v4l2.VIDIOC_G_CTRL, - self._ag_control) - self._ag_control.value = value - ioctl(self._video_capture_device, v4l2.VIDIOC_S_CTRL, - self._ag_control) - ioctl(self._video_capture_device, v4l2.VIDIOC_G_CTRL, - self._ag_control) - except: - _logger.debug('AUTOGAIN control not available') - - def _get_volume(self): - """ return mic in value """ - #TODO: Adjust gain for different HW - buf = self.ringbuffer.read(None, self.input_step) - if len(buf) > 0: - volume = float(_avg(buf, abs_value=True)) - self.update_label_value('volume', volume) - return volume - else: - return 0 - - def _get_sound(self): - """ return raw mic in value """ - buf = self.ringbuffer.read(None, self.input_step) - if len(buf) > 0: - sound = float(buf[0]) - self.update_label_value('sound', sound) - return sound - else: - return 0 - - def _get_pitch(self): - """ return index of max value in fft of mic in values """ - buf = [] - for i in range(4): - buf = append(buf, self.ringbuffer.read(None, self.input_step)) - if len(buf) > 0: - r = [] - for j in rfft(buf): - r.append(abs(j)) - # Convert output to Hertz - pitch = r.index(max(r)) * 48000 / len(buf) - self.update_label_value('pitch', pitch) - return pitch - else: - return 0 - - def _get_resistance(self): - """ return resistance sensor value """ - buf = self.ringbuffer.read(None, self.input_step) - if len(buf) > 0: - # See - # TODO: test this calibration on XO 1.5 - if self.tw.hw == XO1: - resistance = 2.718 ** ((float(_avg(buf)) * 0.000045788) + \ - 8.0531) - else: - avg_buf = float(_avg(buf)) - if avg_buf > 0: - resistance = (420000000 / avg_buf) - 13500 - else: - resistance = 420000000 - self.update_label_value('resistance', resistance) - return resistance - else: - return 0 - - def _get_voltage(self): - """ return voltage sensor value """ - buf = self.ringbuffer.read(None, self.input_step) - if len(buf) > 0: - # See - voltage = float(_avg(buf)) * self.voltage_gain + self.voltage_bias - self.update_label_value('voltage', voltage) - return voltage - else: - return 0 - - # Depreciated block methods - - def _show_template1x1(self, title, media): - """ title, one image, and description """ - xo = self.tw.calc_position('t1x1')[2] - x = -(self.tw.canvas.width / 2) + xo - y = self.tw.canvas.height / 2 - self.tw.canvas.setxy(x, y, pendown=False) - # save the text size so we can restore it later - save_text_size = self.tw.canvas.textsize - # set title text - self.tw.canvas.settextsize(self.title_height) - self._show(title) - # calculate and set scale for media blocks - myscale = 45 * (self.tw.canvas.height - self.title_height * 2) \ - / self.tw.canvas.height - self._set_scale(myscale) - # set body text size - self.tw.canvas.settextsize(self.body_height) - # render media object - # leave some space below the title - y -= int(self.title_height * 2 * self.tw.lead) - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media) - if self.tw.running_sugar: - x = 0 - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media.replace('media_', 'descr_')) - # restore text size - self.tw.canvas.settextsize(save_text_size) - - def _show_template2x1(self, title, media1, media2): - """ title, two images (horizontal), two descriptions """ - xo = self.tw.calc_position('t2x1')[2] - x = -(self.tw.canvas.width / 2) + xo - y = self.tw.canvas.height / 2 - self.tw.canvas.setxy(x, y, pendown=False) - # save the text size so we can restore it later - save_text_size = self.tw.canvas.textsize - # set title text - self.tw.canvas.settextsize(self.title_height) - self._show(title) - # calculate and set scale for media blocks - myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ - self.tw.canvas.height - self._set_scale(myscale) - # set body text size - self.tw.canvas.settextsize(self.body_height) - # render four quadrents - # leave some space below the title - y -= int(self.title_height * 2 * self.tw.lead) - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media1) - x = 0 - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media2) - y = -self.title_height - if self.tw.running_sugar: - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media2.replace('media_', 'descr_')) - x = -(self.tw.canvas.width / 2) + xo - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media1.replace('media_', 'descr_')) - # restore text size - self.tw.canvas.settextsize(save_text_size) - - def _show_bullets(self, sarray): - """ title and varible number of bullets """ - xo = self.tw.calc_position('bullet')[2] - x = -(self.tw.canvas.width / 2) + xo - y = self.tw.canvas.height / 2 - self.tw.canvas.setxy(x, y, pendown=False) - # save the text size so we can restore it later - save_text_size = self.tw.canvas.textsize - # set title text - self.tw.canvas.settextsize(self.title_height) - self._show(sarray[0]) - # set body text size - self.tw.canvas.settextsize(self.bullet_height) - # leave some space below the title - y -= int(self.title_height * 2 * self.tw.lead) - for s in sarray[1:]: - self.tw.canvas.setxy(x, y, pendown=False) - self._show(s) - y -= int(self.bullet_height * 2 * self.tw.lead) - # restore text size - self.tw.canvas.settextsize(save_text_size) - - def _show_template1x2(self, title, media1, media2): - """ title, two images (vertical), two desciptions """ - xo = self.tw.calc_position('t1x2')[2] - x = -(self.tw.canvas.width / 2) + xo - y = self.tw.canvas.height / 2 - self.tw.canvas.setxy(x, y, pendown=False) - # save the text size so we can restore it later - save_text_size = self.tw.canvas.textsize - # set title text - self.tw.canvas.settextsize(self.title_height) - self._show(title) - # calculate and set scale for media blocks - myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ - self.tw.canvas.height - self._set_scale(myscale) - # set body text size - self.tw.canvas.settextsize(self.body_height) - # render four quadrents - # leave some space below the title - y -= int(self.title_height * 2 * self.tw.lead) - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media1) - if self.tw.running_sugar: - x = 0 - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media1.replace('media_', 'descr_')) - y = -self.title_height - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media2.replace('media_', 'descr_')) - x = -(self.tw.canvas.width / 2) + xo - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media2) - # restore text size - self.tw.canvas.settextsize(save_text_size) - - def _show_template2x2(self, title, media1, media2, media3, media4): - """ title and four images """ - xo = self.tw.calc_position('t2x2')[2] - x = -(self.tw.canvas.width / 2) + xo - y = self.tw.canvas.height / 2 - self.tw.canvas.setxy(x, y, pendown=False) - # save the text size so we can restore it later - save_text_size = self.tw.canvas.textsize - # set title text - self.tw.canvas.settextsize(self.title_height) - self._show(title) - # calculate and set scale for media blocks - myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ - self.tw.canvas.height - self._set_scale(myscale) - # set body text size - self.tw.canvas.settextsize(self.body_height) - # render four quadrents - # leave some space below the title - y -= int(self.title_height * 2 * self.tw.lead) - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media1) - x = 0 - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media2) - y = -self.title_height - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media4) - x = -(self.tw.canvas.width / 2) + xo - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media3) - # restore text size - self.tw.canvas.settextsize(save_text_size) - - def _show_template1x1a(self, title, media1): - """ title, one media object """ - xo = self.tw.calc_position('t1x1a')[2] - x = -(self.tw.canvas.width / 2) + xo - y = self.tw.canvas.height / 2 - self.tw.canvas.setxy(x, y, pendown=False) - # save the text size so we can restore it later - save_text_size = self.tw.canvas.textsize - # set title text - self.tw.canvas.settextsize(self.title_height) - self._show(title) - # calculate and set scale for media blocks - myscale = 90 * (self.tw.canvas.height - self.title_height * 2) / \ - self.tw.canvas.height - self._set_scale(myscale) - # set body text size - self.tw.canvas.settextsize(self.body_height) - # render media object - # leave some space below the title - y -= int(self.title_height * 2 * self.tw.lead) - self.tw.canvas.setxy(x, y, pendown=False) - self._show(media1) - # restore text size - self.tw.canvas.settextsize(save_text_size) - - def _write(self, string, fsize): - """ Write string at size """ - x = self.tw.canvas.width / 2 + int(self.tw.canvas.xcor) - y = self.tw.canvas.height / 2 - int(self.tw.canvas.ycor) - self.tw.canvas.draw_text(string, x, y - 15, int(fsize), - self.tw.canvas.width) + if self.tw.gst_available: + from tagplay import play_movie_from_file + play_movie_from_file(self, self.filepath, self.x2tx(), self.y2ty(), + w, h) diff --git a/TurtleArt/tapalette.py b/TurtleArt/tapalette.py new file mode 100644 index 0000000..61fcb14 --- /dev/null +++ b/TurtleArt/tapalette.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +palette_names = [] +palette_blocks = [] +block_colors = [] +expandable_blocks = [] +block_names = {} +block_primitives = {} +default_values = {} +logo_commands = {} +logo_functions = {} +special_names = {} # Names for blocks without names for popup help +content_blocks = ['number', 'string', 'description', 'audio', 'video', + 'journal'] +value_blocks = [] # blocks whose labels are updated get added here +special_block_colors = {} +block_styles = {'basic-style': [], + 'blank-style': [], + 'basic-style-head': [], + 'basic-style-head-1arg': [], + 'basic-style-tail': [], + 'basic-style-extended': [], + 'basic-style-extended-vertical': [], + 'basic-style-1arg': [], + 'basic-style-2arg': [], + 'basic-style-3arg': [], + 'basic-style-var-arg': [], + 'bullet-style': [], + 'invisible': [], + 'box-style': [], + 'box-style-media': [], + 'number-style': [], + 'number-style-var-arg': [], + 'number-style-block': [], + 'number-style-porch': [], + 'number-style-1arg': [], + 'number-style-1strarg': [], + 'compare-style': [], + 'compare-porch-style': [], + 'boolean-style': [], + 'not-style': [], + 'flow-style': [], + 'flow-style-tail': [], + 'flow-style-1arg': [], + 'flow-style-boolean': [], + 'flow-style-while': [], + 'flow-style-else': [], + 'collapsible-top': [], + 'collapsible-top-no-arm': [], + 'collapsible-top-no-label': [], + 'collapsible-top-no-arm-no-label': [], + 'collapsible-bottom': [], + 'portfolio-style-2x2': [], + 'portfolio-style-1x1': [], + 'portfolio-style-2x1': [], + 'portfolio-style-1x2': []} + +from taconstants import EXPANDABLE_STYLE +from tautils import debug_output + +from gettext import gettext as _ + +help_strings = { + 'next': _('displays next palette'), + 'orientation': _("changes the orientation of the palette of blocks") + } + + +class Palette(): + """ a class for defining new palettes """ + + def __init__(self, name, colors=["#00FF00", "#00A000"], position=None): + self._name = name + self._special_name = _(name) + self._colors = colors + self._help = None + + def add_palette(self, position=None): + if self._name is None: + debug_output('You must specify a name for your palette') + return + + # Insert new palette just before the trash + if 'trash' in palette_names: + i = palette_names.index('trash') + else: + i = len(palette_names) + + if position is not None and type(position) is int and position < i: + i = position + + if self._name not in palette_names: + palette_names.insert(i, self._name) + palette_blocks.insert(i, []) + block_colors.insert(i, self._colors) + else: + # debug_output('Palette %s already defined' % (self._name)) + return + + # Special name entry is needed for help hover mechanism + special_names[self._name] = self._special_name + if self._help is not None: + help_strings[self._name] = self._help + else: + help_strings[self._name] = '' + + def set_help(self, help): + self._help = help + + def set_special_name(self, name): + self._special_name = name + + def add_block(self, block_name, style='basic-block', label=None, + special_name=None, default=None, prim_name=None, + help_string=None, value_block=False, content_block=False, + logo_command=None, hidden=False, colors=None): + """ Add a new block to the palette """ + block = Block(block_name) + block.set_style(style) + if label is not None: + block.set_label(label) + if special_name is not None: + block.set_special_name(special_name) + if default is not None: + if default == 'None': + block.set_default(None) + else: + block.set_default(default) + if prim_name is not None: + block.set_prim_name(prim_name) + if logo_command is not None: + block.set_logo_command(logo_command) + if help_string is not None: + block.set_help(help_string) + if colors is not None: + block.set_colors(colors) + block.set_value_block(value_block) + block.set_content_block(content_block) + if not hidden: + block.set_palette(self._name) + block.add_block() + + +def make_palette(palette_name, colors=None, help_string=None): + """ Palette helper function """ + if colors is None: + palette = Palette(palette_name) + else: + palette = Palette(palette_name, colors) + if help_string is not None: + palette.set_help(help_string) + palette.add_palette() + return palette + + +def palette_name_to_index(palette_name): + ''' Find the index associated with palette_name. ''' + if palette_name in palette_names: + return palette_names.index(palette_name) + else: + return None + +def define_logo_function(key, value): + ''' Add a logo function to the table. ''' + logo_functions[key] = value + +class Block(): + """ a class for defining new block primitives """ + + def __init__(self, name): + self._name = name + self._special_name = None + self._palette = None + self._style = None + self._label = None + self._default = None + self._help = None + self._prim_name = None + self._logo_command = None + self._value_block = False + self._content_block = False + self._colors = None + + def add_block(self, position=None): + if self._name is None: + debug_output('You must specify a name for your block') + return + + if self._style is None: + debug_output('You must specify a style for your block') + return + else: + block_styles[self._style].append(self._name) + + if self._label is not None: + block_names[self._name] = self._label + + if self._palette is not None: + i = palette_names.index(self._palette) + if position is not None and type(position) is int and \ + position < len(palette_blocks[i]): + palette_blocks[i].insert(position, self._name) + else: + palette_blocks[i].append(self._name) + if position is not None: + debug_output('Ignoring position (%s)' % (str(position))) + + if self._help is not None: + help_strings[self._name] = self._help + else: + help_strings[self._name] = '' + + if self._value_block: + value_blocks.append(self._name) + + if self._content_block: + content_blocks.append(self._name) + + if self._prim_name is not None: + block_primitives[self._name] = self._prim_name + + if self._logo_command is not None and self._prim_name is not None: + logo_commands[self._prim_name] = self._logo_command + + if self._default is not None: + default_values[self._name] = self._default + + if self._special_name is not None: + special_names[self._name] = self._special_name + + if self._style in EXPANDABLE_STYLE: + expandable_blocks.append(self._name) + + if self._colors is not None: + special_block_colors[self._name] = self._colors + + def set_colors(self, colors=None): + self._colors = colors + + def set_value_block(self, value=True): + self._value_block = value + + def set_content_block(self, value=True): + self._content_block = value + + def set_palette(self, palette): + if not palette in palette_names: + debug_output('Could not find palette %s' % (palette)) + else: + self._palette = palette + + def set_help(self, help): + self._help = help + + def set_special_name(self, name): + self._special_name = name + + def set_label(self, label): + if type(label) == type([]): + self._label = label[:] + else: + self._label = [label] + + def set_default(self, default): + if type(default) == type([]): + self._default = default[:] + else: + self._default = [default] + + def set_style(self, style): + if style not in block_styles: + debug_output('Unknown style: %s' % (style)) + else: + self._style = style + + def set_prim_name(self, prim_name): + self._prim_name = prim_name + + def set_logo_command(self, logo_command): + self._logo_command = logo_command diff --git a/TurtleArt/tasprite_factory.py b/TurtleArt/tasprite_factory.py index fe9166d..c9fc4c4 100755 --- a/TurtleArt/tasprite_factory.py +++ b/TurtleArt/tasprite_factory.py @@ -24,7 +24,6 @@ import pygtk pygtk.require('2.0') import gtk import os -from gettext import gettext as _ from taconstants import HIT_RED, HIT_GREEN, HIDE_WHITE, SHOW_WHITE @@ -80,6 +79,15 @@ class SVG: self._gradient = False self.margins = [0, 0, 0, 0] + """ + The block construction methods typically start on the left side of + a block and proceed clockwise around the block, first constructing a + left-side connector ("outie"), a corner (1, -1), a slot or hat on along + the top, a corner (1, 1), right side connectors ("innie"), possibly a + "porch" to suggest an order of arguments, another corner (-1, 1), + a tab or tail, and the fourth corner (-1, -1). + """ + def basic_block(self): self.reset_min_max() (x, y) = self._calculate_x_y() @@ -334,12 +342,8 @@ class SVG: yoffset = self._radius * 2 + 2 * self._innie_y2 + \ self._innie_spacer + self._stroke_width / 2.0 + \ self._expand_y - if self._porch is True: - yoffset += self._porch_y svg = self._start_boolean(self._stroke_width / 2.0, yoffset) yoffset = -2 * self._innie_y2 - self._innie_spacer - self._stroke_width - if self._porch is True: - yoffset -= self._porch_y svg += self._rline_to(0, yoffset) self._hide_x = self._x + self._radius / 2 + self._stroke_width @@ -353,12 +357,13 @@ class SVG: svg += self._do_innie() svg += self._rline_to(0, self._expand_y) if self._porch is True: - svg += self._do_porch() + svg += self._do_porch(False) else: svg += self._rline_to(0, 2 * self._innie_y2 + self._innie_spacer) svg += self._do_innie() svg += self._rline_to(0, self._radius) svg += self.line_to(xx, self._y) + svg += self._rline_to(-self._expand_x, 0) self._show_y = self._y + self._radius / 2 @@ -369,7 +374,6 @@ class SVG: self._scale) self.margins[1] = int(self._stroke_width * self._scale) self.margins[2] = int(self._stroke_width * self._scale) - self.margins[3] = int(self._stroke_width * self._scale) return self.header() + svg def turtle(self, colors): @@ -591,7 +595,7 @@ class SVG: def set_gradient(self, flag=False, color='#FFFFFF'): self._gradient = flag - self._gradient_color = color + self._gradient_color = color def set_innie(self, innie_array=[False]): self._innie = innie_array diff --git a/TurtleArt/taturtle.py b/TurtleArt/taturtle.py index a7a3205..0ca72e4 100644 --- a/TurtleArt/taturtle.py +++ b/TurtleArt/taturtle.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -#Copyright (c) 2010 Walter Bender +#Copyright (c) 2010,11 Walter Bender #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal @@ -19,14 +19,15 @@ #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. -from taconstants import TURTLE_LAYER +from random import uniform +from math import sin, cos, pi +from gettext import gettext as _ + +from taconstants import TURTLE_LAYER, DEFAULT_TURTLE_COLORS from tasprite_factory import SVG, svg_str_to_pixbuf from tacanvas import wrap100, color_table from sprites import Sprite - -import logging -_logger = logging.getLogger('turtleart-activity') - +from tautils import debug_output SHAPES = 36 @@ -59,6 +60,8 @@ class Turtles: else: if colors == None: Turtle(self, k) + elif type(colors) in [list, tuple]: + Turtle(self, k, colors) else: Turtle(self, k, colors.split(',')) return self.dict[k] @@ -120,11 +123,25 @@ class Turtle: self.pen_gray = 100 self.pen_size = 5 self.pen_state = True + self.label_block = None self._prep_shapes(key, turtles, turtle_colors) + # Choose a random angle from which to attach the turtle label if turtles.sprite_list is not None: self.spr = Sprite(turtles.sprite_list, 0, 0, self.shapes[0]) + angle = uniform(0, pi * 4 / 3.0) # 240 degrees + w = self.shapes[0].get_width() + r = w * 0.67 + # Restrict angle the the sides 30-150; 210-330 + if angle > pi * 2 / 3.0: + angle += pi / 2.0 # + 90 + self.label_xy = [int(r * sin(angle)), + int(r * cos(angle) + w / 2.0)] + else: + angle += pi / 6.0 # + 30 + self.label_xy = [int(r * sin(angle) + w / 2.0), + int(r * cos(angle) + w / 2.0)] else: self.spr = None turtles.add_to_dict(key, self) @@ -149,9 +166,16 @@ class Turtle: self.shapes = generate_turtle_pixbufs(self.colors) else: if turtles is not None: - self.colors = ['#008000', '#00A000'] + self.colors = DEFAULT_TURTLE_COLORS self.shapes = turtles.get_pixbufs() + def set_turtle_colors(self, turtle_colors): + ''' reset the colors of a preloaded turtle ''' + if turtle_colors is not None: + self.colors = turtle_colors[:] + self.shapes = generate_turtle_pixbufs(self.colors) + self.set_heading(self.heading) + def set_shapes(self, shapes): """ Reskin the turtle """ n = len(shapes) @@ -159,7 +183,8 @@ class Turtle: self.shapes = shapes[:] else: if n != 1: - _logger.debug("%d images passed to set_shapes: ignoring" % (n)) + debug_output("%d images passed to set_shapes: ignoring" % (n), + self.tw.running_sugar) images = [shapes[0]] if self.heading == 0: for i in range(3): @@ -213,6 +238,8 @@ class Turtle: """ Hide the turtle. """ if self.spr is not None: self.spr.hide() + if self.label_block is not None: + self.label_block.spr.hide() self.hidden = True def show(self): @@ -222,14 +249,25 @@ class Turtle: self.hidden = False self.move((self.x, self.y)) self.set_heading(self.heading) + if self.label_block is not None: + self.label_block.spr.move((self.x + self.label_xy[0], + self.y + self.label_xy[1])) + self.label_block.spr.set_layer(TURTLE_LAYER + 1) def move(self, pos): """ Move the turtle. """ self.x, self.y = int(pos[0]), int(pos[1]) if not self.hidden and self.spr is not None: self.spr.move(pos) + if self.label_block is not None: + self.label_block.spr.move((pos[0] + self.label_xy[0], + pos[1] + self.label_xy[1])) return(self.x, self.y) + def get_name(self): + ''' return turtle name (key) ''' + return self.name + def get_xy(self): """ Return the turtle's x, y coordinates. """ return(self.x, self.y) diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py index c66b322..0c6fe64 100644 --- a/TurtleArt/tautils.py +++ b/TurtleArt/tautils.py @@ -1,5 +1,5 @@ #Copyright (c) 2007-8, Playful Invention Company. -#Copyright (c) 2008-10, Walter Bender +#Copyright (c) 2008-11, Walter Bender #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,8 @@ import gtk import pickle import subprocess import dbus +import os.path +from gettext import gettext as _ try: OLD_SUGAR_SYSTEM = False @@ -37,17 +39,31 @@ except (ImportError, AttributeError): from simplejson import dump as jdump except: OLD_SUGAR_SYSTEM = True - -from taconstants import STRING_OR_NUMBER_ARGS, HIDE_LAYER, CONTENT_ARGS, \ - COLLAPSIBLE, BLOCK_LAYER, CONTENT_BLOCKS, HIT_HIDE, \ - HIT_SHOW, XO1, XO15, UNKNOWN from StringIO import StringIO -import os.path -from gettext import gettext as _ + +from taconstants import HIDE_LAYER, COLLAPSIBLE, BLOCK_LAYER, HIT_HIDE, \ + HIT_SHOW, XO1, XO15, UNKNOWN + import logging _logger = logging.getLogger('turtleart-activity') +def debug_output(message_string, running_sugar=False): + """ unified debugging output """ + if running_sugar: + _logger.debug(message_string) + else: + print(message_string) + + +def error_output(message_string, running_sugar=False): + """ unified debugging output """ + if running_sugar: + _logger.error(message_string) + else: + print(message_string) + + class pythonerror(Exception): def __init__(self, value): @@ -105,15 +121,22 @@ def json_load(text): if OLD_SUGAR_SYSTEM is True: _listdata = json.read(text) else: - # strip out leading and trailing whitespace, nulls, and newlines + # Strip out leading and trailing whitespace, nulls, and newlines clean_text = text.lstrip() clean_text = clean_text.replace('\12', '') clean_text = clean_text.replace('\00', '') - _io = StringIO(clean_text.rstrip()) + clean_text = clean_text.rstrip() + # Look for missing ']'s + left_count = clean_text.count('[') + right_count = clean_text.count(']') + while left_count > right_count: + clean_text += ']' + right_count = clean_text.count(']') + _io = StringIO(clean_text) try: _listdata = jload(_io) except ValueError: - # assume that text is ascii list + # Assume that text is ascii list _listdata = text.split() for i, value in enumerate(_listdata): _listdata[i] = convert(value, float) @@ -147,7 +170,7 @@ def json_dump(data): def get_load_name(suffix, load_save_folder): """ Open a load file dialog. """ - _dialog = gtk.FileChooserDialog("Load...", None, + _dialog = gtk.FileChooserDialog(_('Load...'), None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) _dialog.set_default_response(gtk.RESPONSE_OK) @@ -156,7 +179,7 @@ def get_load_name(suffix, load_save_folder): def get_save_name(suffix, load_save_folder, save_file_name): """ Open a save file dialog. """ - _dialog = gtk.FileChooserDialog("Save...", None, + _dialog = gtk.FileChooserDialog(_('Save...'), None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) _dialog.set_default_response(gtk.RESPONSE_OK) @@ -285,24 +308,36 @@ def get_path(activity, subpath): "org.laptop.TurtleArtActivity", subpath)) -def image_to_base64(pixbuf, activity): - """ Convert an image to base64 """ - _file_name = os.path.join(get_path(activity, 'instance'), 'imagetmp.png') +def image_to_base64(pixbuf, path_name): + """ Convert an image to base64-encoded data """ + file_name = os.path.join(path_name, 'imagetmp.png') if pixbuf != None: - pixbuf.save(_file_name, "png") - _base64 = os.path.join(get_path(activity, 'instance'), 'base64tmp') - _cmd = "base64 <" + _file_name + " >" + _base64 - subprocess.check_call(_cmd, shell=True) - _file_handle = open(_base64, 'r') - _data = _file_handle.read() - _file_handle.close() - return _data + pixbuf.save(file_name, "png") + base64 = os.path.join(path_name, 'base64tmp') + cmd = "base64 <" + file_name + " >" + base64 + subprocess.check_call(cmd, shell=True) + file_handle = open(base64, 'r') + data = file_handle.read() + file_handle.close() + return data + + +def base64_to_image(data, path_name): + """ Convert base64-encoded data to an image """ + base64 = os.path.join(path_name, 'base64tmp') + file_handle = open(base64, 'w') + file_handle.write(data) + file_handle.close() + file_name = os.path.join(path_name, 'imagetmp.png') + cmd = "base64 -d <" + base64 + ">" + file_name + subprocess.check_call(cmd, shell=True) + return file_name def movie_media_type(name): """ Is it movie media? """ return name.lower().endswith(('.ogv', '.vob', '.mp4', '.wmv', '.mov', - '.mpeg', 'ogg')) + '.mpeg', '.ogg', '.webm')) def audio_media_type(name): @@ -318,7 +353,7 @@ def image_media_type(name): def text_media_type(name): """ Is it text media? """ - return name.lower().endswith(('.txt', '.py', '.lg', '.rtf', '.ta')) + return name.lower().endswith(('.txt', '.py', '.lg', '.rtf')) def round_int(num): @@ -326,7 +361,6 @@ def round_int(num): try: float(num) except TypeError: - _logger.debug("error trying to convert %s to number" % (str(num))) raise pythonerror("#syntaxerror") if int(float(num)) == num: @@ -613,39 +647,6 @@ def neg_arg(value): return False -def dock_dx_dy(block1, dock1n, block2, dock2n): - """ Find the distance between the dock points of two blocks. """ - _dock1 = block1.docks[dock1n] - _dock2 = block2.docks[dock2n] - _d1type, _d1dir, _d1x, _d1y = _dock1[0:4] - _d2type, _d2dir, _d2x, _d2y = _dock2[0:4] - if block1 == block2: - return (100, 100) - if _d1dir == _d2dir: - return (100, 100) - if (_d2type is not 'number') or (dock2n is not 0): - if block1.connections is not None and \ - dock1n < len(block1.connections) and \ - block1.connections[dock1n] is not None: - return (100, 100) - if block2.connections is not None and \ - dock2n < len(block2.connections) and \ - block2.connections[dock2n] is not None: - return (100, 100) - if _d1type != _d2type: - if block1.name in STRING_OR_NUMBER_ARGS: - if _d2type == 'number' or _d2type == 'string': - pass - elif block1.name in CONTENT_ARGS: - if _d2type in CONTENT_BLOCKS: - pass - else: - return (100, 100) - (_b1x, _b1y) = block1.spr.get_xy() - (_b2x, _b2y) = block2.spr.get_xy() - return ((_b1x + _d1x) - (_b2x + _d2x), (_b1y + _d1y) - (_b2y + _d2y)) - - def journal_check(blk1, blk2, dock1, dock2): """ Dock blocks only if arg is Journal block """ if blk1 == None or blk2 == None: @@ -796,22 +797,29 @@ def find_blk_below(blk, name): def get_hardware(): """ Determine whether we are using XO 1.0, 1.5, or "unknown" hardware """ - bus = dbus.SystemBus() - - comp_obj = bus.get_object('org.freedesktop.Hal', - '/org/freedesktop/Hal/devices/computer') - dev = dbus.Interface(comp_obj, 'org.freedesktop.Hal.Device') - if dev.PropertyExists('system.hardware.vendor') and \ - dev.PropertyExists('system.hardware.version'): - if dev.GetProperty('system.hardware.vendor') == 'OLPC': - if dev.GetProperty('system.hardware.version') == '1.5': - return XO15 - else: - return XO1 + product = _get_dmi('product_name') + if product is None: + if os.path.exists('/etc/olpc-release') or \ + os.path.exists('/sys/power/olpc-pm'): + return XO1 else: return UNKNOWN - elif os.path.exists('/etc/olpc-release') or \ - os.path.exists('/sys/power/olpc-pm'): + if product != 'XO': + return UNKNOWN + version = _get_dmi('product_version') + if version == '1': return XO1 + elif version == '1.5': + return XO15 else: return UNKNOWN + + +def _get_dmi(node): + ''' The desktop management interface should be a reliable source + for product and version information. ''' + path = os.path.join('/sys/class/dmi/id', node) + try: + return open(path).readline().strip() + except: + return None diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py index ad830d9..64a32fd 100644 --- a/TurtleArt/tawindow.py +++ b/TurtleArt/tawindow.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- #Copyright (c) 2007, Playful Invention Company -#Copyright (c) 2008-10, Walter Bender -#Copyright (c) 2009-10 Raúl Gutiérrez Segalés -#Copyright (C) 2010 Emiliano Pastorino +#Copyright (c) 2008-11, Walter Bender +#Copyright (c) 2009-11 Raúl Gutiérrez Segalés #Copyright (c) 2011 Collabora Ltd. #Permission is hereby granted, free of charge, to any person obtaining a copy @@ -27,7 +26,15 @@ import pygtk pygtk.require('2.0') import gtk import gobject -import gst +from gettext import gettext as _ + +try: + import gst + GST_AVAILABLE = True +except ImportError: + # Turtle Art should not fail if gst is not available + GST_AVAILABLE = False + import os import os.path import dbus @@ -36,7 +43,6 @@ from math import atan2, pi DEGTOR = 2 * pi / 360 import locale -from gettext import gettext as _ try: from sugar.datastore import datastore @@ -45,84 +51,74 @@ except ImportError: pass from taconstants import HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE, \ - PALETTE_NAMES, TITLEXY, MEDIA_SHAPES, STATUS_SHAPES, \ - OVERLAY_SHAPES, TOOLBAR_SHAPES, TAB_LAYER, RETURN, \ - OVERLAY_LAYER, CATEGORY_LAYER, BLOCKS_WITH_SKIN, \ - ICON_SIZE, PALETTES, PALETTE_SCALE, BOX_STYLE_MEDIA, \ - PALETTE_WIDTH, MACROS, TOP_LAYER, BLOCK_LAYER, \ - CONTENT_BLOCKS, DEFAULTS, SPECIAL_NAMES, \ - HELP_STRINGS, CURSOR, EXPANDABLE, COLLAPSIBLE, \ - DEAD_DICTS, DEAD_KEYS, TEMPLATES, PYTHON_SKIN, \ - PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK, OLD_NAMES, \ - BOOLEAN_STYLE, BLOCK_NAMES, DEFAULT_TURTLE, \ - TURTLE_LAYER, EXPANDABLE_BLOCKS, COMPARE_STYLE, \ - BOOLEAN_STYLE, EXPANDABLE_ARGS, NUMBER_STYLE, \ - NUMBER_STYLE_PORCH, NUMBER_STYLE_BLOCK, \ - NUMBER_STYLE_VAR_ARG, CONSTANTS, XO1, XO15, UNKNOWN, \ - BASIC_STYLE_VAR_ARG -from talogo import LogoCode, stop_logo + MEDIA_SHAPES, STATUS_SHAPES, OVERLAY_SHAPES, STRING_OR_NUMBER_ARGS, \ + TOOLBAR_SHAPES, TAB_LAYER, RETURN, OVERLAY_LAYER, CATEGORY_LAYER, \ + BLOCKS_WITH_SKIN, ICON_SIZE, PALETTE_SCALE, PALETTE_WIDTH, \ + MACROS, TOP_LAYER, BLOCK_LAYER, OLD_NAMES, DEFAULT_TURTLE, TURTLE_LAYER, \ + CURSOR, EXPANDABLE, COLLAPSIBLE, DEAD_DICTS, DEAD_KEYS, \ + TEMPLATES, PYTHON_SKIN, PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK, \ + EXPANDABLE_ARGS, XO1, XO15, UNKNOWN, TITLEXY, CONTENT_ARGS, CONSTANTS +from tapalette import palette_names, palette_blocks, expandable_blocks, \ + block_names, content_blocks, default_values, special_names, block_styles, \ + help_strings +from talogo import LogoCode from tacanvas import TurtleGraphics from tablock import Blocks, Block from taturtle import Turtles, Turtle from tautils import magnitude, get_load_name, get_save_name, data_from_file, \ - data_to_file, round_int, get_id, get_pixbuf_from_journal, \ - movie_media_type, audio_media_type, image_media_type, \ - save_picture, save_svg, calc_image_size, get_path, \ - reset_stack_arm, grow_stack_arm, find_sandwich_top, \ - find_sandwich_bottom, restore_stack, collapse_stack, \ - collapsed, collapsible, hide_button_hit, show_button_hit, \ - arithmetic_check, xy, find_block_to_run, find_top_block, \ - find_start_stack, find_group, find_blk_below, \ - dock_dx_dy, data_to_string, journal_check, chooser, \ - get_hardware + data_to_file, round_int, get_id, get_pixbuf_from_journal, \ + movie_media_type, audio_media_type, image_media_type, save_picture, \ + save_svg, calc_image_size, get_path, reset_stack_arm, grow_stack_arm, \ + find_sandwich_top, find_sandwich_bottom, restore_stack, collapse_stack, \ + collapsed, collapsible, hide_button_hit, show_button_hit, chooser, \ + arithmetic_check, xy, find_block_to_run, find_top_block, journal_check, \ + find_group, find_blk_below, data_to_string, find_start_stack, \ + get_hardware, debug_output, error_output from tasprite_factory import SVG, svg_str_to_pixbuf, svg_from_file -from tagplay import stop_media from sprites import Sprites, Sprite -from audiograb import AudioGrab_Unknown, AudioGrab_XO1, AudioGrab_XO15 -from rfidutils import strhex2bin, strbin2dec, find_device from dbus.mainloop.glib import DBusGMainLoop -HAL_SERVICE = 'org.freedesktop.Hal' -HAL_MGR_PATH = '/org/freedesktop/Hal/Manager' -HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager' -HAL_DEV_IFACE = 'org.freedesktop.Hal.Device' -REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\ - 'a-z,A-Z,0-9,_]*serial_usb_[0-9]' - -import logging -_logger = logging.getLogger('turtleart-activity') +if GST_AVAILABLE: + from tagplay import stop_media class TurtleArtWindow(): """ TurtleArt Window class abstraction """ timeout_tag = [0] + _PLUGIN_SUBPATH = 'plugins' def __init__(self, win, path, parent=None, mycolors=None, mynick=None): self._loaded_project = '' - self.win = None self._sharing = False self.parent = parent - self.send_event = None # method to send events over the network + self.send_event = None # method to send events over the network + self.gst_available = GST_AVAILABLE if type(win) == gtk.DrawingArea: self.interactive_mode = True self.window = win self.window.set_flags(gtk.CAN_FOCUS) + self.window.show_all() if self.parent is not None: self.parent.show_all() self.running_sugar = True else: - self.window.show_all() self.running_sugar = False self.area = self.window.window - self.gc = self.area.new_gc() + if self.area is not None: + self.gc = self.area.new_gc() + else: + # We lose... + debug_output('drawable area is None... punting') + exit() self._setup_events() elif type(win) == gtk.gdk.Pixmap: self.interactive_mode = False self.window = win self.running_sugar = False - self.gc = self.window.new_gc() + if self.window is not None: + self.gc = self.window.new_gc() else: - _logger.debug("bad win type %s" % (type(win))) + debug_output("bad win type %s" % (type(win)), False) if self.running_sugar: self.activity = parent @@ -147,7 +143,10 @@ class TurtleArtWindow(): self.mouse_x = 0 self.mouse_y = 0 - locale.setlocale(locale.LC_NUMERIC, '') + try: + locale.setlocale(locale.LC_NUMERIC, '') + except locale.Error: + debug_output('unsupported locale', self.running_sugar) self.decimal_point = locale.localeconv()['decimal_point'] if self.decimal_point == '' or self.decimal_point is None: self.decimal_point = '.' @@ -155,7 +154,6 @@ class TurtleArtWindow(): self.orientation = HORIZONTAL_PALETTE self.hw = get_hardware() - _logger.debug('running on %s hardware' % (self.hw)) if self.hw in (XO1, XO15): self.lead = 1.0 self.scale = 0.67 @@ -163,14 +161,14 @@ class TurtleArtWindow(): self.color_mode = '565' else: self.color_mode = '888' - if self.running_sugar and not self.activity.new_sugar_system: + if self.running_sugar and not self.activity.has_toolbarbox: self.orientation = VERTICAL_PALETTE else: self.lead = 1.0 self.scale = 1.0 self.color_mode = '888' # TODO: Read visual mode from gtk image - self.block_scale = BLOCK_SCALE + self.block_scale = BLOCK_SCALE[3] self.trash_scale = 0.5 self.myblock = {} self.python_code = None @@ -187,6 +185,7 @@ class TurtleArtWindow(): self.media_shapes = {} self.cartesian = False self.polar = False + self.metric = False self.overlay_shapes = {} self.toolbar_shapes = {} self.toolbar_offset = 0 @@ -196,7 +195,6 @@ class TurtleArtWindow(): self.palette_sprs = [] self.palettes = [] self.palette_button = [] - self.trash_index = PALETTE_NAMES.index('trash') self.trash_stack = [] self.selected_palette = None self.previous_palette = None @@ -219,10 +217,10 @@ class TurtleArtWindow(): self.sprite_list = None self.turtles = Turtles(self.sprite_list) - if mynick is None: + if self.nick is None: self.default_turtle_name = DEFAULT_TURTLE else: - self.default_turtle_name = mynick + self.default_turtle_name = self.nick if mycolors is None: Turtle(self.turtles, self.default_turtle_name) else: @@ -252,99 +250,100 @@ class TurtleArtWindow(): CONSTANTS['width'] = int(self.canvas.width / self.coord_scale) CONSTANTS['height'] = int(self.canvas.height / self.coord_scale) - if self.interactive_mode: - self._setup_misc() - self._show_toolbar_palette(0, False) - - # setup sound/sensor grab - if self.hw in [XO1, XO15]: - PALETTES[PALETTE_NAMES.index('sensor')].append('resistance') - PALETTES[PALETTE_NAMES.index('sensor')].append('voltage') - self.audio_started = False - - self.camera_available = False - v4l2src = gst.element_factory_make('v4l2src') - if v4l2src.props.device_name is not None: - PALETTES[PALETTE_NAMES.index('sensor')].append('readcamera') - PALETTES[PALETTE_NAMES.index('sensor')].append('luminance') - PALETTES[PALETTE_NAMES.index('sensor')].append('camera') - self.camera_available = True + self._icon_paths = [os.path.join(self.path, 'icons')] + self._plugins = [] + self._init_plugins() self.lc = LogoCode(self) - self.saved_pictures = [] - - self.block_operation = '' - """ - The following code will initialize a USB RFID reader. Please note that - in order to make this initialization function work, it is necessary to - set the permission for the ttyUSB device to 0666. You can do this by - adding a rule to /etc/udev/rules.d - - As root (using sudo or su), copy the following text into a new file in - /etc/udev/rules.d/94-ttyUSB-rules - - KERNEL=="ttyUSB[0-9]",MODE="0666" - - You only have to do this once. - """ - - self.rfid_connected = False - self.rfid_device = find_device() - self.rfid_idn = '' - - if self.rfid_device is not None: - _logger.info("RFID device found") - self.rfid_connected = self.rfid_device.do_connect() - if self.rfid_connected: - self.rfid_device.connect("tag-read", self._tag_read_cb) - self.rfid_device.connect("disconnected", self._disconnected_cb) - - loop = DBusGMainLoop() - bus = dbus.SystemBus(mainloop=loop) - hmgr_iface = dbus.Interface(bus.get_object(HAL_SERVICE, - HAL_MGR_PATH), HAL_MGR_IFACE) + from tabasics import Palettes + p = Palettes(self) + self._setup_plugins() - hmgr_iface.connect_to_signal('DeviceAdded', self._device_added_cb) + if self.interactive_mode: + self._setup_misc() + self.show_toolbar_palette(0, False) - PALETTES[PALETTE_NAMES.index('sensor')].append('rfid') + self.saved_pictures = [] + self.block_operation = '' - def _device_added_cb(self, path): - """ - Called from hal connection when a new device is plugged. - """ - if not self.rfid_connected: - self.rfid_device = find_device() - _logger.debug("DEVICE_ADDED: %s"%self.rfid_device) - if self.rfid_device is not None: - _logger.debug("DEVICE_ADDED: RFID device is not None!") - self.rfid_connected = self._device.do_connect() - if self.rfid_connected: - _logger.debug("DEVICE_ADDED: Connected!") - self.rfid_device.connect("tag-read", self._tag_read_cb) - self.rfid_device.connect("disconnected", self._disconnected_cb) - - def _disconnected_cb(self, device, text): - """ - Called when the device is disconnected. - """ - self.rfid_connected = False - self.rfid_device = None + def _get_plugin_home(self): + """ Look in the execution directory """ + path = os.path.join(self.path, self._PLUGIN_SUBPATH) + if os.path.exists(path): + return path + else: + return None - def _tag_read_cb(self, device, tagid): - """ - Callback for "tag-read" signal. Receives the read tag id. - """ - idbin = strhex2bin(tagid) - self.rfid_idn = strbin2dec(idbin[26:64]) - while self.rfid_idn.__len__() < 9: - self.rfid_idn = '0' + self.rfid_idn - print tagid, idbin, self.rfid_idn - - def new_buffer(self, buf): - """ Append a new buffer to the ringbuffer """ - self.lc.ringbuffer.append(buf) - return True + def _get_plugins_from_plugins_dir(self, path): + """ Look for plugin files in plugin dir. """ + plugin_files = [] + if path is not None: + candidates = os.listdir(path) + for dirname in candidates: + if os.path.exists( + os.path.join(path, dirname, dirname + '.py')): + plugin_files.append(dirname) + return plugin_files + + def _init_plugins(self): + """ Try importing plugin files from the plugin dir. """ + for plugin_dir in self._get_plugins_from_plugins_dir( + self._get_plugin_home()): + plugin_class = plugin_dir.capitalize() + f = "def f(self): from plugins.%s.%s import %s; return %s(self)" \ + % (plugin_dir, plugin_dir, plugin_class, plugin_class) + plugins = {} + try: + exec f in globals(), plugins + self._plugins.append(plugins.values()[0](self)) + # debug_output('successfully importing %s' % (plugin_class)) + except ImportError: + debug_output('failed to import %s' % (plugin_class)) + + # Add the icon dir for each plugin to the icon_theme search path + for plugin_dir in self._get_plugins_from_plugins_dir( + self._get_plugin_home()): + self._add_plugin_icon_dir(os.path.join(self._get_plugin_home(), + plugin_dir)) + + def _add_plugin_icon_dir(self, dirname): + ''' If there is an icon subdir, add it to the search path. ''' + icon_theme = gtk.icon_theme_get_default() + icon_path = os.path.join(dirname, 'icons') + if os.path.exists(icon_path): + icon_theme.append_search_path(icon_path) + self._icon_paths.append(icon_path) + + def _setup_plugins(self): + """ Initial setup -- call just once. """ + for plugin in self._plugins: + plugin.setup() + + def _start_plugins(self): + """ Start is called everytime we execute blocks. """ + for plugin in self._plugins: + plugin.start() + + def _stop_plugins(self): + """ Stop is called whenever we stop execution. """ + for plugin in self._plugins: + plugin.stop() + + def background_plugins(self): + """ Background is called when we are pushed to the background. """ + for plugin in self._plugins: + plugin.goto_background() + + def foreground_plugins(self): + """ Foreground is called when we are return from the background. """ + for plugin in self._plugins: + plugin.return_to_foreground() + + def _quit_plugins(self): + """ Quit is called upon program exit. """ + for plugin in self._plugins: + plugin.quit() def _setup_events(self): """ Register the events we listen to. """ @@ -361,41 +360,41 @@ class TurtleArtWindow(): def _setup_misc(self): """ Misc. sprites for status, overlays, etc. """ # media blocks get positioned into other blocks - for _name in MEDIA_SHAPES: - if _name[0:7] == 'journal' and not self.running_sugar: - file_name = 'file' + _name[7:] + for name in MEDIA_SHAPES: + if name[0:7] == 'journal' and not self.running_sugar: + filename = 'file' + name[7:] else: - file_name = _name - self.media_shapes[_name] = svg_str_to_pixbuf(svg_from_file( - "%s/images/%s.svg" % (self.path, file_name))) + filename = name + self.media_shapes[name] = svg_str_to_pixbuf(svg_from_file( + "%s/images/%s.svg" % (self.path, filename))) - for i, _name in enumerate(STATUS_SHAPES): - self.status_shapes[_name] = svg_str_to_pixbuf(svg_from_file( - "%s/images/%s.svg" % (self.path, _name))) + for i, name in enumerate(STATUS_SHAPES): + self.status_shapes[name] = svg_str_to_pixbuf(svg_from_file( + "%s/images/%s.svg" % (self.path, name))) self.status_spr = Sprite(self.sprite_list, 0, self.height - 200, self.status_shapes['status']) self.status_spr.hide() self.status_spr.type = 'status' - for _name in OVERLAY_SHAPES: - self.overlay_shapes[_name] = Sprite(self.sprite_list, + for name in OVERLAY_SHAPES: + self.overlay_shapes[name] = Sprite(self.sprite_list, int(self.width / 2 - 600), int(self.height / 2 - 450), svg_str_to_pixbuf( - svg_from_file("%s/images/%s.svg" % (self.path, _name)))) - self.overlay_shapes[_name].hide() - self.overlay_shapes[_name].type = 'overlay' + svg_from_file("%s/images/%s.svg" % (self.path, name)))) + self.overlay_shapes[name].hide() + self.overlay_shapes[name].type = 'overlay' if not self.running_sugar: offset = self.width - 55 * len(TOOLBAR_SHAPES) - for i, _name in enumerate(TOOLBAR_SHAPES): - self.toolbar_shapes[_name] = Sprite(self.sprite_list, - i * 55 + offset, 0, - svg_str_to_pixbuf( - svg_from_file("%s/icons/%s.svg" % (self.path, _name)))) - self.toolbar_shapes[_name].set_layer(TAB_LAYER) - self.toolbar_shapes[_name].name = _name - self.toolbar_shapes[_name].type = 'toolbar' + for i, name in enumerate(TOOLBAR_SHAPES): + self.toolbar_shapes[name] = Sprite( + self.sprite_list, i * 55 + offset, 0, + svg_str_to_pixbuf(svg_from_file(os.path.join( + self.path, 'icons', '%s.svg' % (name))))) + self.toolbar_shapes[name].set_layer(TAB_LAYER) + self.toolbar_shapes[name].name = name + self.toolbar_shapes[name].type = 'toolbar' self.toolbar_shapes['stopiton'].hide() def set_sharing(self, shared): @@ -420,35 +419,17 @@ class TurtleArtWindow(): self.lc.prim_clear() self.display_coordinates() - def _start_audiograb(self): - """ Start grabbing audio if there is an audio block in use """ - if len(self.block_list.get_similar_blocks('block', - ['volume', 'sound', 'pitch', 'resistance', 'voltage'])) > 0: - if self.audio_started: - self.audiograb.resume_grabbing() - else: - if self.hw == XO15: - self.audiograb = AudioGrab_XO15(self.new_buffer, self) - elif self.hw == XO1: - self.audiograb = AudioGrab_XO1(self.new_buffer, self) - else: - self.audiograb = AudioGrab_Unknown(self.new_buffer, self) - self.audiograb.start_grabbing() - self.audio_started = True - def run_button(self, time): """ Run turtle! """ if self.running_sugar: self.activity.recenter() - if self.interactive_mode: - self._start_audiograb() - # Look for a 'start' block for blk in self.just_blocks(): if find_start_stack(blk): self.step_time = time - _logger.debug("running stack starting from %s" % (blk.name)) + debug_output("running stack starting from %s" % (blk.name), + self.running_sugar) self._run_stack(blk) return @@ -456,15 +437,15 @@ class TurtleArtWindow(): for blk in self.just_blocks(): if find_block_to_run(blk): self.step_time = time - _logger.debug("running stack starting from %s" % (blk.name)) + debug_output("running stack starting from %s" % (blk.name), + self.running_sugar) self._run_stack(blk) return def stop_button(self): """ Stop button """ - stop_logo(self) - if self.audio_started: - self.audiograb.pause_grabbing() + self.lc.stop_logo() + self._stop_plugins() def set_userdefined(self, blk=None): """ Change icon for user-defined blocks after loading Python code. """ @@ -505,28 +486,38 @@ class TurtleArtWindow(): self.overlay_shapes['polar'].hide() self.polar = False + def set_metric(self, flag): + """ Turn on/off metric coordinates """ + if flag: + self.overlay_shapes['metric'].set_layer(OVERLAY_LAYER) + self.metric = True + else: + self.overlay_shapes['metric'].hide() + self.metric = False + def update_overlay_position(self, widget, event): """ Reposition the overlays when window size changes """ self.width = event.width self.height = event.height - for _name in OVERLAY_SHAPES: - shape = self.overlay_shapes[_name] + for name in OVERLAY_SHAPES: + shape = self.overlay_shapes[name] showing = False if shape in shape._sprites.list: shape.hide() showing = True - self.overlay_shapes[_name] = Sprite(self.sprite_list, + self.overlay_shapes[name] = Sprite(self.sprite_list, int(self.width / 2 - 600), int(self.height / 2 - 450), svg_str_to_pixbuf( - svg_from_file("%s/images/%s.svg" % (self.path, _name)))) + svg_from_file("%s/images/%s.svg" % (self.path, name)))) if showing: - self.overlay_shapes[_name].set_layer(OVERLAY_LAYER) + self.overlay_shapes[name].set_layer(OVERLAY_LAYER) else: - self.overlay_shapes[_name].hide() - self.overlay_shapes[_name].type = 'overlay' + self.overlay_shapes[name].hide() + self.overlay_shapes[name].type = 'overlay' self.cartesian = False self.polar = False + self.metric = False self.canvas.width = self.width self.canvas.height = self.height self.canvas.move_turtle() @@ -543,9 +534,9 @@ class TurtleArtWindow(): if blk.status != 'collapsed': blk.spr.set_layer(BLOCK_LAYER) self.show_palette() - if self.activity is not None and self.activity.new_sugar_system: + if self.activity is not None and self.activity.has_toolbarbox: self.activity.palette_buttons[0].set_icon( - PALETTE_NAMES[0] + 'on') + palette_names[0] + 'on') self.hide = False if self.running_sugar: self.activity.recenter() @@ -568,10 +559,10 @@ class TurtleArtWindow(): def show_palette(self, n=0): """ Show palette """ - self._show_toolbar_palette(n) + self.show_toolbar_palette(n) self.palette_button[self.orientation].set_layer(TAB_LAYER) self.palette_button[2].set_layer(TAB_LAYER) - if self.activity is None or not self.activity.new_sugar_system: + if self.activity is None or not self.activity.has_toolbarbox: self.toolbar_spr.set_layer(CATEGORY_LAYER) self.palette = True @@ -580,7 +571,7 @@ class TurtleArtWindow(): self._hide_toolbar_palette() self.palette_button[self.orientation].hide() self.palette_button[2].hide() - if self.activity is None or not self.activity.new_sugar_system: + if self.activity is None or not self.activity.has_toolbarbox: self.toolbar_spr.hide() self.palette = False @@ -591,7 +582,7 @@ class TurtleArtWindow(): self.hide = False self.hideshow_button() if self.running_sugar: - self.activity.do_hide() + self.activity.do_hide_blocks() def showblocks(self): """ Callback from 'show blocks' block """ @@ -600,7 +591,7 @@ class TurtleArtWindow(): self.hide = True self.hideshow_button() if self.running_sugar: - self.activity.do_show() + self.activity.do_show_blocks() def resize_blocks(self, blocks=None): """ Resize blocks or if blocks is None, all of the blocks """ @@ -635,65 +626,21 @@ class TurtleArtWindow(): if blk.name in BLOCKS_WITH_SKIN: self._resize_skin(blk) - def _show_toolbar_palette(self, n, init_only=False): + def show_toolbar_palette(self, n, init_only=False, regenerate=False): """ Show the toolbar palettes, creating them on init_only """ - if (self.activity is None or not self.activity.new_sugar_system) and\ + # If we are running the 0.86+ toolbar, the selectors are already + # created, as toolbar buttons. Otherwise, we need to create them. + if (self.activity is None or not self.activity.has_toolbarbox) and \ self.selectors == []: - # Create the selectors - svg = SVG() - x, y = 50, 0 - for i, name in enumerate(PALETTE_NAMES): - a = svg_str_to_pixbuf(svg_from_file("%s/icons/%soff.svg" % ( - self.path, name))) - b = svg_str_to_pixbuf(svg_from_file("%s/icons/%son.svg" % ( - self.path, name))) - self.selector_shapes.append([a, b]) - self.selectors.append(Sprite(self.sprite_list, x, y, a)) - self.selectors[i].type = 'selector' - self.selectors[i].name = name - self.selectors[i].set_layer(TAB_LAYER) - w = self.selectors[i].get_dimensions()[0] - x += int(w) - - # Create the toolbar background - self.toolbar_offset = ICON_SIZE - self.toolbar_spr = Sprite(self.sprite_list, 0, 0, - svg_str_to_pixbuf(svg.toolbar(self.width, ICON_SIZE))) - self.toolbar_spr.type = 'toolbar' - self.toolbar_spr.set_layer(CATEGORY_LAYER) + # First, create the selector buttons + self._create_the_selectors() + # Create the empty palettes that we'll then populate with prototypes. if self.palette_sprs == []: - # Create the empty palettes - if len(self.palettes) == 0: - for i in range(len(PALETTES)): - self.palettes.append([]) - - # Create empty palette backgrounds - for i in PALETTE_NAMES: - self.palette_sprs.append([None, None]) - - # Create the palette orientation button - self.palette_button.append(Sprite(self.sprite_list, 0, - self.toolbar_offset, svg_str_to_pixbuf(svg_from_file( - "%s/images/palettehorizontal.svg" % (self.path))))) - self.palette_button.append(Sprite(self.sprite_list, 0, - self.toolbar_offset, svg_str_to_pixbuf(svg_from_file( - "%s/images/palettevertical.svg" % (self.path))))) - self.palette_button[0].name = _('orientation') - self.palette_button[1].name = _('orientation') - self.palette_button[0].type = 'palette' - self.palette_button[1].type = 'palette' - self.palette_button[self.orientation].set_layer(TAB_LAYER) - self.palette_button[1 - self.orientation].hide() - - # Create the palette next button - self.palette_button.append(Sprite(self.sprite_list, 16, - self.toolbar_offset, svg_str_to_pixbuf(svg_from_file( - "%s/images/palettenext.svg" % (self.path))))) - self.palette_button[2].name = _('next') - self.palette_button[2].type = 'palette' - self.palette_button[2].set_layer(TAB_LAYER) + self._create_the_empty_palettes() + # At initialization of the program, we don't actually populate + # the palettes. if init_only: return @@ -703,27 +650,121 @@ class TurtleArtWindow(): self.selected_palette = n self.previous_palette = self.selected_palette - if self.activity is None or not self.activity.new_sugar_system: + # Make sure all of the selectors are visible. (We don't need to do + # this for 0.86+ toolbars since the selectors are toolbar buttons.) + if self.activity is None or not self.activity.has_toolbarbox: self.selected_selector = self.selectors[n] - # Make sure all of the selectors are visible. self.selectors[n].set_shape(self.selector_shapes[n][1]) - for i in range(len(PALETTES)): + for i in range(len(palette_blocks)): self.selectors[i].set_layer(TAB_LAYER) # Show the palette with the current orientation. if self.palette_sprs[n][self.orientation] is not None: self.palette_sprs[n][self.orientation].set_layer(CATEGORY_LAYER) + # Create 'proto' blocks for each palette entry + self._create_proto_blocks(n, regenerate=regenerate) + + self._layout_palette(n, regenerate=regenerate) + for blk in self.palettes[n]: + blk.spr.set_layer(TAB_LAYER) + if n == palette_names.index('trash'): + for blk in self.trash_stack: + for gblk in find_group(blk): + if gblk.status != 'collapsed': + gblk.spr.set_layer(TAB_LAYER) + + def _create_the_selectors(self): + ''' Create the palette selector buttons. ''' + svg = SVG() + x, y = 50, 0 # positioned at the left, top + for i, name in enumerate(palette_names): + for path in self._icon_paths: + if os.path.exists(os.path.join(path, '%soff.svg' % (name))): + icon_pathname = os.path.join(path, '%soff.svg' % (name)) + break + if icon_pathname is not None: + off_shape = svg_str_to_pixbuf(svg_from_file(icon_pathname)) + else: + off_shape = svg_str_to_pixbuf(svg_from_file(os.path.join( + self._icon_paths[0], 'extrasoff.svg'))) + error_output('Unable to open %soff.svg' % (name), + self.running_sugar) + for path in self._icon_paths: + if os.path.exists(os.path.join(path, '%son.svg' % (name))): + icon_pathname = os.path.join(path, '%son.svg' % (name)) + break + if icon_pathname is not None: + on_shape = svg_str_to_pixbuf(svg_from_file(icon_pathname)) + else: + on_shape = svg_str_to_pixbuf(svg_from_file(os.path.join( + self._icon_paths[0], 'extrason.svg'))) + error_output('Unable to open %son.svg' % (name), + self.running_sugar) + + self.selector_shapes.append([off_shape, on_shape]) + self.selectors.append(Sprite(self.sprite_list, x, y, off_shape)) + self.selectors[i].type = 'selector' + self.selectors[i].name = name + self.selectors[i].set_layer(TAB_LAYER) + w = self.selectors[i].get_dimensions()[0] + x += int(w) # running from left to right + + # Create the toolbar background for the selectors + self.toolbar_offset = ICON_SIZE + self.toolbar_spr = Sprite(self.sprite_list, 0, 0, + svg_str_to_pixbuf(svg.toolbar(self.width, ICON_SIZE))) + self.toolbar_spr.type = 'toolbar' + self.toolbar_spr.set_layer(CATEGORY_LAYER) + + def _create_the_empty_palettes(self): + ''' Create the empty palettes to be populated by prototype blocks. ''' + if len(self.palettes) == 0: + for i in range(len(palette_blocks)): + self.palettes.append([]) + + # Create empty palette backgrounds + for i in palette_names: + self.palette_sprs.append([None, None]) + + # Create the palette orientation button + self.palette_button.append(Sprite(self.sprite_list, 0, + self.toolbar_offset, svg_str_to_pixbuf(svg_from_file( + "%s/images/palettehorizontal.svg" % (self.path))))) + self.palette_button.append(Sprite(self.sprite_list, 0, + self.toolbar_offset, svg_str_to_pixbuf(svg_from_file( + "%s/images/palettevertical.svg" % (self.path))))) + self.palette_button[0].name = _('orientation') + self.palette_button[1].name = _('orientation') + self.palette_button[0].type = 'palette' + self.palette_button[1].type = 'palette' + self.palette_button[self.orientation].set_layer(TAB_LAYER) + self.palette_button[1 - self.orientation].hide() + + # Create the palette next button + self.palette_button.append(Sprite(self.sprite_list, 16, + self.toolbar_offset, svg_str_to_pixbuf(svg_from_file( + "%s/images/palettenext.svg" % (self.path))))) + self.palette_button[2].name = _('next') + self.palette_button[2].type = 'palette' + self.palette_button[2].set_layer(TAB_LAYER) + + def _create_proto_blocks(self, n, regenerate=False): + ''' Create the protoblocks that will populate a palette. ''' + if regenerate: + for blk in self.palettes[n]: + blk.type = 'trash' + self.palettes[n] = [] + if self.palettes[n] == []: - # Create 'proto' blocks for each palette entry - for i, name in enumerate(PALETTES[n]): + for i, name in enumerate(palette_blocks[n]): self.palettes[n].append(Block(self.block_list, self.sprite_list, name, 0, 0, 'proto', [], PALETTE_SCALE)) self.palettes[n][i].spr.set_layer(TAB_LAYER) self.palettes[n][i].unhighlight() # Some proto blocks get a skin. - if name in BOX_STYLE_MEDIA: + if name in block_styles['box-style-media']: self._proto_skin(name + 'small', n, i) elif name[:8] == 'template': self._proto_skin(name[8:], n, i) @@ -732,25 +773,16 @@ class TurtleArtWindow(): elif name in PYTHON_SKIN: self._proto_skin('pythonsmall', n, i) - self._layout_palette(n) - for blk in self.palettes[n]: - blk.spr.set_layer(TAB_LAYER) - if n == self.trash_index: - for blk in self.trash_stack: - for gblk in find_group(blk): - if gblk.status != 'collapsed': - gblk.spr.set_layer(TAB_LAYER) - def _hide_toolbar_palette(self): """ Hide the toolbar palettes """ self._hide_previous_palette() - if self.activity is None or not self.activity.new_sugar_system: + if self.activity is None or not self.activity.has_toolbarbox: # Hide the selectors - for i in range(len(PALETTES)): + for i in range(len(palette_blocks)): self.selectors[i].hide() elif self.selected_palette is not None: self.activity.palette_buttons[self.selected_palette].set_icon( - PALETTE_NAMES[self.selected_palette] + 'off') + palette_names[self.selected_palette] + 'off') self.selected_palette = None self.previous_palette = None @@ -758,119 +790,117 @@ class TurtleArtWindow(): """ Hide just the previously viewed toolbar palette """ # Hide previous palette if self.previous_palette is not None: - for i in range(len(PALETTES[self.previous_palette])): - self.palettes[self.previous_palette][i].spr.hide() - self.palette_sprs[self.previous_palette][ - self.orientation].hide() - if self.activity is None or not self.activity.new_sugar_system: + for proto in self.palettes[self.previous_palette]: + proto.spr.hide() + self.palette_sprs[self.previous_palette][self.orientation].hide() + if self.activity is None or not self.activity.has_toolbarbox: self.selectors[self.previous_palette].set_shape( self.selector_shapes[self.previous_palette][0]) elif self.previous_palette is not None and \ self.previous_palette != self.selected_palette: self.activity.palette_buttons[self.previous_palette].set_icon( - PALETTE_NAMES[self.previous_palette] + 'off') - if self.previous_palette == self.trash_index: + palette_names[self.previous_palette] + 'off') + if self.previous_palette == palette_names.index('trash'): for blk in self.trash_stack: for gblk in find_group(blk): gblk.spr.hide() def _horizontal_layout(self, x, y, blocks): """ Position prototypes in a horizontal palette. """ - _max_w = 0 + max_w = 0 for blk in blocks: - _w, _h = self._width_and_height(blk) - if y + _h > PALETTE_HEIGHT + self.toolbar_offset: - x += int(_max_w + 3) + w, h = self._width_and_height(blk) + if y + h > PALETTE_HEIGHT + self.toolbar_offset: + x += int(max_w + 3) y = self.toolbar_offset + 3 - _max_w = 0 - (_bx, _by) = blk.spr.get_xy() - _dx = x - _bx - _dy = y - _by + max_w = 0 + (bx, by) = blk.spr.get_xy() + dx = x - bx + dy = y - by for g in find_group(blk): - g.spr.move_relative((int(_dx), int(_dy))) - y += int(_h + 3) - if _w > _max_w: - _max_w = _w - return x, y, _max_w + g.spr.move_relative((int(dx), int(dy))) + y += int(h + 3) + if w > max_w: + max_w = w + return x, y, max_w def _vertical_layout(self, x, y, blocks): """ Position prototypes in a vertical palette. """ - _row = [] - _row_w = 0 - _max_h = 0 - for _b in blocks: - _w, _h = self._width_and_height(_b) - if x + _w > PALETTE_WIDTH: + row = [] + row_w = 0 + max_h = 0 + for b in blocks: + w, h = self._width_and_height(b) + if x + w > PALETTE_WIDTH: # Recenter row. - _dx = int((PALETTE_WIDTH - _row_w) / 2) - for _r in _row: - for _g in find_group(_r): - _g.spr.move_relative((_dx, 0)) - _row = [] - _row_w = 0 + dx = int((PALETTE_WIDTH - row_w) / 2) + for r in row: + for g in find_group(r): + g.spr.move_relative((dx, 0)) + row = [] + row_w = 0 x = 4 - y += int(_max_h + 3) - _max_h = 0 - _row.append(_b) - _row_w += (4 + _w) - (_bx, _by) = _b.spr.get_xy() - _dx = int(x - _bx) - _dy = int(y - _by) - for _g in find_group(_b): - _g.spr.move_relative((_dx, _dy)) - x += int(_w + 4) - if _h > _max_h: - _max_h = _h + y += int(max_h + 3) + max_h = 0 + row.append(b) + row_w += (4 + w) + (bx, by) = b.spr.get_xy() + dx = int(x - bx) + dy = int(y - by) + for g in find_group(b): + g.spr.move_relative((dx, dy)) + x += int(w + 4) + if h > max_h: + max_h = h # Recenter last row. - _dx = int((PALETTE_WIDTH - _row_w) / 2) - for _r in _row: - for _g in find_group(_r): - _g.spr.move_relative((_dx, 0)) - return x, y, _max_h + dx = int((PALETTE_WIDTH - row_w) / 2) + for r in row: + for g in find_group(r): + g.spr.move_relative((dx, 0)) + return x, y, max_h - def _layout_palette(self, n): + def _layout_palette(self, n, regenerate=False): """ Layout prototypes in a palette. """ if n is not None: if self.orientation == HORIZONTAL_PALETTE: - _x, _y = 20, self.toolbar_offset + 5 - _x, _y, _max = self._horizontal_layout(_x, _y, - self.palettes[n]) - if n == self.trash_index: - _x, _y, _max = self._horizontal_layout(_x + _max, _y, - self.trash_stack) - _w = _x + _max + 25 - if self.palette_sprs[n][self.orientation] is None: - svg = SVG() - self.palette_sprs[n][self.orientation] = Sprite( - self.sprite_list, 0, self.toolbar_offset, - svg_str_to_pixbuf(svg.palette(_w, PALETTE_HEIGHT))) - self.palette_sprs[n][self.orientation].type = 'category' - if n == PALETTE_NAMES.index('trash'): - svg = SVG() - self.palette_sprs[n][self.orientation].set_shape( - svg_str_to_pixbuf(svg.palette(_w, PALETTE_HEIGHT))) - self.palette_button[2].move((_w - 20, self.toolbar_offset)) + x, y = 20, self.toolbar_offset + 5 + x, y, max_w = self._horizontal_layout(x, y, self.palettes[n]) + if n == palette_names.index('trash'): + x, y, max_w = self._horizontal_layout(x + max_w, y, + self.trash_stack) + w = x + max_w + 25 + self._make_palette_spr(n, 0, self.toolbar_offset, + w, PALETTE_HEIGHT, regenerate) + self.palette_button[2].move((w - 20, self.toolbar_offset)) else: - _x, _y = 5, self.toolbar_offset + 15 - _x, _y, _max = self._vertical_layout(_x, _y, self.palettes[n]) - if n == PALETTE_NAMES.index('trash'): - _x, _y, _max = self._vertical_layout(_x, _y + _max, - self.trash_stack) - _h = _y + _max + 25 - self.toolbar_offset - if self.palette_sprs[n][self.orientation] is None: - svg = SVG() - self.palette_sprs[n][self.orientation] = \ - Sprite(self.sprite_list, 0, self.toolbar_offset, - svg_str_to_pixbuf(svg.palette(PALETTE_WIDTH, _h))) - self.palette_sprs[n][self.orientation].type = 'category' - if n == PALETTE_NAMES.index('trash'): - svg = SVG() - self.palette_sprs[n][self.orientation].set_shape( - svg_str_to_pixbuf(svg.palette(PALETTE_WIDTH, _h))) + x, y = 5, self.toolbar_offset + 15 + x, y, max_h = self._vertical_layout(x, y, self.palettes[n]) + if n == palette_names.index('trash'): + x, y, max_h = self._vertical_layout(x, y + max_h, + self.trash_stack) + h = y + max_h + 25 - self.toolbar_offset + self._make_palette_spr(n, 0, self.toolbar_offset, + PALETTE_WIDTH, h, regenerate) self.palette_button[2].move((PALETTE_WIDTH - 20, self.toolbar_offset)) self.palette_sprs[n][self.orientation].set_layer(CATEGORY_LAYER) + def _make_palette_spr(self, n, x, y, w, h, regenerate=False): + ''' Make the background for the palette. ''' + if regenerate and not self.palette_sprs[n][self.orientation] is None: + self.palette_sprs[n][self.orientation].hide() + self.palette_sprs[n][self.orientation] = None + if self.palette_sprs[n][self.orientation] is None: + svg = SVG() + self.palette_sprs[n][self.orientation] = \ + Sprite(self.sprite_list, x, y, svg_str_to_pixbuf( + svg.palette(w, h))) + self.palette_sprs[n][self.orientation].type = 'category' + if n == palette_names.index('trash'): + svg = SVG() + self.palette_sprs[n][self.orientation].set_shape( + svg_str_to_pixbuf(svg.palette(w, h))) + def _buttonpress_cb(self, win, event): """ Button press """ self.window.grab_focus() @@ -916,6 +946,10 @@ class TurtleArtWindow(): self._restore_latest_from_trash() elif blk.name == 'empty': self._empty_trash() + elif blk.name == 'trashall': + for b in self.just_blocks(): + if b.type != 'trash': + self._put_in_trash(find_top_block(b)) elif blk.name in MACROS: self._new_macro(blk.name, x + 20, y + 20) else: @@ -925,7 +959,7 @@ class TurtleArtWindow(): 'block', blk.name)) > 0: self.showlabel('dupstack') return True - # You cannot miz and match sensor blocks + # You cannot mix and match sensor blocks elif blk.name in ['sound', 'volume', 'pitch']: if len(self.block_list.get_similar_blocks( 'block', ['resistance', 'voltage'])) > 0: @@ -954,6 +988,9 @@ class TurtleArtWindow(): # Next, look for a turtle t = self.turtles.spr_to_turtle(spr) if t is not None: + # If turtle is shared, ignore click + if self.remote_turtle(t.get_name()): + return True self.selected_turtle = t self.canvas.set_turtle(self.turtles.get_turtle_key(t)) self._turtle_pressed(x, y) @@ -972,18 +1009,18 @@ class TurtleArtWindow(): elif spr.type == 'palette': if spr.name == _('next'): i = self.selected_palette + 1 - if i == len(PALETTE_NAMES): + if i == len(palette_names): i = 0 if self.activity is None or \ - not self.activity.new_sugar_system: + not self.activity.has_toolbarbox: self._select_category(self.selectors[i]) else: if self.selected_palette is not None: self.activity.palette_buttons[ self.selected_palette].set_icon( - PALETTE_NAMES[self.selected_palette] + 'off') + palette_names[self.selected_palette] + 'off') self.activity.palette_buttons[i].set_icon( - PALETTE_NAMES[i] + 'on') + palette_names[i] + 'on') self.show_palette(i) else: self.orientation = 1 - self.orientation @@ -1018,7 +1055,7 @@ class TurtleArtWindow(): self.lc.trace = 0 self.run_button(0) elif spr.name == 'run-slowoff': - self.lc.trace = 0 + self.lc.trace = 1 self.run_button(3) elif spr.name == 'debugoff': self.lc.trace = 1 @@ -1060,8 +1097,8 @@ class TurtleArtWindow(): if gblk.name in BLOCKS_WITH_SKIN: self._resize_skin(gblk) - # self.show_palette(self.trash_index) - if self.selected_palette != self.trash_index: + # self.show_palette(palette_names.index('trash')) + if self.selected_palette != palette_names.index('trash'): for gblk in group: gblk.spr.hide() @@ -1113,8 +1150,8 @@ class TurtleArtWindow(): def _in_the_trash(self, x, y): """ Is x, y over the trash can? """ """ - if self.selected_palette == self.trash_index and \ - self.palette_sprs[self.trash_index][self.orientation].hit((x, y)): + if self.selected_palette == palette_names.index('trash') and \ + self.palette_sprs[palette_names.index('trash')][self.orientation].hit((x, y)): return True """ if self.selected_palette is not None and \ @@ -1146,14 +1183,18 @@ class TurtleArtWindow(): self.selected_blk.unhighlight() self.selected_blk = None - def _new_block(self, name, x, y): + def _new_block(self, name, x, y, defaults=None): """ Make a new block. """ - if name in CONTENT_BLOCKS: - newblk = Block(self.block_list, self.sprite_list, name, x - 20, - y - 20, 'block', DEFAULTS[name], self.block_scale) + x_pos = x - 20 + y_pos = y - 20 + if name in content_blocks: + if defaults == None: + defaults = default_values[name] + newblk = Block(self.block_list, self.sprite_list, name, x_pos, + y_pos, 'block', defaults, self.block_scale) else: - newblk = Block(self.block_list, self.sprite_list, name, x - 20, - y - 20, 'block', [], self.block_scale) + newblk = Block(self.block_list, self.sprite_list, name, x_pos, + y_pos, 'block', [], self.block_scale) # Add a 'skin' to some blocks if name in PYTHON_SKIN: @@ -1161,15 +1202,17 @@ class TurtleArtWindow(): self._block_skin('pythonon', newblk) else: self._block_skin('pythonoff', newblk) - elif name in BOX_STYLE_MEDIA: + elif name in block_styles['box-style-media']: self._block_skin(name + 'off', newblk) newspr = newblk.spr newspr.set_layer(TOP_LAYER) self.drag_pos = 20, 20 newblk.connections = [None] * len(newblk.docks) - if newblk.name in DEFAULTS: - for i, argvalue in enumerate(DEFAULTS[newblk.name]): + if newblk.name in default_values: + if defaults == None: + defaults = default_values[newblk.name] + for i, argvalue in enumerate(defaults): # skip the first dock position since it is always a connector dock = newblk.docks[i + 1] argname = dock[0] @@ -1186,7 +1229,7 @@ class TurtleArtWindow(): argname = argvalue (sx, sy) = newspr.get_xy() if argname is not None: - if argname in CONTENT_BLOCKS: + if argname in content_blocks: argblk = Block(self.block_list, self.sprite_list, argname, 0, 0, 'block', [argvalue], self.block_scale) @@ -1218,8 +1261,7 @@ class TurtleArtWindow(): def process_data(self, block_data, offset=0): """ Process block_data (from a macro, a file, or the clipboard). """ - if offset != 0: - _logger.debug("offset is %d" % (offset)) + # Create the blocks (or turtle). blocks = [] for blk in block_data: @@ -1238,7 +1280,8 @@ class TurtleArtWindow(): else: cons.append(blocks[c]) else: - _logger.debug("connection error %s" % (str(block_data[i]))) + debug_output("connection error %s" % (str(block_data[i])), + self.running_sugar) cons.append(None) elif blocks[i].connections == 'check': # Convert old-style boolean and arithmetic blocks @@ -1249,7 +1292,7 @@ class TurtleArtWindow(): else: cons.append(blocks[c]) # If the boolean op was connected, readjust the plumbing. - if blocks[i].name in BOOLEAN_STYLE: + if blocks[i].name in block_styles['boolean-style']: if block_data[i][4][0] is not None: c = block_data[i][4][0] cons[0] = blocks[block_data[c][4][0]] @@ -1262,7 +1305,8 @@ class TurtleArtWindow(): blocks[c].connections[3] = None else: # Connection was to a block we haven't seen yet. - _logger.debug("Warning: dock to the future") + debug_output("Warning: dock to the future", + self.running_sugar) else: if block_data[i][4][0] is not None: c = block_data[i][4][0] @@ -1276,10 +1320,12 @@ class TurtleArtWindow(): blocks[c].connections[1] = None else: # Connection was to a block we haven't seen yet. - _logger.debug("Warning: dock to the future") + debug_output("Warning: dock to the future", + self.running_sugar) else: - _logger.debug("Warning: unknown connection state %s" % \ - (str(blocks[i].connections))) + debug_output("Warning: unknown connection state %s" % \ + (str(blocks[i].connections)), + self.running_sugar) blocks[i].connections = cons[:] # Block sizes and shapes may have changed. @@ -1310,7 +1356,7 @@ class TurtleArtWindow(): return (sx, sy) = blk.spr.get_xy() for i, c in enumerate(blk.connections): - if i > 0 and c is not None: + if i > 0 and c is not None and i < len(blk.docks): bdock = blk.docks[i] for j in range(len(c.docks)): if j < len(c.connections) and c.connections[j] == blk: @@ -1322,8 +1368,8 @@ class TurtleArtWindow(): def _turtle_pressed(self, x, y): (tx, ty) = self.selected_turtle.get_xy() - w = self.active_turtle.spr.rect.width / 2 - h = self.active_turtle.spr.rect.height / 2 + w = self.selected_turtle.spr.rect.width / 2 + h = self.selected_turtle.spr.rect.height / 2 dx = x - tx - w dy = y - ty - h # if x, y is near the edge, rotate @@ -1349,17 +1395,29 @@ class TurtleArtWindow(): if self.selected_turtle is not None: dtype, dragx, dragy = self.drag_turtle (sx, sy) = self.selected_turtle.get_xy() + # self.canvas.set_turtle(self.selected_turtle.get_name()) if dtype == 'move': - dx = x - dragx - sx - dy = y - dragy - sy + dx = x - dragx - sx + self.selected_turtle.spr.rect.width / 2 + dy = y - dragy - sy + self.selected_turtle.spr.rect.width / 2 self.selected_turtle.spr.set_layer(TOP_LAYER) - self.selected_turtle.move((sx + dx, sy + dy)) + tx, ty = self.canvas.screen_to_turtle_coordinates(sx + dx, + sy + dy) + if self.canvas.pendown: + self.canvas.setpen(False) + self.canvas.setxy(tx, ty) + self.canvas.setpen(True) + else: + self.canvas.setxy(tx, ty) else: - dx = x - sx - self.active_turtle.spr.rect.width / 2 - dy = y - sy - self.active_turtle.spr.rect.height / 2 + dx = x - sx - self.selected_turtle.spr.rect.width / 2 + dy = y - sy - self.selected_turtle.spr.rect.height / 2 self.canvas.seth(int(dragx + atan2(dy, dx) / DEGTOR + 5) / \ 10 * 10) self.lc.update_label_value('heading', self.canvas.heading) + if self.sharing(): # share turtle rotation + self.send_event("r|%s" % ( + data_to_string([self.selected_turtle.get_name(), + round_int(self.canvas.heading)]))) # If we are hoving, show popup help. elif self.drag_group is None: @@ -1468,27 +1526,27 @@ class TurtleArtWindow(): def _do_show_popup(self, block_name): """ Fetch the help text and display it. """ - if block_name in SPECIAL_NAMES: - block_name_s = SPECIAL_NAMES[block_name] - elif block_name in BLOCK_NAMES: - block_name_s = BLOCK_NAMES[block_name][0] + if block_name in special_names: + special_block_name = special_names[block_name] + elif block_name in block_names: + special_block_name = block_names[block_name][0] elif block_name in TOOLBAR_SHAPES: - block_name_s = '' + special_block_name = '' else: - block_name_s = _(block_name) - if block_name in HELP_STRINGS: - if block_name_s == '': - label = HELP_STRINGS[block_name] + special_block_name = _(block_name) + if block_name in help_strings: + if special_block_name == '': + label = help_strings[block_name] else: - label = block_name_s + ": " + HELP_STRINGS[block_name] + label = special_block_name + ": " + help_strings[block_name] else: - label = block_name_s + label = special_block_name if self.running_sugar: self.activity.hover_help_label.set_text(label) self.activity.hover_help_label.show() else: if self.interactive_mode: - self.win.set_title(_("Turtle Art") + " — " + label) + self.parent.set_title(_("Turtle Art") + " — " + label) return 0 def _buttonrelease_cb(self, win, event): @@ -1514,14 +1572,15 @@ class TurtleArtWindow(): else: self.selected_turtle.hide() self.turtles.remove_from_dict(k) + self.active_turtle = None else: self._move_turtle(tx - self.canvas.width / 2 + \ self.active_turtle.spr.rect.width / 2, self.canvas.height / 2 - ty - \ self.active_turtle.spr.rect.height / 2) self.selected_turtle = None - self.active_turtle = self.turtles.get_turtle( - self.default_turtle_name) + if self.active_turtle is None: + self.canvas.set_turtle(self.default_turtle_name) return # If we don't have a group of blocks, then there is nothing to do. @@ -1557,6 +1616,30 @@ class TurtleArtWindow(): if self.block_operation == 'click': self._click_block(x, y) + def remote_turtle(self, name): + ''' Is this a remote turtle? ''' + if name == self.nick: + return False + if hasattr(self, 'remote_turtle_dictionary') and \ + name in self.remote_turtle_dictionary: + return True + return False + + def label_remote_turtle(self, name, colors=['#A0A0A0', '#C0C0C0']): + ''' Add a label to remote turtles ''' + turtle = self.turtles.get_turtle(name) + if turtle is not None: + turtle.label_block = Block(self.block_list, + self.sprite_list, 'turtle-label', 0, 0, + 'label', [], 1.0 / self.scale, + colors) + turtle.label_block.spr.set_label_attributes(6.0 / self.scale) + if len(name) > 6: + turtle.label_block.spr.set_label(name[0:4] + '…') + else: + turtle.label_block.spr.set_label(name) + turtle.show() + def _move_turtle(self, x, y): """ Move the selected turtle to (x, y). """ (cx, cy) = self.canvas.canvas.get_xy() @@ -1570,8 +1653,6 @@ class TurtleArtWindow(): self.canvas.xcor / self.coord_scale) self.lc.update_label_value('ycor', self.canvas.ycor / self.coord_scale) - if len(self.lc.value_blocks['see']) > 0: - self.lc.see() def _click_block(self, x, y): """ Click block: lots of special cases to handle... """ @@ -1584,7 +1665,9 @@ class TurtleArtWindow(): self.saved_string = blk.spr.labels[0] blk.spr.labels[0] += CURSOR - elif blk.name in BOX_STYLE_MEDIA and blk.name != 'camera': + elif blk.name in block_styles['box-style-media'] and \ + blk.name != 'camera': + # TODO: isolate reference to camera self._import_from_journal(self.selected_blk) if blk.name == 'journal' and self.running_sugar: self._load_description_block(blk) @@ -1597,7 +1680,8 @@ class TurtleArtWindow(): dx = 20 blk.expand_in_x(dx) else: - dx = 0 + self._run_stack(blk) + return for gblk in group: if gblk != blk: gblk.spr.move_relative((dx * blk.scale, 0)) @@ -1610,13 +1694,14 @@ class TurtleArtWindow(): dy = 20 blk.expand_in_y(dy) else: - dy = 0 + self._run_stack(blk) + return for gblk in group: if gblk != blk: gblk.spr.move_relative((0, dy * blk.scale)) grow_stack_arm(find_sandwich_top(blk)) - elif blk.name in EXPANDABLE_BLOCKS: + elif blk.name in expandable_blocks: # Connection may be lost during expansion, so store it... blk0 = blk.connections[0] if blk0 is not None: @@ -1628,11 +1713,10 @@ class TurtleArtWindow(): dy = 20 blk.expand_in_y(dy) else: - self._start_audiograb() self._run_stack(blk) return - if blk.name in BOOLEAN_STYLE: + if blk.name in block_styles['boolean-style']: self._expand_boolean(blk, blk.connections[1], dy) else: self._expand_expandable(blk, blk.connections[1], dy) @@ -1664,17 +1748,20 @@ class TurtleArtWindow(): dy = blk.add_arg() blk.primitive = 'userdefined2' blk.name = 'userdefined2args' + self._resize_skin(blk) elif blk.name == 'userdefined2args': dy = blk.add_arg(False) blk.primitive = 'userdefined3' blk.name = 'userdefined3args' + self._resize_skin(blk) else: dy = blk.add_arg() for gblk in group: gblk.spr.move_relative((0, dy)) blk.connections.append(blk.connections[n - 1]) argname = blk.docks[n - 1][0] - argvalue = DEFAULTS[blk.name][len(DEFAULTS[blk.name]) - 1] + argvalue = default_values[blk.name][len( + default_values[blk.name]) - 1] argblk = Block(self.block_list, self.sprite_list, argname, 0, 0, 'block', [argvalue], self.block_scale) argdock = argblk.docks[0] @@ -1685,28 +1772,26 @@ class TurtleArtWindow(): argblk.spr.set_layer(TOP_LAYER) argblk.connections = [blk, None] blk.connections[n - 1] = argblk - if blk.name in NUMBER_STYLE_VAR_ARG: + if blk.name in block_styles['number-style-var-arg']: self._cascade_expandable(blk) grow_stack_arm(find_sandwich_top(blk)) elif blk.name in PYTHON_SKIN: self._import_py() else: - self._start_audiograb() self._run_stack(blk) - elif blk.name in ['sandwichtop_no_arm_no_label', + elif blk.name in ['sandwichtop_no_arm_no_label', 'sandwichtop_no_arm']: restore_stack(blk) elif blk.name in COLLAPSIBLE: top = find_sandwich_top(blk) if collapsed(blk): - restore_stack(top) # depreciated (bottom block is invisible) + restore_stack(top) # deprecated (bottom block is invisible) elif top is not None: collapse_stack(top) else: - self._start_audiograb() self._run_stack(blk) def _expand_boolean(self, blk, blk2, dy): @@ -1726,19 +1811,27 @@ class TurtleArtWindow(): for gblk in find_group(blk): if gblk not in group: gblk.spr.move_relative((0, dy * blk.scale)) - if blk.name in COMPARE_STYLE: + if blk.name in block_styles['compare-style']: for gblk in find_group(blk): gblk.spr.move_relative((0, -dy * blk.scale)) + def _number_style(self, name): + if name in block_styles['number-style']: + return True + if name in block_styles['number-style-porch']: + return True + if name in block_styles['number-style-block']: + return True + if name in block_styles['number-style-var-arg']: + return True + return False + def _cascade_expandable(self, blk): """ If expanding/shrinking a block, cascade. """ - while blk.name in NUMBER_STYLE or \ - blk.name in NUMBER_STYLE_PORCH or \ - blk.name in NUMBER_STYLE_BLOCK or \ - blk.name in NUMBER_STYLE_VAR_ARG: + while self._number_style(blk.name): if blk.connections[0] is None: break - if blk.connections[0].name in EXPANDABLE_BLOCKS: + if blk.connections[0].name in expandable_blocks: if blk.connections[0].connections.index(blk) != 1: break blk = blk.connections[0] @@ -1755,7 +1848,7 @@ class TurtleArtWindow(): for gblk in find_group(blk): if gblk not in group: gblk.spr.move_relative((0, dy * blk.scale)) - if blk.name in COMPARE_STYLE: + if blk.name in block_styles['compare-style']: for gblk in find_group(blk): gblk.spr.move_relative((0, -dy * blk.scale)) else: @@ -1790,7 +1883,8 @@ class TurtleArtWindow(): """ Run a stack of blocks. """ if blk is None: return - self.lc.ag = None + self.lc.find_value_blocks() # Are there blocks to update? + self._start_plugins() # Let the plugins know we are running. top = find_top_block(blk) self.lc.run_blocks(top, self.just_blocks(), True) if self.interactive_mode: @@ -1848,17 +1942,18 @@ class TurtleArtWindow(): selected_block.connections[best_selected_block_dockn] = \ best_destination - if best_destination.name in BOOLEAN_STYLE: + if best_destination.name in block_styles['boolean-style']: if best_destination_dockn == 2 and \ - selected_block.name in COMPARE_STYLE: + selected_block.name in block_styles['compare-style']: dy = selected_block.ey - best_destination.ey best_destination.expand_in_y(dy) self._expand_boolean(best_destination, selected_block, dy) - elif best_destination.name in EXPANDABLE_BLOCKS and \ + elif best_destination.name in expandable_blocks and \ best_destination_dockn == 1: dy = 0 - if (selected_block.name in EXPANDABLE_BLOCKS or - selected_block.name in NUMBER_STYLE_VAR_ARG): + if (selected_block.name in expandable_blocks or + selected_block.name in block_styles[ + 'number-style-var-arg']): if selected_block.name == 'myfunc2arg': dy = 40 + selected_block.ey - best_destination.ey elif selected_block.name == 'myfunc3arg': @@ -1877,6 +1972,8 @@ class TurtleArtWindow(): def _disconnect(self, blk): """ Disconnect block from stack above it. """ + if blk is None: + return if blk.connections[0] is None: return if collapsed(blk): @@ -1886,12 +1983,12 @@ class TurtleArtWindow(): c = blk2.connections.index(blk) blk2.connections[c] = None - if blk2.name in BOOLEAN_STYLE: + if blk2.name in block_styles['boolean-style']: if c == 2 and blk2.ey > 0: dy = -blk2.ey blk2.expand_in_y(dy) self._expand_boolean(blk2, blk, dy) - elif blk2.name in EXPANDABLE_BLOCKS and c == 1: + elif blk2.name in expandable_blocks and c == 1: if blk2.ey > 0: dy = blk2.reset_y() if dy != 0: @@ -1976,7 +2073,6 @@ class TurtleArtWindow(): """ Keyboard """ keyname = gtk.gdk.keyval_name(event.keyval) keyunicode = gtk.gdk.keyval_to_unicode(event.keyval) - if event.get_state() & gtk.gdk.MOD1_MASK: alt_mask = True alt_flag = 'T' @@ -1996,9 +2092,9 @@ class TurtleArtWindow(): if keyname == "p": self.hideshow_button() elif keyname == 'q': - if self.audio_started: - self.audiograb.stop_grabbing() - stop_media(self.lc) + self._plugins_quit() + if self.gst_available: + stop_media(self.lc) exit() elif keyname == 'g': self._align_to_grid() @@ -2077,7 +2173,8 @@ class TurtleArtWindow(): oldleft, oldright = \ self.selected_blk.spr.labels[0].split(CURSOR) except ValueError: - _logger.debug("[%s]" % self.selected_blk.spr.labels[0]) + debug_output("[%s]" % self.selected_blk.spr.labels[0], + self.running_sugar) oldleft = self.selected_blk.spr.labels[0] oldright = '' else: @@ -2286,7 +2383,8 @@ class TurtleArtWindow(): f.close() id = fname except IOError: - _logger.error("Unable to read Python code from %s" % (fname)) + error_output("Unable to read Python code from %s" % (fname), + self.running_sugar) return id # if we are running Sugar, copy the file into the Journal @@ -2302,7 +2400,8 @@ class TurtleArtWindow(): datastore.write(dsobject) id = dsobject.object_id except IOError: - _logger.error("Error copying %s to the datastore" % (fname)) + error_output("Error copying %s to the datastore" % (fname), + self.running_sugar) id = None dsobject.destroy() @@ -2327,12 +2426,14 @@ class TurtleArtWindow(): """ Read the Python code from the Journal object """ self.python_code = None try: - _logger.debug("opening %s " % dsobject.file_path) + debug_output("opening %s " % dsobject.file_path, + self.running_sugar) file_handle = open(dsobject.file_path, "r") self.python_code = file_handle.read() file_handle.close() except IOError: - _logger.debug("couldn't open %s" % dsobject.file_path) + debug_output("couldn't open %s" % dsobject.file_path, + self.running_sugar) if blk is None: blk = self.selected_blk if blk is not None: @@ -2356,7 +2457,7 @@ class TurtleArtWindow(): def new_project(self): """ Start a new project """ - stop_logo(self) + self.lc.stop_logo() self._loaded_project = "" # Put current project in the trash. while len(self.just_blocks()) > 0: @@ -2377,8 +2478,9 @@ class TurtleArtWindow(): saved_project_data = f.read() f.close() except: - _logger.debug("problem loading saved project data from %s" % \ - (self._loaded_project)) + debug_output("problem loading saved project data from %s" % \ + (self._loaded_project), + self.running_sugar) saved_project_data = "" current_project_data = data_to_string(self.assemble_data_to_save()) @@ -2409,14 +2511,13 @@ class TurtleArtWindow(): if blk[1] == 'turtle': self.load_turtle(blk) return True - elif type(blk[1]) == list and blk[1][0] == 'turtle': - self.load_turtle(blk, blk[1][1]) + elif type(blk[1]) in [list, tuple] and blk[1][0] == 'turtle': + if blk[1][1] == DEFAULT_TURTLE: + if self.nick is not None and self.nick is not '': + self.load_turtle(blk, self.nick) + else: + self.load_turtle(blk, blk[1][1]) return True - elif type(blk[1]) == tuple: - _btype, _key = blk[1] - if _btype == 'turtle': - self.load_turtle(blk, _key) - return True return False def load_turtle(self, blk, key=1): @@ -2438,7 +2539,7 @@ class TurtleArtWindow(): btype, value = btype elif type(btype) == list: btype, value = btype[0], btype[1] - if btype in CONTENT_BLOCKS or btype in COLLAPSIBLE: + if btype in content_blocks or btype in COLLAPSIBLE: if btype == 'number': try: values = [round_int(value)] @@ -2467,16 +2568,17 @@ class TurtleArtWindow(): 'block', values, self.block_scale) # Some blocks get transformed. - if btype in BASIC_STYLE_VAR_ARG and value is not None: + if btype in block_styles['basic-style-var-arg'] and value is not None: # Is there code stored in this userdefined block? - if value > 0: # catch depreciated format (#2501) + if value > 0: # catch deprecated format (#2501) self.python_code = None if self.running_sugar: try: dsobject = datastore.get(value) - except: # Should be IOError, but dbus error is raised + except: # Should be IOError, but dbus error is raised dsobject = None - _logger.debug("couldn't get dsobject %s" % value) + debug_output("couldn't get dsobject %s" % value, + self.running_sugar) if dsobject is not None: self.load_python_code_from_journal(dsobject, blk) else: @@ -2493,9 +2595,9 @@ class TurtleArtWindow(): elif btype == 'start': # block size is saved in start block if value is not None: self.block_scale = value - elif btype in EXPANDABLE or btype in EXPANDABLE_BLOCKS or \ + elif btype in EXPANDABLE or btype in expandable_blocks or \ btype in EXPANDABLE_ARGS or btype == 'nop': - if btype == 'vspace' or btype in EXPANDABLE_BLOCKS: + if btype == 'vspace' or btype in expandable_blocks: if value is not None: blk.expand_in_y(value) elif btype == 'hspace' or btype == 'identity2': @@ -2514,7 +2616,8 @@ class TurtleArtWindow(): self._block_skin('pythonon', blk) else: self._block_skin('pythonoff', blk) - elif btype in BOX_STYLE_MEDIA and blk.spr is not None: + elif btype in block_styles['box-style-media'] and blk.spr is not None: + # TODO: isolate reference to camera if len(blk.values) == 0 or blk.values[0] == 'None' or \ blk.values[0] is None or btype == 'camera': self._block_skin(btype + 'off', blk) @@ -2541,8 +2644,8 @@ class TurtleArtWindow(): x, y = self._calc_image_offset('', blk.spr) blk.set_image(pixbuf, x, y) except: - _logger.debug("Couldn't open dsobject (%s)" % \ - (blk.values[0])) + debug_output("Couldn't open dsobject (%s)" % \ + (blk.values[0]), self.running_sugar) self._block_skin('journaloff', blk) else: if not movie_media_type(blk.values[0][-4:]): @@ -2605,14 +2708,15 @@ class TurtleArtWindow(): for _i, _blk in enumerate(_blks): _blk.id = _i for _blk in _blks: - if _blk.name in CONTENT_BLOCKS or _blk.name in COLLAPSIBLE: + if _blk.name in content_blocks or _blk.name in COLLAPSIBLE: if len(_blk.values) > 0: _name = (_blk.name, _blk.values[0]) else: _name = (_blk.name) - elif _blk.name in BASIC_STYLE_VAR_ARG and len(_blk.values) > 0: + elif _blk.name in block_styles['basic-style-var-arg'] and \ + len(_blk.values) > 0: _name = (_blk.name, _blk.values[0]) - elif _blk.name in EXPANDABLE or _blk.name in EXPANDABLE_BLOCKS or\ + elif _blk.name in EXPANDABLE or _blk.name in expandable_blocks or\ _blk.name in EXPANDABLE_ARGS: _ex, _ey = _blk.get_expand_x_y() if _ex > 0: @@ -2637,12 +2741,16 @@ class TurtleArtWindow(): _data.append((_blk.id, _name, _sx - self.canvas.cx, _sy - self.canvas.cy, connections)) if save_turtle: - for _turtle in iter(self.turtles.dict): - self.canvas.set_turtle(_turtle) - _data.append((-1, ['turtle', _turtle], - self.canvas.xcor, self.canvas.ycor, - self.canvas.heading, self.canvas.color, - self.canvas.shade, self.canvas.pensize)) + for turtle in iter(self.turtles.dict): + # Don't save remote turtles + if not self.remote_turtle(turtle): + # Save default turtle as 'Yertle' + if turtle == self.nick: + turtle = DEFAULT_TURTLE + _data.append((-1, ['turtle', turtle], + self.canvas.xcor, self.canvas.ycor, + self.canvas.heading, self.canvas.color, + self.canvas.shade, self.canvas.pensize)) return _data def display_coordinates(self): @@ -2655,13 +2763,13 @@ class TurtleArtWindow(): (_("xcor"), x, _("ycor"), y, _("heading"), h)) self.activity.coordinates_label.show() elif self.interactive_mode: - self.win.set_title("%s — %s: %d %s: %d %s: %d" % \ + self.parent.set_title("%s — %s: %d %s: %d %s: %d" % \ (_("Turtle Art"), _("xcor"), x, _("ycor"), y, _("heading"), h)) def showlabel(self, shp, label=''): """ Display a message on a status block """ if not self.interactive_mode: - _logger.debug(label) + debug_output(label, self.running_sugar) return if shp == 'syntaxerror' and str(label) != '': if str(label)[1:] in self.status_shapes: @@ -2683,7 +2791,7 @@ class TurtleArtWindow(): self.status_spr.move((PALETTE_WIDTH, self.height - 200)) def calc_position(self, template): - """ Relative placement of portfolio objects (depreciated) """ + """ Relative placement of portfolio objects (deprecated) """ w, h, x, y, dx, dy = TEMPLATES[template] x *= self.canvas.width y *= self.canvas.height @@ -2705,49 +2813,30 @@ class TurtleArtWindow(): def save_as_image(self, name="", svg=False, pixbuf=None): """ Grab the current canvas and save it. """ + if svg: + suffix = '.svg' + else: + suffix = '.png' if not self.interactive_mode: - save_picture(self.canvas, name[:-3] + ".png") + save_picture(self.canvas, name[:-3] + suffix) return - """ - self.color_map = self.window.get_colormap() - new_pix = pixbuf.get_from_drawable(self.window, self.color_map, - 0, 0, 0, 0, - self.width, self.height) - new_pix.save(name[:-3] + ".png", "png") - """ - if self.running_sugar: - if svg: - if len(name) == 0: - filename = "ta.svg" - else: - filename = name + ".svg" + if len(name) == 0: + filename = 'ta' + suffix else: - if len(name) == 0: - filename = "ta.png" - else: - filename = name + ".png" + filename = name + suffix datapath = get_path(self.activity, 'instance') elif len(name) == 0: - name = "ta" + name = 'ta' if self.save_folder is not None: self.load_save_folder = self.save_folder - if svg: - filename, self.load_save_folder = get_save_name('.svg', - self.load_save_folder, - name) - else: - filename, self.load_save_folder = get_save_name('.png', - self.load_save_folder, - name) + filename, self.load_save_folder = get_save_name( + suffix, self.load_save_folder, name) datapath = self.load_save_folder else: datapath = os.getcwd() - if svg: - filename = name + ".svg" - else: - filename = name + ".png" + filename = name + suffix if filename is None: return @@ -2864,3 +2953,36 @@ class TurtleArtWindow(): w, h = self._calc_w_h('descriptionoff', blk.spr) x, y = self._calc_image_offset('descriptionoff', blk.spr, w, h) blk.scale_image(x, y, w, h) + + +def dock_dx_dy(block1, dock1n, block2, dock2n): + """ Find the distance between the dock points of two blocks. """ + _dock1 = block1.docks[dock1n] + _dock2 = block2.docks[dock2n] + _d1type, _d1dir, _d1x, _d1y = _dock1[0:4] + _d2type, _d2dir, _d2x, _d2y = _dock2[0:4] + if block1 == block2: + return (100, 100) + if _d1dir == _d2dir: + return (100, 100) + if (_d2type is not 'number') or (dock2n is not 0): + if block1.connections is not None and \ + dock1n < len(block1.connections) and \ + block1.connections[dock1n] is not None: + return (100, 100) + if block2.connections is not None and \ + dock2n < len(block2.connections) and \ + block2.connections[dock2n] is not None: + return (100, 100) + if _d1type != _d2type: + if block1.name in STRING_OR_NUMBER_ARGS: + if _d2type == 'number' or _d2type == 'string': + pass + elif block1.name in CONTENT_ARGS: + if _d2type in content_blocks: + pass + else: + return (100, 100) + (_b1x, _b1y) = block1.spr.get_xy() + (_b2x, _b2y) = block2.spr.get_xy() + return ((_b1x + _d1x) - (_b2x + _d2x), (_b1y + _d1y) - (_b2y + _d2y)) diff --git a/TurtleArtActivity.py b/TurtleArtActivity.py index 98968a8..69bb506 100644 --- a/TurtleArtActivity.py +++ b/TurtleArtActivity.py @@ -1,6 +1,6 @@ #Copyright (c) 2007, Playful Invention Company -#Copyright (c) 2008-10, Walter Bender -#Copyright (c) 2009,10 Raul Gutierrez Segales +#Copyright (c) 2008-11, Walter Bender +#Copyright (c) 2009-10 Raul Gutierrez Segales #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal @@ -32,22 +32,26 @@ from sugar.activity import activity try: # 0.86 toolbar widgets from sugar.activity.widgets import ActivityToolbarButton, StopButton from sugar.graphics.toolbarbox import ToolbarBox, ToolbarButton - NEW_SUGAR_SYSTEM = True + HAS_TOOLBARBOX = True except ImportError: - NEW_SUGAR_SYSTEM = False + HAS_TOOLBARBOX = False from sugar.graphics.toolbutton import ToolButton from sugar.datastore import datastore from sugar import profile -from gettext import gettext as _ import os.path import tarfile -from TurtleArt.taconstants import PALETTE_NAMES, OVERLAY_LAYER, HELP_STRINGS +# installs the global _() magic (reverted as it is broken) +# import TurtleArt.tagettext +from gettext import gettext as _ + +from TurtleArt.tapalette import palette_names, help_strings +from TurtleArt.taconstants import ICON_SIZE, BLOCK_SCALE, XO1, XO15 from TurtleArt.taexporthtml import save_html from TurtleArt.taexportlogo import save_logo from TurtleArt.tautils import data_to_file, data_to_string, data_from_string, \ - get_path, chooser + get_path, chooser, get_hardware from TurtleArt.tawindow import TurtleArtWindow from TurtleArt.tacollaboration import Collaboration @@ -55,39 +59,37 @@ from TurtleArt.tacollaboration import Collaboration class TurtleArtActivity(activity.Activity): def __init__(self, handle): - """ Activity subclass for Turtle Art """ + ''' Activity subclass for Turtle Art ''' super(TurtleArtActivity, self).__init__(handle) - datapath = get_path(activity, 'data') + self._check_ver_change(get_path(activity, 'data')) self._setup_visibility_handler() - self.new_sugar_system = NEW_SUGAR_SYSTEM + self.has_toolbarbox = HAS_TOOLBARBOX self._setup_toolbar() - canvas = self._setup_scrolled_window() + self._setup_canvas(self._setup_scrolled_window()) - self._check_ver_change(datapath) - - self._setup_canvas(canvas) + self._setup_palette_toolbar() self._setup_sharing() # Activity toolbar callbacks def do_save_as_html_cb(self, button): - """ Write html out to datastore. """ - self.save_as_html.set_icon("htmlon") - _logger.debug("saving html code") - # until we have URLs for datastore objects, always embed images + ''' Write html out to datastore. ''' + self.save_as_html.set_icon('htmlon') + _logger.debug('saving html code') + # Until we have URLs for datastore objects, always embed images. embed_flag = True - # grab code from stacks + # Generate HTML by processing TA code from stacks. html = save_html(self, self.tw, embed_flag) if len(html) == 0: return - # save the html code to the instance directory + # Save the HTML code to the instance directory. datapath = get_path(activity, 'instance') save_type = '.html' @@ -95,17 +97,17 @@ class TurtleArtActivity(activity.Activity): if self.tw.saved_pictures[0].endswith(('.svg')): save_type = '.xml' - html_file = os.path.join(datapath, "portfolio" + save_type) - f = file(html_file, "w") + html_file = os.path.join(datapath, 'portfolio' + save_type) + f = file(html_file, 'w') f.write(html) f.close() - if embed_flag == False: - # need to make a tarball that includes the images + if not embed_flag: + # We need to make a tar ball that includes the images. tar_path = os.path.join(datapath, 'portfolio.tar') tar_fd = tarfile.open(tar_path, 'w') try: - tar_fd.add(html_file, "portfolio.html") + tar_fd.add(html_file, 'portfolio.html') import glob image_list = glob.glob(os.path.join(datapath, 'image*')) for i in image_list: @@ -113,15 +115,11 @@ class TurtleArtActivity(activity.Activity): finally: tar_fd.close() - # Create a datastore object dsobject = datastore.create() - - # Write any metadata (here we specifically set the title of the file - # and specify that this is a plain text file). - dsobject.metadata['title'] = self.metadata['title'] + " " + \ - _("presentation") + dsobject.metadata['title'] = self.metadata['title'] + ' ' + \ + _('presentation') dsobject.metadata['icon-color'] = profile.get_color().to_string() - if embed_flag == True: + if embed_flag: if save_type == '.xml': dsobject.metadata['mime_type'] = 'application/xml' else: @@ -130,82 +128,72 @@ class TurtleArtActivity(activity.Activity): else: dsobject.metadata['mime_type'] = 'application/x-tar' dsobject.set_file_path(tar_path) - dsobject.metadata['activity'] = 'org.laptop.WebActivity' datastore.write(dsobject) dsobject.destroy() - gobject.timeout_add(250, self.save_as_html.set_icon, "htmloff") - self.tw.saved_pictures = [] + gobject.timeout_add(250, self.save_as_html.set_icon, 'htmloff') + + self.tw.saved_pictures = [] # Clear queue of pictures we have viewed. return def do_save_as_logo_cb(self, button): - """ Write logo code out to datastore. """ - self.save_as_logo.set_icon("logo-saveon") + ''' Write UCB logo code to datastore. ''' + self.save_as_logo.set_icon('logo-saveon') logo_code_path = self._dump_logo_code() if logo_code_path is None: return - # Create a datastore object dsobject = datastore.create() - - # Write any metadata (here we specifically set the title of the file - # and specify that this is a plain text file). - dsobject.metadata['title'] = self.metadata['title'] + ".lg" + dsobject.metadata['title'] = self.metadata['title'] + '.lg' dsobject.metadata['mime_type'] = 'text/plain' dsobject.metadata['icon-color'] = profile.get_color().to_string() - - # Set the file_path in the datastore. dsobject.set_file_path(logo_code_path) - datastore.write(dsobject) - gobject.timeout_add(250, self.save_as_logo.set_icon, "logo-saveoff") + dsobject.destroy() + + gobject.timeout_add(250, self.save_as_logo.set_icon, 'logo-saveoff') return def do_load_ta_project_cb(self, button): - """ Load a project from the Journal """ - chooser(self, SERVICE, self._load_ta_project) + ''' Load a project from the Journal. ''' + chooser(self, 'org.laptop.TurtleArtActivity', self._load_ta_project) def _load_ta_project(self, dsobject): - """ Load a ta project from the datastore """ + ''' Load a TA project from the datastore. ''' try: - _logger.debug("opening %s " % dsobject.file_path) + _logger.debug('opening %s ' % dsobject.file_path) self.read_file(dsobject.file_path, False) except: _logger.debug("couldn't open %s" % dsobject.file_path) def do_load_python_cb(self, button): - """ Load Python code from the Journal. """ - self.load_python.set_icon("pippy-openon") + ''' Load Python code from the Journal. ''' + self.load_python.set_icon('pippy-openon') self.tw.load_python_code_from_file(fname=None, add_new_block=True) - gobject.timeout_add(250, self.load_python.set_icon, "pippy-openoff") + gobject.timeout_add(250, self.load_python.set_icon, 'pippy-openoff') def do_save_as_image_cb(self, button): - """ Save the canvas to the Journal. """ - self.save_as_image.set_icon("image-saveon") - _logger.debug("saving image to journal") + ''' Save the canvas to the Journal. ''' + self.save_as_image.set_icon('image-saveon') + _logger.debug('saving image to journal') self.tw.save_as_image() - gobject.timeout_add(250, self.save_as_image.set_icon, "image-saveoff") + gobject.timeout_add(250, self.save_as_image.set_icon, 'image-saveoff') return def do_keep_cb(self, button): - """ Save a snapshot of the project to the Journal. """ + ''' Save a snapshot of the project to the Journal. ''' tmpfile = self._dump_ta_code() if tmpfile is not None: - # Create a datastore object dsobject = datastore.create() - - # Write any metadata - dsobject.metadata['title'] = self.metadata['title'] + " " + \ - _("snapshot") + dsobject.metadata['title'] = self.metadata['title'] + ' ' + \ + _('snapshot') dsobject.metadata['icon-color'] = profile.get_color().to_string() dsobject.metadata['mime_type'] = 'application/x-turtle-art' dsobject.metadata['activity'] = 'org.laptop.TurtleArtActivity' dsobject.set_file_path(tmpfile) datastore.write(dsobject) - - # Clean up dsobject.destroy() os.remove(tmpfile) return @@ -213,120 +201,119 @@ class TurtleArtActivity(activity.Activity): # Main/palette toolbar button callbacks def do_palette_cb(self, button): - """ Show/hide palette """ - if self.tw.palette == True: + ''' Show/hide palette ''' + if self.tw.palette: self.tw.hideshow_palette(False) self.do_hidepalette() - if self.new_sugar_system and self.tw.selected_palette is not None: + if self.has_toolbarbox and self.tw.selected_palette is not None: self.palette_buttons[self.tw.selected_palette].set_icon( - PALETTE_NAMES[self.tw.selected_palette] + 'off') + palette_names[self.tw.selected_palette] + 'off') else: self.tw.hideshow_palette(True) self.do_showpalette() - if self.new_sugar_system: - self.palette_buttons[0].set_icon(PALETTE_NAMES[0] + 'on') + if self.has_toolbarbox: + self.palette_buttons[0].set_icon(palette_names[0] + 'on') def do_palette_buttons_cb(self, button, i): - """ Palette selector buttons """ + ''' Palette selector buttons ''' if self.tw.selected_palette is not None: self.palette_buttons[self.tw.selected_palette].set_icon( - PALETTE_NAMES[self.tw.selected_palette] + 'off') + palette_names[self.tw.selected_palette] + 'off') if self.tw.selected_palette == i: - # second click so hide the palette (#2505) + # Hide the palette if it is already selected. self.tw.hideshow_palette(False) self.do_hidepalette() return - self.palette_buttons[i].set_icon(PALETTE_NAMES[i] + 'on') + self.palette_buttons[i].set_icon(palette_names[i] + 'on') self.tw.show_palette(i) self.do_showpalette() - # These methods are called both from buttons and palette. + # These methods are called both from toolbar buttons and blocks. def do_hidepalette(self): - """ Hide the palette. """ + ''' Hide the palette. ''' if hasattr(self, 'palette_button'): - self.palette_button.set_icon("paletteon") + self.palette_button.set_icon('paletteon') self.palette_button.set_tooltip(_('Show palette')) def do_showpalette(self): - """ Show the palette. """ + ''' Show the palette. ''' if hasattr(self, 'palette_button'): - self.palette_button.set_icon("paletteoff") + self.palette_button.set_icon('paletteoff') self.palette_button.set_tooltip(_('Hide palette')) def do_hideshow_cb(self, button): - """ Toggle visibility. """ + ''' Toggle visibility. ''' self.tw.hideshow_button() - if self.tw.hide == True: # we just hid the blocks - self.blocks_button.set_icon("hideshowon") + if self.tw.hide: # We just hid the blocks. + self.blocks_button.set_icon('hideshowon') self.blocks_button.set_tooltip(_('Show blocks')) else: - self.blocks_button.set_icon("hideshowoff") + self.blocks_button.set_icon('hideshowoff') self.blocks_button.set_tooltip(_('Hide blocks')) - # update palette buttons too - if self.tw.palette == False: + # Update palette buttons too. + if not self.tw.palette: self.do_hidepalette() else: self.do_showpalette() - def do_hide(self): - """ Hide blocks. """ - self.blocks_button.set_icon("hideshowon") + def do_hide_blocks(self): + ''' Hide blocks. ''' + self.blocks_button.set_icon('hideshowon') self.blocks_button.set_tooltip(_('Show blocks')) self.do_hidepalette() - def do_show(self): - """ Show blocks. """ - self.blocks_button.set_icon("hideshowoff") + def do_show_blocks(self): + ''' Show blocks. ''' + if not hasattr(self, 'blocks_button'): + return + self.blocks_button.set_icon('hideshowoff') self.blocks_button.set_tooltip(_('Hide blocks')) self.do_showpalette() def do_eraser_cb(self, button): - """ Clear the screen and recenter. """ - self.eraser_button.set_icon("eraseroff") + ''' Clear the screen and recenter. ''' + self.eraser_button.set_icon('eraseroff') self.recenter() self.tw.eraser_button() - gobject.timeout_add(250, self.eraser_button.set_icon, "eraseron") + gobject.timeout_add(250, self.eraser_button.set_icon, 'eraseron') def do_run_cb(self, button): - """ Callback for run button (rabbit). """ - self.run_button.set_icon("run-faston") + ''' Callback for run button (rabbit) ''' + self.run_button.set_icon('run-faston') self.tw.lc.trace = 0 self.tw.run_button(0) - gobject.timeout_add(1000, self.run_button.set_icon, "run-fastoff") + gobject.timeout_add(1000, self.run_button.set_icon, 'run-fastoff') def do_step_cb(self, button): - """ Callback for step button (turtle). """ - self.step_button.set_icon("run-slowon") + ''' Callback for step button (turtle) ''' + self.step_button.set_icon('run-slowon') self.tw.lc.trace = 1 self.tw.run_button(3) - gobject.timeout_add(1000, self.step_button.set_icon, "run-slowoff") + gobject.timeout_add(1000, self.step_button.set_icon, 'run-slowoff') def do_debug_cb(self, button): - """ Callback for debug button (bug). """ - self.debug_button.set_icon("debugon") + ''' Callback for debug button (bug) ''' + self.debug_button.set_icon('debugon') self.tw.lc.trace = 1 self.tw.run_button(9) - gobject.timeout_add(1000, self.debug_button.set_icon, "debugoff") + gobject.timeout_add(1000, self.debug_button.set_icon, 'debugoff') def do_stop_cb(self, button): - """ Callback for stop button. """ - self.stop_turtle_button.set_icon("stopitoff") + ''' Callback for stop button. ''' + self.stop_turtle_button.set_icon('stopitoff') self.tw.stop_button() - self.step_button.set_icon("run-slowoff") - self.run_button.set_icon("run-fastoff") + self.step_button.set_icon('run-slowoff') + self.run_button.set_icon('run-fastoff') def do_samples_cb(self, button): - """ Sample projects open dialog """ - # FIXME: encapsulation! + ''' Sample-projects open dialog ''' self.tw.load_file(True) - # run the activity - self.stop_turtle_button.set_icon("stopiton") self.tw.run_button(0) def recenter(self): - """ Recenter scrolled window around canvas. """ + ''' Recenter scrolled window around canvas. ''' hadj = self.sw.get_hadjustment() hadj.set_value(0) self.sw.set_hadjustment(hadj) @@ -335,39 +322,55 @@ class TurtleArtActivity(activity.Activity): self.sw.set_vadjustment(vadj) def do_fullscreen_cb(self, button): - """ Hide the Sugar toolbars. """ + ''' Hide the Sugar toolbars. ''' self.fullscreen() self.recenter() def do_grow_blocks_cb(self, button): - """ Grow the blocks. """ - self.do_resize_blocks(1.5) + ''' Grow the blocks. ''' + self.do_resize_blocks(1) def do_shrink_blocks_cb(self, button): - """ Shrink the blocks. """ - self.do_resize_blocks(0.67) + ''' Shrink the blocks. ''' + self.do_resize_blocks(-1) - def do_resize_blocks(self, scale_factor): - """ Scale the blocks. """ - self.tw.block_scale *= scale_factor + def do_resize_blocks(self, inc): + ''' Scale the blocks. ''' + if self.tw.block_scale in BLOCK_SCALE: + i = BLOCK_SCALE.index(self.tw.block_scale) + inc + else: + i = BLOCK_SCALE[3] # 2.0 + if i < 0: + self.tw.block_scale = BLOCK_SCALE[0] + elif i == len(BLOCK_SCALE): + self.tw.block_scale = BLOCK_SCALE[-1] + else: + self.tw.block_scale = BLOCK_SCALE[i] self.tw.resize_blocks() def do_cartesian_cb(self, button): - """ Display Cartesian coordinate grid. """ + ''' Display Cartesian-coordinate grid. ''' if self.tw.cartesian: self.tw.set_cartesian(False) else: self.tw.set_cartesian(True) def do_polar_cb(self, button): - """ Display Polar coordinate grid. """ + ''' Display polar-coordinate grid. ''' if self.tw.polar: self.tw.set_polar(False) else: self.tw.set_polar(True) + def do_metric_cb(self, button): + ''' Display metric-coordinate grid. ''' + if self.tw.metric: + self.tw.set_metric(False) + else: + self.tw.set_metric(True) + def do_rescale_cb(self, button): - """ Rescale coordinate system (100==height/2 or 100 pixels). """ + ''' Rescale coordinate system (100==height/2 or 100 pixels). ''' if self.tw.cartesian: cartesian = True self.tw.set_cartesian(False) @@ -378,28 +381,35 @@ class TurtleArtActivity(activity.Activity): self.tw.set_polar(False) else: polar = False + if self.tw.metric: + metric = True + self.tw.set_metric(False) + else: + polar = False if self.tw.coord_scale == 1: self.tw.coord_scale = self.tw.height / 200 - self.rescale_button.set_icon("contract-coordinates") + self.rescale_button.set_icon('contract-coordinates') self.rescale_button.set_tooltip(_('Rescale coordinates down')) else: self.tw.coord_scale = 1 - self.rescale_button.set_icon("expand-coordinates") + self.rescale_button.set_icon('expand-coordinates') self.rescale_button.set_tooltip(_('Rescale coordinates up')) self.tw.eraser_button() if cartesian: self.tw.set_cartesian(True) if polar: self.tw.set_polar(True) + if metric: + self.tw.set_metric(True) def get_document_path(self, async_cb, async_err_cb): - """ View TA code as part of view source. """ + ''' View TA code as part of view source. ''' ta_code_path = self._dump_ta_code() if ta_code_path is not None: async_cb(ta_code_path) def _dump_logo_code(self): - """ Save Logo code to temporary file. """ + ''' Save Logo code to temporary file. ''' datapath = get_path(activity, 'instance') tmpfile = os.path.join(datapath, 'tmpfile.lg') code = save_logo(self.tw) @@ -407,47 +417,41 @@ class TurtleArtActivity(activity.Activity): _logger.debug('save_logo returned None') return None try: - f = file(tmpfile, "w") + f = file(tmpfile, 'w') f.write(code) f.close() except Exception, e: - _logger.error("Couldn't dump code to view source: " + str(e)) + _logger.error("Couldn't save Logo code: " + str(e)) + tmpfile = None return tmpfile def _dump_ta_code(self): - """ Save TA code to temporary file. """ + ''' Save TA code to temporary file. ''' datapath = get_path(activity, 'instance') tmpfile = os.path.join(datapath, 'tmpfile.ta') try: data_to_file(self.tw.assemble_data_to_save(), tmpfile) - except: - _logger.debug("couldn't save snapshot to journal") + except Exception, e: + _logger.error("Couldn't save project code: " + str(e)) tmpfile = None return tmpfile def __visibility_notify_cb(self, window, event): - """ Callback method for when the activity's visibility changes. """ + ''' Callback method for when the activity's visibility changes. ''' if event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED: - self.tw.lc.ag = None + self.tw.background_plugins() elif event.state in \ [gtk.gdk.VISIBILITY_UNOBSCURED, gtk.gdk.VISIBILITY_PARTIAL]: - pass - - def update_title_cb(self, widget, event, toolbox): - """ Update the title. """ - toolbox._activity_toolbar._update_title_cb() - toolbox._activity_toolbar._update_title_sid = True + self.tw.foreground_plugins() def _keep_clicked_cb(self, button): - """ Keep button clicked. """ + ''' Keep-button clicked. ''' self.jobject_new_patch() def _setup_toolbar(self): - """ Setup toolbar according to Sugar version """ - if self.new_sugar_system: - # Use 0.86 toolbar design - # Create toolbox and secondary toolbars - toolbox = ToolbarBox() + ''' Setup toolbar according to Sugar version. ''' + if self.has_toolbarbox: + self._toolbox = ToolbarBox() activity_toolbar_button = ActivityToolbarButton(self) @@ -459,61 +463,60 @@ class TurtleArtActivity(activity.Activity): view_toolbar_button = ToolbarButton(label=_('View'), page=view_toolbar, icon_name='toolbar-view') - palette_toolbar = gtk.Toolbar() - palette_toolbar_button = ToolbarButton(page=palette_toolbar, - icon_name='palette') + self._palette_toolbar = gtk.Toolbar() + self._palette_toolbar_button = ToolbarButton( + page=self._palette_toolbar, icon_name='palette') help_toolbar = gtk.Toolbar() - help_toolbar_button = ToolbarButton(label=_("Help"), + help_toolbar_button = ToolbarButton(label=_('Help'), page=help_toolbar, icon_name='help-toolbar') journal_toolbar = gtk.Toolbar() journal_toolbar_button = ToolbarButton(page=journal_toolbar, icon_name='activity-journal') - # Add the toolbars and buttons to the toolbox + activity_toolbar_button.show() - toolbox.toolbar.insert(activity_toolbar_button, -1) + self._toolbox.toolbar.insert(activity_toolbar_button, -1) edit_toolbar_button.show() - toolbox.toolbar.insert(edit_toolbar_button, -1) + self._toolbox.toolbar.insert(edit_toolbar_button, -1) journal_toolbar_button.show() - toolbox.toolbar.insert(journal_toolbar_button, -1) + self._toolbox.toolbar.insert(journal_toolbar_button, -1) view_toolbar_button.show() - toolbox.toolbar.insert(view_toolbar_button, -1) - palette_toolbar_button.show() - toolbox.toolbar.insert(palette_toolbar_button, -1) + self._toolbox.toolbar.insert(view_toolbar_button, -1) + self._palette_toolbar_button.show() + self._toolbox.toolbar.insert(self._palette_toolbar_button, -1) help_toolbar_button.show() - toolbox.toolbar.insert(help_toolbar_button, -1) + self._toolbox.toolbar.insert(help_toolbar_button, -1) - self._add_separator(toolbox.toolbar) + self._add_separator(self._toolbox.toolbar) - self._make_project_buttons(toolbox.toolbar) + self._make_project_buttons(self._toolbox.toolbar) - self._add_separator(toolbox.toolbar, True) + self._add_separator(self._toolbox.toolbar, True) stop_button = StopButton(self) stop_button.props.accelerator = 'Q' - toolbox.toolbar.insert(stop_button, -1) + self._toolbox.toolbar.insert(stop_button, -1) stop_button.show() else: - # Use pre-0.86 toolbar design - toolbox = activity.ActivityToolbox(self) - self.set_toolbox(toolbox) + self._toolbox = activity.ActivityToolbox(self) + self.set_toolbox(self._toolbox) project_toolbar = gtk.Toolbar() - toolbox.add_toolbar(_('Project'), project_toolbar) + self._toolbox.add_toolbar(_('Project'), project_toolbar) view_toolbar = gtk.Toolbar() - toolbox.add_toolbar(_('View'), view_toolbar) + self._toolbox.add_toolbar(_('View'), view_toolbar) view_toolbar_button = view_toolbar edit_toolbar = gtk.Toolbar() - toolbox.add_toolbar(_('Edit'), edit_toolbar) + self._toolbox.add_toolbar(_('Edit'), edit_toolbar) edit_toolbar_button = edit_toolbar journal_toolbar = gtk.Toolbar() - toolbox.add_toolbar(_('Import/Export'), journal_toolbar) + self._toolbox.add_toolbar(_('Import/Export'), journal_toolbar) journal_toolbar_button = journal_toolbar help_toolbar = gtk.Toolbar() - toolbox.add_toolbar(_('Help'), help_toolbar) + self._toolbox.add_toolbar(_('Help'), help_toolbar) help_toolbar_button = help_toolbar self._make_palette_buttons(project_toolbar, palette_button=True) @@ -522,193 +525,208 @@ class TurtleArtActivity(activity.Activity): self._make_project_buttons(project_toolbar) - self.keep_button = self._add_button('filesaveoff', _("Save snapshot"), - self.do_keep_cb, - journal_toolbar_button) - self.save_as_html = self._add_button('htmloff', _("Save as HTML"), - self.do_save_as_html_cb, - journal_toolbar_button) - self.save_as_logo = self._add_button('logo-saveoff', _("Save as Logo"), - self.do_save_as_logo_cb, - journal_toolbar_button) - self.save_as_image = self._add_button('image-saveoff', _("Save as image"), - self.do_save_as_image_cb, - journal_toolbar_button) - self.load_ta_project = self._add_button('load-from-journal', - _("Import project from the Journal"), self.do_load_ta_project_cb, - journal_toolbar_button) + self.keep_button = self._add_button( + 'filesaveoff', _('Save snapshot'), self.do_keep_cb, + journal_toolbar_button) + self.save_as_html = self._add_button( + 'htmloff', _('Save as HTML'), self.do_save_as_html_cb, + journal_toolbar_button) + self.save_as_logo = self._add_button( + 'logo-saveoff', _('Save as Logo'), self.do_save_as_logo_cb, + journal_toolbar_button) + self.save_as_image = self._add_button( + 'image-saveoff', _('Save as image'), self.do_save_as_image_cb, + journal_toolbar_button) + self.load_ta_project = self._add_button( + 'load-from-journal', _('Import project from the Journal'), + self.do_load_ta_project_cb, journal_toolbar_button) self._add_separator(journal_toolbar) - self.load_python = self._add_button('pippy-openoff', _("Load Python block"), - self.do_load_python_cb, - journal_toolbar_button) - self.samples_button = self._add_button("ta-open", _('Load example'), - self.do_samples_cb, journal_toolbar_button) - copy = self._add_button('edit-copy', _('Copy'), self._copy_cb, - edit_toolbar_button, 'c') - paste = self._add_button('edit-paste', _('Paste'), self._paste_cb, - edit_toolbar_button, 'v') - fullscreen_button = self._add_button('view-fullscreen', _("Fullscreen"), - self.do_fullscreen_cb, - view_toolbar_button, 'Return') - cartesian_button = self._add_button('view-Cartesian', - _("Cartesian coordinates"), - self.do_cartesian_cb, - view_toolbar_button) - polar_button = self._add_button('view-polar', _("Polar coordinates"), - self.do_polar_cb, view_toolbar_button) + self.load_python = self._add_button( + 'pippy-openoff', _('Load Python block'), self.do_load_python_cb, + journal_toolbar_button) + self.samples_button = self._add_button( + 'ta-open', _('Load example'), self.do_samples_cb, + journal_toolbar_button) + self._add_button('edit-copy', _('Copy'), self._copy_cb, + edit_toolbar_button, 'c') + self._add_button('edit-paste', _('Paste'), self._paste_cb, + edit_toolbar_button, 'v') + self._add_button('view-fullscreen', _('Fullscreen'), + self.do_fullscreen_cb, view_toolbar_button, + 'Return') + self._add_button('view-Cartesian', _('Cartesian coordinates'), + self.do_cartesian_cb, view_toolbar_button) + self._add_button('view-polar', _('Polar coordinates'), + self.do_polar_cb, view_toolbar_button) + if get_hardware() in [XO1, XO15]: + self._add_button('view-metric', _('Metric coordinates'), + self.do_metric_cb, view_toolbar_button) self._add_separator(view_toolbar) - self.coordinates_label = self._add_label( - _("xcor") + " = 0 " + _("ycor") + " = 0 " + _("heading") + " = 0", - view_toolbar) + self.coordinates_label = self._add_label(_('xcor') + ' = 0 ' + \ + _('ycor') + ' = 0 ' + _('heading') + ' = 0', view_toolbar) self._add_separator(view_toolbar, True) - self.rescale_button = self._add_button('expand-coordinates', - _("Rescale coordinates up"), self.do_rescale_cb, - view_toolbar_button) - self.resize_up_button = self._add_button('resize+', _("Grow blocks"), - self.do_grow_blocks_cb, view_toolbar_button) - self.resize_down_button = self._add_button('resize-', _("Shrink blocks"), - self.do_shrink_blocks_cb, view_toolbar_button) - self.hover_help_label = self._add_label( - _("Move the cursor over the orange palette for help."), - help_toolbar) - - # The palette toolbar is only used with 0.86+ - if self.new_sugar_system: - self.palette_buttons = [] - for i, name in enumerate(PALETTE_NAMES): - if i > 0: - suffix = 'off' - else: - suffix = 'on' - self.palette_buttons.append(self._add_button(name + suffix, - HELP_STRINGS[name], self.do_palette_buttons_cb, - palette_toolbar_button, None, i)) - self._add_separator(palette_toolbar, True) - - self._make_palette_buttons(palette_toolbar_button) - - self.set_toolbar_box(toolbox) - palette_toolbar.show() + self.rescale_button = self._add_button( + 'expand-coordinates', _('Rescale coordinates up'), + self.do_rescale_cb, view_toolbar_button) + self.resize_up_button = self._add_button( + 'resize+', _('Grow blocks'), self.do_grow_blocks_cb, + view_toolbar_button) + self.resize_down_button = self._add_button( + 'resize-', _('Shrink blocks'), self.do_shrink_blocks_cb, + view_toolbar_button) + if gtk.gtk_version[0] > 2 or gtk.gtk_version[1] > 16: + self.hover_help_label = self._add_label( + _('Move the cursor over the orange palette for help.'), + help_toolbar, gtk.gdk.screen_width() - 2 * ICON_SIZE) + else: + self.hover_help_label = self._add_label( + _('Move the cursor over the orange palette for help.'), + help_toolbar) edit_toolbar.show() view_toolbar.show() help_toolbar.show() - toolbox.show() + self._toolbox.show() + # Setup palette toolbar only *after* initializing the plugins - if self.new_sugar_system: + if self.has_toolbarbox: # Hack as a workaround for #2050 edit_toolbar_button.set_expanded(True) edit_toolbar_button.set_expanded(False) - palette_toolbar_button.set_expanded(True) + self._palette_toolbar_button.set_expanded(True) else: - toolbox.set_current_toolbar(1) + self._toolbox.set_current_toolbar(1) + + def _setup_palette_toolbar(self): + # The palette toolbar must be setup *after* plugins are loaded. + if self.has_toolbarbox: + self.palette_buttons = [] + for i, name in enumerate(palette_names): + if i > 0: + suffix = 'off' + else: + suffix = 'on' + self.palette_buttons.append(self._add_button(name + suffix, + help_strings[name], self.do_palette_buttons_cb, + self._palette_toolbar_button, None, i)) + self._add_separator(self._palette_toolbar, True) + + self._make_palette_buttons(self._palette_toolbar_button) + + self.set_toolbar_box(self._toolbox) + self._palette_toolbar.show() def _make_palette_buttons(self, toolbar, palette_button=False): - """ Creates the palette and block buttons for both toolbar types""" + ''' Creates the palette and block buttons for both toolbar types''' if palette_button: # old-style toolbars need this button - self.palette_button = self._add_button("paletteoff", _('Hide palette'), - self.do_palette_cb, toolbar, _('p')) - self.blocks_button = self._add_button("hideshowoff", _('Hide blocks'), - self.do_hideshow_cb, toolbar, _('b')) + self.palette_button = self._add_button( + 'paletteoff', _('Hide palette'), self.do_palette_cb, + toolbar, _('p')) + self.blocks_button = self._add_button( + 'hideshowoff', _('Hide blocks'), self.do_hideshow_cb, toolbar, + _('b')) def _make_project_buttons(self, toolbar): - """ Creates the turtle buttons for both toolbar types""" - self.eraser_button = self._add_button("eraseron", _('Clean'), - self.do_eraser_cb, toolbar, _('e')) - self.run_button = self._add_button("run-fastoff", _('Run'), self.do_run_cb, - toolbar, _('r')) - self.step_button = self._add_button("run-slowoff", _('Step'), - self.do_step_cb, toolbar, _('w')) - self.debug_button = self._add_button("debugoff", _('Debug'), - self.do_debug_cb, toolbar, _('d')) - self.stop_turtle_button = self._add_button("stopitoff", _('Stop turtle'), - self.do_stop_cb, toolbar, _('s')) - - def _setup_scrolled_window(self): - """ Create a scrolled window to contain the turtle canvas. """ - self.sw = gtk.ScrolledWindow() - self.set_canvas(self.sw) - self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self.sw.show() - canvas = gtk.DrawingArea() - width = gtk.gdk.screen_width() * 2 - height = gtk.gdk.screen_height() * 2 - canvas.set_size_request(width, height) - self.sw.add_with_viewport(canvas) - canvas.show() - return canvas + ''' Creates the turtle buttons for both toolbar types''' + self.eraser_button = self._add_button( + 'eraseron', _('Clean'), self.do_eraser_cb, toolbar, _('e')) + self.run_button = self._add_button( + 'run-fastoff', _('Run'), self.do_run_cb, toolbar, _('r')) + self.step_button = self._add_button( + 'run-slowoff', _('Step'), self.do_step_cb, toolbar, _('w')) + self.debug_button = self._add_button( + 'debugoff', _('Debug'), self.do_debug_cb, toolbar, _('d')) + self.stop_turtle_button = self._add_button( + 'stopitoff', _('Stop turtle'), self.do_stop_cb, toolbar, + _('s')) def _check_ver_change(self, datapath): - """ To be replaced with date checking. """ - # Check to see if the version has changed + ''' Check to see if the version has changed. ''' + # We don't do anything with this info at the moment. try: version = os.environ['SUGAR_BUNDLE_VERSION'] except KeyError: - version = "unknown" + version = 'unknown' - filename = "version.dat" + filename = 'version.dat' version_data = [] new_version = True try: - file_handle = open(os.path.join(datapath, filename), "r") + file_handle = open(os.path.join(datapath, filename), 'r') if file_handle.readline() == version: new_version = False file_handle.close() except IOError: - _logger.debug("Couldn't read version number") + _logger.debug("Couldn't read version number.") version_data.append(version) try: - file_handle = open(os.path.join(datapath, filename), "w") + file_handle = open(os.path.join(datapath, filename), 'w') file_handle.writelines(version_data) file_handle.close() except IOError: - _logger.debug("Couldn't write version number") + _logger.debug("Couldn't write version number.") return new_version + def _setup_scrolled_window(self): + ''' Create a scrolled window to contain the turtle canvas. ''' + self.sw = gtk.ScrolledWindow() + self.set_canvas(self.sw) + self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.sw.show() + + canvas = gtk.DrawingArea() + canvas.set_size_request(gtk.gdk.screen_width() * 2, + gtk.gdk.screen_height() * 2) + self.sw.add_with_viewport(canvas) + canvas.show() + self.sw.show() + self.show_all() + return canvas + def _setup_canvas(self, canvas): - """ Initialize the turtle art canvas. """ + ''' Initialize the turtle art canvas. ''' bundle_path = activity.get_bundle_path() self.tw = TurtleArtWindow(canvas, bundle_path, self, profile.get_color().to_string(), profile.get_nick_name()) - # self.tw.activity = self self.tw.window.grab_focus() path = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'data') self.tw.save_folder = path + # Try restoring an existing project... if self._jobject and self._jobject.file_path: self.read_file(self._jobject.file_path) - else: # if new, load a start brick onto the canvas + else: # ...or else, load a Start Block onto the canvas. self.tw.load_start() def _setup_sharing(self): + ''' Setup the Collabora stack. ''' self._collaboration = Collaboration(self.tw, self) self._collaboration.setup() def _setup_visibility_handler(self): - """ Notify when the visibility state changes """ + ''' Notify me when the visibility state changes. ''' self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK) - self.connect("visibility-notify-event", self.__visibility_notify_cb) + self.connect('visibility-notify-event', self.__visibility_notify_cb) def write_file(self, file_path): - """ Write the project to the Journal. """ - _logger.debug("Write file: %s" % file_path) + ''' Write the project to the Journal. ''' + _logger.debug('Write file: %s' % file_path) self.metadata['mime_type'] = 'application/x-turtle-art' data_to_file(self.tw.assemble_data_to_save(), file_path) def read_file(self, file_path, run_it=True): - """ Read a project in and then run it. """ - import os - import tempfile - import shutil - + ''' Read a project in and then run it. ''' if hasattr(self, 'tw'): - _logger.debug("Read file: %s" % (file_path)) - # Could be a gtar (newer builds) or tar (767) file + _logger.debug('Read file: %s' % (file_path)) + # Could be a deprecated gtar or tar file... if file_path.endswith(('.gtar', '.tar')): + import tempfile + import shutil + tar_fd = tarfile.open(file_path, 'r') tmpdir = tempfile.mkdtemp() try: @@ -716,56 +734,54 @@ class TurtleArtActivity(activity.Activity): # but we will ignore the .png file # If run_it is True, we want to create a new project tar_fd.extractall(tmpdir) - self.tw.load_files(os.path.join(tmpdir, 'ta_code.ta'), \ - run_it) # create a new project flag + self.tw.load_files(os.path.join(tmpdir, 'ta_code.ta'), + run_it) finally: shutil.rmtree(tmpdir) tar_fd.close() - # Otherwise, assume it is a .ta file + # ...otherwise, assume it is a .ta file. else: - _logger.debug("trying to open a .ta file:" + file_path) + _logger.debug('Trying to open a .ta file:' + file_path) self.tw.load_files(file_path, run_it) - # run the activity + # Finally, run the project. if run_it: - self.stop_turtle_button.set_icon("stopiton") self.tw.run_button(0) else: - _logger.debug("Deferring reading file %s" % (file_path)) + _logger.debug('Deferring reading file %s' % (file_path)) def jobject_new_patch(self): - """ Save instance to Journal. """ + ''' Save instance to Journal. ''' oldj = self._jobject self._jobject = datastore.create() self._jobject.metadata['title'] = oldj.metadata['title'] self._jobject.metadata['title_set_by_user'] = \ oldj.metadata['title_set_by_user'] - # self._jobject.metadata['activity'] = self.get_service_name() self._jobject.metadata['activity_id'] = self.get_id() self._jobject.metadata['keep'] = '0' - # Is this the correct syntax for saving the buddies list? - # self._jobject.metadata['buddies'] = self.tw.buddies self._jobject.metadata['preview'] = '' self._jobject.metadata['icon-color'] = profile.get_color().to_string() self._jobject.file_path = '' - datastore.write(self._jobject, - reply_handler=self._internal_jobject_create_cb, - error_handler=self._internal_jobject_error_cb) + datastore.write( + self._jobject, reply_handler=self._internal_jobject_create_cb, + error_handler=self._internal_jobject_error_cb) self._jobject.destroy() def _copy_cb(self, button): - clipBoard = gtk.Clipboard() - _logger.debug("serialize the project and copy to clipboard") + ''' Copy to the clipboard. ''' + clipboard = gtk.Clipboard() + _logger.debug('Serialize the project and copy to clipboard.') data = self.tw.assemble_data_to_save(False, False) if data is not []: text = data_to_string(data) - clipBoard.set_text(text) + clipboard.set_text(text) self.tw.paste_offset = 20 def _paste_cb(self, button): - clipBoard = gtk.Clipboard() - _logger.debug("paste to the project") - text = clipBoard.wait_for_text() + ''' Paste from the clipboard. ''' + clipboard = gtk.Clipboard() + _logger.debug('Paste to the project.') + text = clipboard.wait_for_text() if text is not None: if self.tw.selected_blk is not None and \ self.tw.selected_blk.name == 'string': @@ -777,10 +793,12 @@ class TurtleArtActivity(activity.Activity): self.tw.paste_offset) self.tw.paste_offset += 20 - def _add_label(self, string, toolbar): - """ add a label to a toolbar """ + def _add_label(self, string, toolbar, width=None): + ''' Add a label to a toolbar. ''' label = gtk.Label(string) label.set_line_wrap(True) + if width is not None: + label.set_size_request(width, -1) label.show() toolitem = gtk.ToolItem() toolitem.add(label) @@ -789,15 +807,16 @@ class TurtleArtActivity(activity.Activity): return label def _add_separator(self, toolbar, expand=False): - """ add a separator to a toolbar """ + ''' Add a separator to a toolbar. ''' separator = gtk.SeparatorToolItem() separator.props.draw = True separator.set_expand(expand) toolbar.insert(separator, -1) separator.show() - def _add_button(self, name, tooltip, callback, toolbar, accelerator=None, arg=None): - """ add a button to a toolbar """ + def _add_button(self, name, tooltip, callback, toolbar, accelerator=None, + arg=None): + ''' Add a button to a toolbar. ''' button = ToolButton(name) button.set_tooltip(tooltip) if arg is None: @@ -810,8 +829,11 @@ class TurtleArtActivity(activity.Activity): except AttributeError: pass button.show() - if hasattr(toolbar, 'insert'): # the main toolbar + if hasattr(toolbar, 'insert'): # Add button to the main toolbar... toolbar.insert(button, -1) - else: # or a secondary toolbar + else: # ...or a secondary toolbar. toolbar.props.page.insert(button, -1) + + if not name in help_strings: + help_strings[name] = tooltip return button diff --git a/activity/activity.info b/activity/activity.info index 151339c..14b5b59 100755..100644 --- a/activity/activity.info +++ b/activity/activity.info @@ -1,6 +1,6 @@ [Activity] name = Turtle Art -activity_version = 106 +activity_version = 109 license = MIT bundle_id = org.laptop.TurtleArtActivity exec = sugar-activity TurtleArtActivity.TurtleArtActivity diff --git a/collaboration/neighborhood.py b/collaboration/neighborhood.py index b587bc8..192b66f 100755 --- a/collaboration/neighborhood.py +++ b/collaboration/neighborhood.py @@ -1,8 +1,21 @@ #!/usr/bin/python +# Copyright (C) 2007, Red Hat, Inc. +# Copyright (C) 2010-11 Collabora Ltd. # -# neighborhood.py +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. # +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. # +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. from functools import partial from hashlib import sha1 diff --git a/collaboration/presenceservice.py b/collaboration/presenceservice.py index 142fc4a..33caadd 100644 --- a/collaboration/presenceservice.py +++ b/collaboration/presenceservice.py @@ -95,7 +95,7 @@ class PresenceService(gobject.GObject): name = 'org.freedesktop.Telepathy.Error.NotAvailable' if e.get_dbus_name() == name: logging.debug("There's no shared activity with the id " - "%s", activity_id) + "%s" % (activity_id)) else: raise else: @@ -166,8 +166,8 @@ class PresenceService(gobject.GObject): dbus_interface=CONNECTION) return self.get_buddy(account_path, contact_ids[0]) - raise ValueError('Unknown buddy in connection %s with handle %d', - tp_conn_path, handle) + raise ValueError('Unknown buddy in connection %s with handle %d' % \ + (tp_conn_path, handle)) def get_owner(self): """Retrieves the laptop Buddy object.""" @@ -203,8 +203,8 @@ class PresenceService(gobject.GObject): properties['private'] = private if self._activity_cache is not None: - raise ValueError('Activity %s is already tracked', - activity.get_id()) + raise ValueError('Activity %s is already tracked' % \ + (activity.get_id())) connection_manager = get_connection_manager() account_path, connection = \ @@ -220,8 +220,8 @@ class PresenceService(gobject.GObject): self._activity_cache = shared_activity if shared_activity.props.joined: - raise RuntimeError('Activity %s is already shared.' % - activity.props.id) + raise RuntimeError('Activity %s is already shared.' % \ + (activity.props.id)) shared_activity.share(self.__share_activity_cb, self.__share_activity_error_cb) diff --git a/collaboration/telepathyclient.py b/collaboration/telepathyclient.py index 5491530..e00f053 100644 --- a/collaboration/telepathyclient.py +++ b/collaboration/telepathyclient.py @@ -18,10 +18,11 @@ import logging import dbus from dbus import PROPERTIES_IFACE + from telepathy.interfaces import CLIENT, \ - CLIENT_APPROVER, \ - CLIENT_HANDLER, \ - CLIENT_INTERFACE_REQUESTS + CLIENT_APPROVER, \ + CLIENT_HANDLER, \ + CLIENT_INTERFACE_REQUESTS from telepathy.server import DBusProperties import dispatch @@ -34,6 +35,7 @@ _instance = None class TelepathyClient(dbus.service.Object, DBusProperties): + def __init__(self): self._interfaces = set([CLIENT, CLIENT_HANDLER, CLIENT_INTERFACE_REQUESTS, PROPERTIES_IFACE, diff --git a/extra/plugin.py b/extra/plugin.py deleted file mode 100644 index fe95f95..0000000 --- a/extra/plugin.py +++ /dev/null @@ -1,10 +0,0 @@ - -import gobject - -class Plugin(gobject.GObject): - def __init__(self): - gobject.GObject.__init__(self) - - def get_menu(self): - raise RuntimeError("You need to define get_menu for your plugin.") - diff --git a/devices/__init__.py b/gnome_plugins/__init__.py index e69de29..e69de29 100644 --- a/devices/__init__.py +++ b/gnome_plugins/__init__.py diff --git a/extra/collaborationplugin.py b/gnome_plugins/collaboration_plugin.py index 56fcae5..19c084b 100644 --- a/extra/collaborationplugin.py +++ b/gnome_plugins/collaboration_plugin.py @@ -1,28 +1,56 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +#Copyright (c) 2011 Collabora Ltd. + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. import sys sys.path.append("..") +import os.path import dbus from gettext import gettext as _ import gobject import gtk + from plugin import Plugin + from util.menubuilder import MenuBuilder from util.configfile import ConfigFile from util.configwizard import ConfigWizard + import telepathy from collaboration.neighborhood import get_neighborhood from collaboration.connectionmanager import get_connection_manager from collaboration.activity import Activity from collaboration import telepathyclient from collaboration.tubeconn import TubeConnection -import traceback + from TurtleArt.tacollaboration import Collaboration +import traceback + CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \ 'org.laptop.Telepathy.ActivityProperties' -class CollaborationPlugin(Plugin): + +class Collaboration_plugin(Plugin): __gsignals__ = { 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, @@ -31,56 +59,76 @@ class CollaborationPlugin(Plugin): ()), } - # Using activity here is kinda misleading cause the - # obj we are receiving is not a Sugar activity. - def __init__(self, activity, config_file_path): + def __init__(self, parent): Plugin.__init__(self) - self._activity = activity + self._parent = parent self._neighborhood = None - self._title = "My Turtle Art session" + self._title = _('My Turtle Art session') self._bundle_id = "org.laptop.TurtleArt" - self._activity_id = "1234567" # This could be hashed from the file path (if resuming) + # This could be hashed from the file path (if resuming) + self._activity_id = "1234567" self._nick = "" - self._config_file_path = config_file_path + self._setup_has_been_called = False + + def _setup_config_file(self, config_file_path): + self._config_file_path = os.path.join(config_file_path, + 'turtleartrc.collab') self._collaboration_config_values = ConfigFile(self._config_file_path) - valid_config_values = { - "nick" : { "type" : "text"}, - "account_id" : { "type" : "text"}, - "password" : { "type" : "text"}, - "server" : { "type" : "text"}, - "port" : { "type" : "integer"}, - "register": { "type" : "boolean"}, - "turtle_color" : { "type" : "text"}, - "colors" : { "type" : "text"} + self._valid_config_values = { + 'nick': {'type': 'text'}, + 'account_id': {'type': 'text'}, + 'password': {'type': 'text'}, + 'server': {'type': 'text'}, + 'port': {'type': 'integer'}, + 'register': {'type': 'boolean'}, + 'colors': {'type': 'text'} } - self._collaboration_config_values.set_valid_keys(valid_config_values) - self._collaboration_config_values.connect("configuration-loaded", self._connect_to_neighborhood) - self._collaboration_config_values.connect("configuration-saved", self._connect_to_neighborhood) + + def _connect_cb(self, button): + """ Enable connection """ + self._collaboration_config_values.set_valid_keys( + self._valid_config_values) + self._collaboration_config_values.connect( + 'configuration-loaded', self._connect_to_neighborhood) + self._collaboration_config_values.connect( + 'configuration-saved', self._connect_to_neighborhood) self._collaboration_config_values.load() + self.setup() def setup(self): self._collaboration = Collaboration(self.tw, self) self._collaboration.setup() + # Do we know if we were successful? + self._setup_has_been_called = True + # TODO: + # use set_sensitive to enable Share and Configuration menuitems def set_tw(self, turtleart_window): self.tw = turtleart_window self.tw.nick = self._get_nick() + self._setup_config_file(self._parent.get_config_home()) def get_menu(self): menu = gtk.Menu() + MenuBuilder.make_menu_item(menu, _('Enable collaboration'), + self._connect_cb) + self._activities_submenu = gtk.Menu() - activities_menu = MenuBuilder.make_sub_menu(self._activities_submenu, _('Activities')) + activities_menu = MenuBuilder.make_sub_menu(self._activities_submenu, + _('Activities')) menu.append(activities_menu) self._buddies_submenu = gtk.Menu() - buddies_menu = MenuBuilder.make_sub_menu(self._buddies_submenu, _('Buddies')) + buddies_menu = MenuBuilder.make_sub_menu(self._buddies_submenu, + _('Buddies')) menu.append(buddies_menu) - + MenuBuilder.make_menu_item(menu, _('Share'), self._share_cb) - MenuBuilder.make_menu_item(menu, _('Configuration'), self._config_neighborhood_cb) - + MenuBuilder.make_menu_item(menu, _('Configuration'), + self._config_neighborhood_cb) + neighborhood_menu = MenuBuilder.make_sub_menu(menu, _('Neighborhood')) return neighborhood_menu @@ -99,40 +147,42 @@ class CollaborationPlugin(Plugin): def _get_title(self): return self._title - - def _get_turtle_color(self): - return self._turtle_color def _connect_to_neighborhood(self, config_file_obj): if self._neighborhood is not None: return - print "_connect_to_neighborhood has been called" - params = {} - params["nickname"] = self._collaboration_config_values.get("nick") - params["account_id"] = self._collaboration_config_values.get("account_id") - params["server"] = self._collaboration_config_values.get("server") - params["port"] = self._collaboration_config_values.get("port") - params["password"] = self._collaboration_config_values.get("password") - params["register"] = self._collaboration_config_values.get("register") - if params["server"] == "": - raise RuntimeError("Invalid server address") - - self._nick = self._collaboration_config_values.get("nick") - self._colors = self._collaboration_config_values.get("colors") - self._turtle_color = self._collaboration_config_values.get("turtle_color") + params['nickname'] = self._collaboration_config_values.get('nick') + params['account_id'] = self._collaboration_config_values.get( + 'account_id') + params['server'] = self._collaboration_config_values.get('server') + params['port'] = self._collaboration_config_values.get('port') + params['password'] = self._collaboration_config_values.get('password') + params['register'] = self._collaboration_config_values.get('register') + if params['server'] == '': + raise RuntimeError('Invalid server address') + + self._nick = self._collaboration_config_values.get('nick') + # Tell the parent activity that the nick may have changed + self._parent.nick_changed(self._nick) + + self._colors = self._collaboration_config_values.get('colors') + # Tell the parent activity that the colors may have changed + self._parent.color_changed(self._colors) self._activities = {} self._buddies = {} - print "connecting to the neighborhood" dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - + self._client_handler = telepathyclient.get_instance() + if self._client_handler == None: + raise RuntimeError('Telepathy client unavailable') self._neighborhood = get_neighborhood(params) self._neighborhood.connect('activity-added', self._activity_added_cb) - self._neighborhood.connect('activity-removed', self._activity_removed_cb) + self._neighborhood.connect('activity-removed', + self._activity_removed_cb) self._neighborhood.connect('buddy-added', self._buddy_added_cb) self._neighborhood.connect('buddy-removed', self._buddy_removed_cb) @@ -147,7 +197,7 @@ class CollaborationPlugin(Plugin): try: self._activities.pop(activity_model.props.name) except: - print "Failed to remove activity %s" % activity_model.props.name + print 'Failed to remove activity %s' % activity_model.props.name self._recreate_available_activities_menu() @@ -162,7 +212,7 @@ class CollaborationPlugin(Plugin): print "Couldn't remove buddy %s" % buddy.get_key() self._recreate_available_buddies_menu() - # TODO: we should have a list of available actions over + # TODO: we should have a list of available actions over # a given buddy. I.e.: a) chat with him b) make friend # c) invite to current activity # @@ -173,26 +223,28 @@ class CollaborationPlugin(Plugin): for buddy in self._buddies.values(): key = buddy.get_key() if key is None: - key = "" - n = buddy.get_nick() + "|" + key[0:15] - MenuBuilder.make_menu_item(self._buddies_submenu, n, self._buddy_actions_cb, buddy) + key = '' + n = buddy.get_nick() + '|' + key[0:15] + MenuBuilder.make_menu_item(self._buddies_submenu, n, + self._buddy_actions_cb, buddy) def _buddy_actions_cb(self, widget, buddy): - print "do something with %s" % buddy.get_nick() + print 'do something with %s' % buddy.get_nick() - # TODO - # - we need an extra menu branch with a) 'Join' button b) List of buddies + # TODO: + # we need an extra menu branch with a) 'Join' button b) List of buddies def _recreate_available_activities_menu(self): for child in self._activities_submenu.get_children(): self._activities_submenu.remove(child) for activity in self._activities.values(): n = activity.props.name - MenuBuilder.make_menu_item(self._activities_submenu, n, self._join_activity_cb, activity) + MenuBuilder.make_menu_item(self._activities_submenu, n, + self._join_activity_cb, activity) def _join_activity_cb(self, widget, activity): - print "Lets try to join..." - + print 'Lets try to join...' + connection_manager = get_connection_manager() account_path, connection = \ connection_manager.get_preferred_connection() @@ -201,17 +253,17 @@ class CollaborationPlugin(Plugin): return properties = {} - properties["id"] = activity.activity_id - properties["color"] = activity.get_color() - print "room handle according to activity %s" % activity.room_handle - properties["private"] = True + properties['id'] = activity.activity_id + properties['color'] = activity.get_color() + print 'room handle according to activity %s' % activity.room_handle + properties['private'] = True try: room_handle = connection.GetActivity(activity.activity_id, - dbus_interface=CONNECTION_INTERFACE_ACTIVITY_PROPERTIES) - print("room_handle = %s" % str(room_handle)) - self._joined_activity = Activity(account_path, connection, room_handle, - properties=properties) + dbus_interface=CONNECTION_INTERFACE_ACTIVITY_PROPERTIES) + print('room_handle = %s' % str(room_handle)) + self._joined_activity = Activity( + account_path, connection, room_handle, properties=properties) # FIXME: this should be unified, no need to keep 2 references self._shared_activity = self._joined_activity except: @@ -224,32 +276,42 @@ class CollaborationPlugin(Plugin): _join_id = self._joined_activity.connect('joined', self.__joined_cb) self._joined_activity.join() - def __joined_cb(self,activity, success, err): + def __joined_cb(self, activity, success, err): print "We've joined an activity" self.emit('joined') def _config_neighborhood_cb(self, widget): + if not self._setup_has_been_called: + return config_w = ConfigWizard(self._config_file_path) config_items = [ - {"item_label" : _("Nickname"), "item_type" : "text", "item_name" : "nick" }, - { "item_label" : _("Account ID"), "item_type" : "text", "item_name" : "account_id" }, - { "item_label" : _("Server"), "item_type" : "text", "item_name" : "server" }, - { "item_label" : _("Port"), "item_type" : "text", "item_name" : "port" }, - { "item_label" : _("Password"), "item_type" : "text", "item_name" : "password" }, - { "item_label" : _("Register"), "item_type" : "boolean", "item_name" : "register" }, - { "item_label" : _("Colors"), "item_type" : "text", "item_name" : "colors" }, - { "item_label" : _("Turtle Color"), "item_type" : "text", "item_name" : "turtle_color" } + {'item_label': _('Nickname'), 'item_type': 'text', + 'item_name': 'nick'}, + {'item_label': _('Account ID'), 'item_type': 'text', + 'item_name': 'account_id'}, + {'item_label': _('Server'), 'item_type': 'text', + 'item_name': 'server'}, + {'item_label': _('Port'), 'item_type': 'text', + 'item_name': 'port'}, + {'item_label': _('Password'), 'item_type': 'text', + 'item_name': 'password'}, + {'item_label': _('Register'), 'item_type': 'boolean', + 'item_name': 'register'}, + {'item_label': _('Colors'), 'item_type': 'text', + 'item_name': 'colors'} ] config_w.set_config_items(config_items) config_w.set_config_file_obj(self._collaboration_config_values) config_w.show() def _share_cb(self, button): + if not self._setup_has_been_called: + return properties = {} properties['id'] = self._get_activity_id() properties['type'] = self._get_bundle_id() properties['name'] = self._get_title() - properties['color'] = self._get_turtle_color() + properties['color'] = self.get_colors() properties['private'] = False connection_manager = get_connection_manager() @@ -261,18 +323,19 @@ class CollaborationPlugin(Plugin): return try: - self._activity._shared_activity = Activity(account_path, connection, - properties=properties) + self._parent._shared_activity = Activity(account_path, + connection, + properties=properties) # FIXME: this should be unified, no need to keep 2 references - self._shared_activity = self._activity._shared_activity + self._shared_activity = self._parent._shared_activity except: traceback.print_exc(file=sys.stdout) - if self._activity._shared_activity.props.joined: + if self._parent._shared_parent.props.joined: raise RuntimeError('Activity %s is already shared.' % - self._activity._get_activity_id()) + self._parent._get_activity_id()) - self._activity._shared_activity.share(self.__share_activity_cb, + self._parent._shared_parent.share(self.__share_activity_cb, self.__share_activity_error_cb) def __share_activity_cb(self, activity): @@ -280,10 +343,8 @@ class CollaborationPlugin(Plugin): self.emit('shared') def __share_activity_error_cb(self, activity, error): - """Notify with GObject event of unsuccessful sharing of activity - """ - print "%s got error: %s" % (activity, error) - -if __name__ == "__main__": - print "testing collaboration" + """Notify with GObject event of unsuccessful sharing of activity""" + print '%s got error: %s' % (activity, error) +if __name__ == '__main__': + print 'testing collaboration' diff --git a/TurtleArt/tacamera.py b/gnome_plugins/plugin.py index 2177288..590c9bc 100644 --- a/TurtleArt/tacamera.py +++ b/gnome_plugins/plugin.py @@ -1,6 +1,6 @@ -# -*- coding: utf-8 -*- -#Copyright (c) 2010, Walter Bender -#Copyright (c) 2010, Tony Forster +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +#Copyright (c) 2011 Collabora Ltd. #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal @@ -20,23 +20,15 @@ #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. -import gst, time +import gobject -GST_PIPE = ['v4l2src', 'ffmpegcolorspace', 'pngenc'] -class Camera(): - """ A class for representing the video camera """ +class Plugin(gobject.GObject): + def __init__(self): + gobject.GObject.__init__(self) - def __init__(self, imagepath): - GST_PIPE.append('filesink location=%s' % imagepath) - self.pipe = gst.parse_launch('!'.join(GST_PIPE)) - self.bus = self.pipe.get_bus() - - def save_camera_input_to_file(self): - """ Grab a frame from the camera """ - self.pipe.set_state(gst.STATE_PLAYING) - self.bus.poll(gst.MESSAGE_EOS, -1) - - def stop_camera_input(self): - self.pipe.set_state(gst.STATE_NULL) + def get_menu(self): + raise RuntimeError("You need to define get__menu for your plugin.") + def set_tw(self, turtle_window=None): + raise RuntimeError("You need to define set_tw for your plugin.") diff --git a/extra/upload.py b/gnome_plugins/uploader_plugin.py index e39d099..06224fa 100644 --- a/extra/upload.py +++ b/gnome_plugins/uploader_plugin.py @@ -1,3 +1,26 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +#Copyright (c) 2010 Jamie Boisture +#Copyright (c) 2011 Collabora Ltd. + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + #!/usr/bin/python try: @@ -8,14 +31,21 @@ except ImportError, e: print "Import Error: %s. Project upload is disabled." % (e) _UPLOAD_AVAILABLE = False +import os import gtk + +from plugin import Plugin +from util.menubuilder import MenuBuilder + from gettext import gettext as _ -class Uploader(): + +class Uploader_plugin(Plugin): MAX_FILE_SIZE = 950000 UPLOAD_SERVER = 'http://turtleartsite.appspot.com' - def __init__(self, upload_server = None, max_file_size = 0): + def __init__(self, parent, upload_server=None, max_file_size=None): + self._parent = parent self.uploading = False if upload_server is None: @@ -23,14 +53,23 @@ class Uploader(): if max_file_size is None: self._max_file_size = self.MAX_FILE_SIZE + else: + self._max_file_size = max_file_size def set_tw(self, turtleart_window): self.tw = turtleart_window + def get_menu(self): + menu = gtk.Menu() + MenuBuilder.make_menu_item(menu, _('Upload to Web'), + self.do_upload_to_web) + upload_menu = MenuBuilder.make_sub_menu(menu, _('Upload')) + return upload_menu + def enabled(self): return _UPLOAD_AVAILABLE - def do_upload_to_web(self, widget = None): + def do_upload_to_web(self, widget=None): if self.uploading: return @@ -127,14 +166,14 @@ http://turtleartsite.sugarlabs.org to upload your project.')) tafile, imagefile = self.tw.save_for_upload(title) # Set a maximum file size for image to be uploaded. - if int(os.path.getsize(imagefile)) > self.max_file_size: + if int(os.path.getsize(imagefile)) > self._max_file_size: import Image while int(os.path.getsize(imagefile)) > self._max_file_size: big_file = Image.open(imagefile) smaller_file = big_file.resize(int(0.9 * big_file.size[0]), int(0.9 * big_file.size[1]), Image.ANTIALIAS) - smaller_file.save(imagefile, quality = 100) + smaller_file.save(imagefile, quality=100) c = pycurl.Curl() c.setopt(c.POST, 1) diff --git a/icons/blocksoff.svg b/icons/blocksoff.svg index f0e10b3..e7db290 100644 --- a/icons/blocksoff.svg +++ b/icons/blocksoff.svg @@ -2,13 +2,29 @@ + + + + image/svg+xml + + + + + + - - + + + + + + diff --git a/icons/blockson.svg b/icons/blockson.svg index df19fdb..4435cc2 100644 --- a/icons/blockson.svg +++ b/icons/blockson.svg @@ -2,89 +2,29 @@ + + + + image/svg+xml + + + + + - - - - - - - - - - - - + id="defs4" /> + id="g3788"> + d="m 15.719636,31.331478 0.114372,5.261133 11.437247,6.290486 L 27.5,29.387146" + id="path2463" + style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#804000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + d="m 27.728744,29.501519 0.05719,13.381578 11.723178,-7.548583 0,-4.689272 -5.947368,3.545548" + id="path2465" + style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#804000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + + diff --git a/icons/colorsoff.svg b/icons/colorsoff.svg index 19f2cd5..c023cf3 100644 --- a/icons/colorsoff.svg +++ b/icons/colorsoff.svg @@ -2,12 +2,27 @@ + + + + image/svg+xml + + + + + - - - - - - - + + + + + + + + diff --git a/icons/colorson.svg b/icons/colorson.svg index 9cd4ef1..42ce7c7 100644 --- a/icons/colorson.svg +++ b/icons/colorson.svg @@ -2,12 +2,27 @@ + + + + image/svg+xml + + + + + - - - - - - - + transform="translate(-4.9972534e-4,0)" + id="toolbar_x5F_colors" + style="display:block"> + + + + + + diff --git a/icons/extrasoff.svg b/icons/extrasoff.svg deleted file mode 100644 index 47547fc..0000000 --- a/icons/extrasoff.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - diff --git a/icons/extrason.svg b/icons/extrason.svg deleted file mode 100644 index 3d2cd85..0000000 --- a/icons/extrason.svg +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - diff --git a/icons/view-metric.svg b/icons/view-metric.svg new file mode 100644 index 0000000..c91675d --- /dev/null +++ b/icons/view-metric.svg @@ -0,0 +1,47 @@ + + + + + + + + CM + + + diff --git a/images/cameraoff.svg b/images/cameraoff.svg index 3c1fb7b..54d9c86 100755..100644 --- a/images/cameraoff.svg +++ b/images/cameraoff.svg @@ -1,37 +1,15 @@ - - - -image/svg+xml - - - - - - - - \ No newline at end of file + + +]> + + + + + + + + + + + \ No newline at end of file diff --git a/images/camerasmall.svg b/images/camerasmall.svg index 0c3aa44..690e940 100755 --- a/images/camerasmall.svg +++ b/images/camerasmall.svg @@ -8,9 +8,9 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" - width="26.163" - height="14.262004" - viewBox="0 0 26.163 14.262004" + width="26" + height="18.668091" + viewBox="0 0 26 18.668091" id="svg2" xml:space="preserve"> \ No newline at end of file diff --git a/images/metric.svg b/images/metric.svg new file mode 100644 index 0000000..33f6d3d --- /dev/null +++ b/images/metric.svg @@ -0,0 +1,489 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2 + 3 + 5 + + 5 + + 4 + + 3 + + 2 + + 1 + + –1 + + –2 + + –3 + + –4 + –1 + –2 + –3 + –4 + 0 + (–7, 5) + (7, 5) + (7, –5) + (–7, –5) + + + 4 + + –5 + + + + + –6 + + + 6 + + 7 + + –5 + + –7 + diff --git a/extra/__init__.py b/plugins/__init__.py index e69de29..e69de29 100644 --- a/extra/__init__.py +++ b/plugins/__init__.py diff --git a/devices/__init__.py b/plugins/audio_sensors/__init__.py index e69de29..e69de29 100644 --- a/devices/__init__.py +++ b/plugins/audio_sensors/__init__.py diff --git a/plugins/audio_sensors/audio_sensors.py b/plugins/audio_sensors/audio_sensors.py new file mode 100644 index 0000000..22298a0 --- /dev/null +++ b/plugins/audio_sensors/audio_sensors.py @@ -0,0 +1,321 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from gettext import gettext as _ + +try: + from numpy import append + from numpy.fft import rfft + PITCH_AVAILABLE = True +except: + PITCH_AVAILABLE = False + +from plugins.plugin import Plugin + +from plugins.audio_sensors.audiograb import AudioGrab_Unknown, AudioGrab_XO1, \ + AudioGrab_XO15, SENSOR_DC_NO_BIAS, SENSOR_DC_BIAS + +from plugins.audio_sensors.ringbuffer import RingBuffer1d + +from TurtleArt.tapalette import make_palette +from TurtleArt.taconstants import XO1, XO15 +from TurtleArt.talogo import primitive_dictionary +from TurtleArt.tautils import debug_output + +import logging +_logger = logging.getLogger('turtleart-activity audio sensors plugin') + + +def _avg(array, abs_value=False): + ''' Calc. the average value of an array ''' + if len(array) == 0: + return 0 + array_sum = 0 + if abs_value: + for a in array: + array_sum += abs(a) + else: + for a in array: + array_sum += a + return float(array_sum) / len(array) + + +class Audio_sensors(Plugin): + + def __init__(self, parent): + self._parent = parent + self._status = True # TODO: test for audio device + # These flags are referenced by audiograb + self.hw = self._parent.hw + self.running_sugar = self._parent.running_sugar + + def setup(self): + # set up audio-sensor-specific blocks + self.max_samples = 1500 + self.input_step = 1 + + self.ringbuffer = RingBuffer1d(self.max_samples, dtype='int16') + + palette = make_palette('sensor', + colors=["#FF6060", "#A06060"], + help_string=_('Palette of sensor blocks')) + + primitive_dictionary['sound'] = self.prim_sound + primitive_dictionary['volume'] = self.prim_volume + if self._status: + palette.add_block('sound', + style='box-style', + label=_('sound'), + help_string=_('raw microphone input signal'), + value_block=True, + prim_name='sound') + + palette.add_block('volume', + style='box-style', + label=_('loudness'), + help_string=_('microphone input volume'), + value_block=True, + prim_name='volume') + else: + palette.add_block('sound', + hidden=True, + style='box-style', + label=_('sound'), + help_string=_('raw microphone input signal'), + value_block=True, + prim_name='sound') + palette.add_block('volume', + hidden=True, + style='box-style', + label=_('loudness'), + help_string=_('microphone input volume'), + value_block=True, + prim_name='volume') + + self._parent.lc.def_prim( + 'sound', 0, lambda self: primitive_dictionary['sound']()) + self._parent.lc.def_prim( + 'volume', 0, lambda self: primitive_dictionary['volume']()) + + primitive_dictionary['pitch'] = self.prim_pitch + if PITCH_AVAILABLE and self._status: + palette.add_block('pitch', + style='box-style', + label=_('pitch'), + help_string=_('microphone input pitch'), + value_block=True, + prim_name='pitch') + else: + palette.add_block('pitch', + hidden=True, + style='box-style', + label=_('pitch'), + help_string=_('microphone input pitch'), + value_block=True, + prim_name='pitch') + self._parent.lc.def_prim('pitch', 0, + lambda self: primitive_dictionary['pitch']()) + + primitive_dictionary['resistance'] = self.prim_resistance + primitive_dictionary['voltage'] = self.prim_voltage + if self.hw in [XO1, XO15] and self._status: + if self.hw == XO1: + self.voltage_gain = 0.00002225 + self.voltage_bias = 1.140 + elif self.hw == XO15: + self.voltage_gain = -0.0001471 + self.voltage_bias = 1.695 + palette.add_block('resistance', + style='box-style', + label=_('resistance'), + help_string=_('microphone input resistance'), + value_block=True, + prim_name='resistance') + palette.add_block('voltage', + style='box-style', + label=_('voltage'), + help_string=_('microphone input voltage'), + value_block=True, + prim_name='voltage') + else: + palette.add_block('resistance', + hidden=True, + style='box-style', + label=_('resistance'), + help_string=_('microphone input resistance'), + value_block=True, + prim_name='resistance') + palette.add_block('voltage', + hidden=True, + style='box-style', + label=_('voltage'), + help_string=_('microphone input voltage'), + value_block=True, + prim_name='resistance') + self._parent.lc.def_prim( + 'resistance', 0, lambda self: primitive_dictionary['resistance']()) + self._parent.lc.def_prim( + 'voltage', 0, lambda self: primitive_dictionary['voltage']()) + + self.audio_started = False + + def start(self): + # This gets called by the start button + if not self._status: + return + ''' Start grabbing audio if there is an audio block in use ''' + if len(self._parent.block_list.get_similar_blocks('block', + ['volume', 'sound', 'pitch', 'resistance', 'voltage'])) > 0: + if self.audio_started: + self.audiograb.resume_grabbing() + else: + if self.hw == XO15: + self.audiograb = AudioGrab_XO15(self.new_buffer, self) + elif self.hw == XO1: + self.audiograb = AudioGrab_XO1(self.new_buffer, self) + else: + self.audiograb = AudioGrab_Unknown(self.new_buffer, self) + self.audiograb.start_grabbing() + self.audio_started = True + self._update_audio_mode() + + def new_buffer(self, buf): + ''' Append a new buffer to the ringbuffer ''' + self.ringbuffer.append(buf) + return True + + def _update_audio_mode(self): + ''' If there are sensor blocks, set the appropriate audio mode ''' + if not hasattr(self._parent.lc, 'value_blocks_to_update'): + return + for name in ['sound', 'volume', 'pitch']: + if name in self._parent.lc.value_blocks_to_update: + if len(self._parent.lc.value_blocks_to_update[name]) > 0: + self.audiograb.set_sensor_type() + return + if 'resistance' in self._parent.lc.value_blocks_to_update: + if len(self._parent.lc.value_blocks_to_update['resistance']) > 0: + self.audiograb.set_sensor_type(SENSOR_DC_BIAS) + return + if 'voltage' in self._parent.lc.value_blocks_to_update: + if len(self._parent.lc.value_blocks_to_update['voltage']) > 0: + self.audiograb.set_sensor_type(SENSOR_DC_NO_BIAS) + return + + def stop(self): + # This gets called by the stop button + if self._status and self.audio_started: + self.audiograb.pause_grabbing() + + def goto_background(self): + # This gets called when your process is sent to the background + # TODO: handle this case + pass + + def return_to_foreground(self): + # This gets called when your process returns from the background + # TODO: handle this case + pass + + def quit(self): + # This gets called by the quit button + if self._status and self.audio_started: + self.audiograb.stop_grabbing() + + def _status_report(self): + debug_output('Reporting audio sensor status: %s' % (str(self._status))) + return self._status + + # Block primitives used in talogo + + def prim_volume(self): + ''' return mic in value ''' + #TODO: Adjust gain for different HW + if not self._status: + return 0 + buf = self.ringbuffer.read(None, self.input_step) + if len(buf) > 0: + volume = float(_avg(buf, abs_value=True)) + self._parent.lc.update_label_value('volume', volume) + return volume + else: + return 0 + + def prim_sound(self): + ''' return raw mic in value ''' + if not self._status: + return 0 + buf = self.ringbuffer.read(None, self.input_step) + if len(buf) > 0: + sound = float(buf[0]) + self._parent.lc.update_label_value('sound', sound) + return sound + else: + return 0 + + def prim_pitch(self): + ''' return index of max value in fft of mic in values ''' + if not PITCH_AVAILABLE or not self._status: + return 0 + buf = [] + for i in range(4): + buf = append(buf, self.ringbuffer.read(None, self.input_step)) + if len(buf) > 0: + r = [] + for j in rfft(buf): + r.append(abs(j)) + # Convert output to Hertz + pitch = r.index(max(r)) * 48000 / len(buf) + self._parent.lc.update_label_value('pitch', pitch) + return pitch + else: + return 0 + + def prim_resistance(self): + ''' return resistance sensor value ''' + if not self.hw in [XO1, XO15] or not self._status: + return 0 + buf = self.ringbuffer.read(None, self.input_step) + if len(buf) > 0: + # See + # TODO: test this calibration on XO 1.5 + if self.hw == XO1: + resistance = 2.718 ** ((float(_avg(buf)) * 0.000045788) + \ + 8.0531) + else: + avg_buf = float(_avg(buf)) + if avg_buf > 0: + resistance = (420000000 / avg_buf) - 13500 + else: + resistance = 420000000 + self._parent.lc.update_label_value('resistance', resistance) + return resistance + else: + return 0 + + def prim_voltage(self): + ''' return voltage sensor value ''' + if not self.hw in [XO1, XO15] or not self._status: + return 0 + buf = self.ringbuffer.read(None, self.input_step) + if len(buf) > 0: + # See + voltage = float(_avg(buf)) * self.voltage_gain + self.voltage_bias + self._parent.lc.update_label_value('voltage', voltage) + return voltage + else: + return 0 diff --git a/TurtleArt/audiograb.py b/plugins/audio_sensors/audiograb.py index 3ecdc11..84e0c8d 100644 --- a/TurtleArt/audiograb.py +++ b/plugins/audio_sensors/audiograb.py @@ -21,6 +21,11 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +SENSOR_AC_NO_BIAS = 'external' +SENSOR_AC_BIAS = 'sound' +SENSOR_DC_NO_BIAS = 'voltage' +SENSOR_DC_BIAS = 'resistance' + import pygst import gst import gst.interfaces @@ -43,25 +48,19 @@ QUIT_DC_MODE_ENABLE = False QUIT_CAPTURE_GAIN = 100 QUIT_BIAS = True -import logging - -_logger = logging.getLogger('TurtleArt') -_logger.setLevel(logging.DEBUG) -logging.basicConfig() - -from taconstants import SENSOR_AC_NO_BIAS, SENSOR_AC_BIAS, SENSOR_DC_NO_BIAS, \ - SENSOR_DC_BIAS, XO1 +from TurtleArt.taconstants import XO1 +from TurtleArt.tautils import debug_output class AudioGrab: """ The interface between measure and the audio device """ - def __init__(self, callable1, activity): + def __init__(self, callable1, parent): """ Initialize the class: callable1 is a data buffer; - activity is the parent class""" + parent is the parent class""" self.callable1 = callable1 - self.activity = activity + self.parent = parent self.sensor = None self.temp_buffer = [0] @@ -105,8 +104,9 @@ class AudioGrab: # Query the available controls try: # F11+ - _logger.debug('controls: %r', [t.props.untranslated_label \ - for t in self._mixer.list_tracks()]) + debug_output('controls: %r' % \ + ([t.props.untranslated_label for t in self._mixer.list_tracks()]), + self.parent.running_sugar) self._dc_control = self._find_control(['dc mode']) self._mic_bias_control = self._find_control(['mic bias', 'dc input bias', @@ -149,7 +149,7 @@ class AudioGrab: """The function that is called whenever new data is available This is the signal handler for the handoff signal""" if buffer is None: - _logger.debug('audiograb buffer is None') + debug_output('audiograb buffer is None', self.parent.running_sugar) return False temp_buffer = fromstring(buffer, 'int16') @@ -234,8 +234,9 @@ class AudioGrab: controls.sort(key=lambda e: e[1]) if controls: - _logger.debug("found control: %s" %\ - (str(controls[0][0].props.untranslated_label))) + debug_output('Found control: %s' % \ + (str(controls[0][0].props.untranslated_label)), + self.parent.running_sugar) return controls[0][0] return None @@ -262,8 +263,9 @@ class AudioGrab: return default value = bool(control.flags & gst.interfaces.MIXER_TRACK_MUTE) - _logger.debug('Getting %s (%s) mute status: %r', name, - control.props.untranslated_label, value) + debug_output('Getting %s (%s) mute status: %r' % (name, + control.props.untranslated_label, value), + self.parent.running_sugar) return value def _set_mute(self, control, name, value): @@ -272,8 +274,9 @@ class AudioGrab: return self._mixer.set_mute(control, value) - _logger.debug('Set mute for %s (%s) to %r', name, - control.props.untranslated_label, value) + debug_output('Set mute for %s (%s) to %r' % (name, + control.props.untranslated_label, value), + self.parent.running_sugar) def _get_volume(self, control, name): """Get volume of a control and convert to a scale of 0-100""" @@ -311,14 +314,14 @@ class AudioGrab: def mute_master(self): """Mutes the Master Control""" - if not self._hardwired and self.activity.hw != XO1: + if not self._hardwired and self.parent.hw != XO1: self._set_mute(self._master_control, 'Master', True) else: self.amixer_set('Master', False) def unmute_master(self): """Unmutes the Master Control""" - if not self._hardwired and self.activity.hw != XO1: + if not self._hardwired and self.parent.hw != XO1: self._set_mute(self._master_control, 'Master', True) else: self.amixer_set('Master', True) @@ -347,7 +350,7 @@ class AudioGrab: def set_bias(self, bias_state=False): """Enables / disables bias voltage.""" - if not self._hardwired and self.activity.hw != XO1: + if not self._hardwired and self.parent.hw != XO1: if self._mic_bias_control is None: return # if not isinstance(self._mic_bias_control, @@ -403,7 +406,7 @@ class AudioGrab: def set_dc_mode(self, dc_mode=False): """Sets the DC Mode Enable control pass False to mute and True to unmute""" - if not self._hardwired and self.activity.hw != XO1: + if not self._hardwired and self.parent.hw != XO1: if self._dc_control is not None: self._set_mute(self._dc_control, 'DC mode', not dc_mode) else: @@ -461,7 +464,8 @@ class AudioGrab: return self._get_mute(self._mic_boost_control, 'Mic Boost', False) current = self._mixer.get_volume(self._mic_boost_control) - _logger.debug('current: %s' % (str(current))) + debug_output('current: %s' % (str(current)), + self.parent.running_sugar) if current != self._mic_boost_control.min_volume: return True return False @@ -480,7 +484,7 @@ class AudioGrab: """Sets the Capture gain slider settings capture_val must be given as an integer between 0 and 100 indicating the percentage of the slider to be set""" - if not self._hardwired and self.activity.hw != XO1: + if not self._hardwired and self.parent.hw != XO1: if self._capture_control is not None: self._set_volume(self._capture_control, 'Capture', capture_val) else: @@ -506,7 +510,7 @@ class AudioGrab: """Sets the MIC gain slider settings mic_val must be given as an integer between 0 and 100 indicating the percentage of the slider to be set""" - if not self._hardwired and self.activity.hw != XO1: + if not self._hardwired and self.parent.hw != XO1: self._set_volume(self._mic_gain_control, 'Mic', mic_val) else: os.system("amixer set Mic " + str(mic_val) + "%") @@ -546,10 +550,9 @@ class AudioGrab: SENSOR_DC_BIAS: (True, True, 0, False) } mode, bias, gain, boost = PARAMETERS[sensor_type] - _logger.debug("====================================") - _logger.debug("Set Sensor Type to %s" % (str(sensor_type))) + debug_output('Set Sensor Type to %s' % (str(sensor_type)), + self.parent.running_sugar) self._set_sensor_type(mode, bias, gain, boost) - _logger.debug("====================================") def _set_sensor_type(self, mode=None, bias=None, gain=None, boost=None): """Helper to modify (some) of the sensor settings.""" @@ -598,11 +601,10 @@ class AudioGrab_XO15(AudioGrab): SENSOR_DC_NO_BIAS: (True, False, 80, False), SENSOR_DC_BIAS: (True, True, 90, False) } - _logger.debug("====================================") - _logger.debug("Set Sensor Type to %s" % (str(sensor_type))) + debug_output('Set Sensor Type to %s' % (str(sensor_type)), + self.parent.running_sugar) mode, bias, gain, boost = PARAMETERS[sensor_type] self._set_sensor_type(mode, bias, gain, boost) - _logger.debug("====================================") class AudioGrab_Unknown(AudioGrab): @@ -615,8 +617,7 @@ class AudioGrab_Unknown(AudioGrab): SENSOR_DC_NO_BIAS: (True, False, 80, False), SENSOR_DC_BIAS: (True, True, 90, False) } - _logger.debug("====================================") - _logger.debug("Set Sensor Type to %s" % (str(sensor_type))) + debug_output('Set Sensor Type to %s' % (str(sensor_type)), + self.parent.running_sugar) mode, bias, gain, boost = PARAMETERS[sensor_type] self._set_sensor_type(mode, bias, gain, boost) - _logger.debug("====================================") diff --git a/icons/sensoroff.svg b/plugins/audio_sensors/icons/sensoroff.svg index ec55f03..ec55f03 100644 --- a/icons/sensoroff.svg +++ b/plugins/audio_sensors/icons/sensoroff.svg diff --git a/icons/sensoron.svg b/plugins/audio_sensors/icons/sensoron.svg index e902656..e902656 100644 --- a/icons/sensoron.svg +++ b/plugins/audio_sensors/icons/sensoron.svg diff --git a/TurtleArt/ringbuffer.py b/plugins/audio_sensors/ringbuffer.py index 2afb5c9..2afb5c9 100644 --- a/TurtleArt/ringbuffer.py +++ b/plugins/audio_sensors/ringbuffer.py diff --git a/devices/__init__.py b/plugins/camera_sensor/__init__.py index e69de29..e69de29 100644 --- a/devices/__init__.py +++ b/plugins/camera_sensor/__init__.py diff --git a/plugins/camera_sensor/camera_sensor.py b/plugins/camera_sensor/camera_sensor.py new file mode 100644 index 0000000..a9f8684 --- /dev/null +++ b/plugins/camera_sensor/camera_sensor.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gst +import gtk +from fcntl import ioctl +import os +from gettext import gettext as _ + +from plugins.camera_sensor.tacamera import Camera +from plugins.camera_sensor.v4l2 import v4l2_control, V4L2_CID_AUTOGAIN, \ + VIDIOC_G_CTRL, VIDIOC_S_CTRL + +from plugins.plugin import Plugin + +from TurtleArt.tapalette import make_palette +from TurtleArt.talogo import media_blocks_dictionary, primitive_dictionary +from TurtleArt.tautils import get_path, debug_output + + +class Camera_sensor(Plugin): + + def __init__(self, parent): + self._parent = parent + self._status = False + + v4l2src = gst.element_factory_make('v4l2src') + if v4l2src.props.device_name is not None: + + if self._parent.running_sugar: + self._imagepath = get_path(self._parent.activity, + 'data/turtlepic.png') + else: + self._imagepath = '/tmp/turtlepic.png' + + self._camera = None + self._status = True + + def setup(self): + palette = make_palette('sensor', + colors=["#FF6060", "#A06060"], + help_string=_('Palette of sensor blocks')) + + # set up camera-specific blocks + primitive_dictionary['luminance'] = self.prim_read_camera + primitive_dictionary['read_camera'] = self.prim_read_camera + media_blocks_dictionary['camera'] = self.prim_take_picture + + if self._status: + palette.add_block('luminance', + style='box-style', + label=_('brightness'), + help_string=_('light level detected by camera'), + value_block=True, + prim_name='luminance') + self._parent.lc.def_prim('luminance', 0, + lambda self: primitive_dictionary['luminance']( + luminance_only=True)) + + # Depreciated block + palette.add_block('read_camera', + hidden=True, + style='box-style', + label=_('brightness'), + help_string=_('Average RGB color from camera \ +is pushed to the stack'), + value_block=True, + prim_name='luminance') + self._parent.lc.def_prim('read_camera', 0, + lambda self: primitive_dictionary['read_camera']()) + + palette.add_block('camera', + style='box-style-media', + label=' ', + default='CAMERA', + help_string=_('camera output'), + content_block=True) + else: # No camera, so blocks should do nothing + palette.add_block('luminance', + hidden=True, + style='box-style', + label=_('brightness'), + help_string=_('light level detected by camera'), + value_block=True, + prim_name='luminance') + self._parent.lc.def_prim('luminance', 0, + lambda self: primitive_dictionary['luminance']( + luminance_only=True)) + + # Depreciated block + palette.add_block('read_camera', + hidden=True, + style='box-style', + label=_('brightness'), + help_string=_('Average RGB color from camera \ +is pushed to the stack'), + value_block=True, + prim_name='luminance') + self._parent.lc.def_prim('read_camera', 0, + lambda self: primitive_dictionary['read_camera']()) + + palette.add_block('camera', + hidden=True, + style='box-style-media', + label=' ', + default='CAMERA', + help_string=_('camera output'), + content_block=True) + + def start(self): + ''' Initialize the camera if there is an camera block in use ''' + if len(self._parent.block_list.get_similar_blocks('block', + ['camera', 'read_camera', 'luminance'])) > 0: + if self._camera is None: + self._camera = Camera(self._imagepath) + + def stop(self): + ''' This gets called by the stop button ''' + if self._status and self._camera is not None: + self._camera.stop_camera_input() + + def _status_report(self): + debug_output('Reporting camera status: %s' % (str(self._status))) + return self._status + + # Block primitives used in talogo + + def prim_take_picture(self): + if self._status: + ''' method called by media block ''' + self._camera.save_camera_input_to_file() + self._camera.stop_camera_input() + self._parent.lc.filepath = self._imagepath + else: + self._parent.lc.filepath = os.path.join( + self._parent.path, 'samples', 'images', 'me.jpg') + + def prim_read_camera(self, luminance_only=False): + """ Read average pixel from camera and push b, g, r to the stack """ + if not self._status: + if luminance_only: + return -1 + else: + self._parent.lc.heap.append(-1) + self._parent.lc.heap.append(-1) + self._parent.lc.heap.append(-1) + return + + pixbuf = None + array = None + w = 4 + h = 3 + if self._status: + try: + self._video_capture_device = open('/dev/video0', 'rw') + except: + self._video_capture_device = None + debug_output('video capture device not available') + + if self._video_capture_device is not None: + self._ag_control = v4l2_control(V4L2_CID_AUTOGAIN) + try: + ioctl(self._video_capture_device, VIDIOC_G_CTRL, + self._ag_control) + self._ag_control.value = 0 # disable AUTOGAIN + ioctl(self._video_capture_device, VIDIOC_S_CTRL, + self._ag_control) + except: + # debug_output('AUTOGAIN control not available') + pass + + if self._video_capture_device is not None: + self._video_capture_device.close() + + self._camera.save_camera_input_to_file() + self._camera.stop_camera_input() + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self._imagepath, + w, h) + try: + array = pixbuf.get_pixels() + except: + array = None + + if array is not None: + length = len(array) / 3 + r, g, b, i = 0, 0, 0, 0 + for j in range(length): + r += ord(array[i]) + i += 1 + g += ord(array[i]) + i += 1 + b += ord(array[i]) + i += 1 + if luminance_only: + lum = int((r * 0.3 + g * 0.6 + b * 0.1) / length) + self._parent.lc.update_label_value('luminance', lum) + return lum + else: + self._parent.lc.heap.append(int((b / length))) + self._parent.lc.heap.append(int((g / length))) + self._parent.lc.heap.append(int((r / length))) + else: + if luminance_only: + return -1 + else: + self._parent.lc.heap.append(-1) + self._parent.lc.heap.append(-1) + self._parent.lc.heap.append(-1) diff --git a/icons/sensoroff.svg b/plugins/camera_sensor/icons/sensoroff.svg index ec55f03..ec55f03 100644 --- a/icons/sensoroff.svg +++ b/plugins/camera_sensor/icons/sensoroff.svg diff --git a/icons/sensoron.svg b/plugins/camera_sensor/icons/sensoron.svg index e902656..e902656 100644 --- a/icons/sensoron.svg +++ b/plugins/camera_sensor/icons/sensoron.svg diff --git a/TurtleArt/tacamera.py b/plugins/camera_sensor/tacamera.py index 2177288..15824a1 100644 --- a/TurtleArt/tacamera.py +++ b/plugins/camera_sensor/tacamera.py @@ -23,14 +23,25 @@ import gst, time GST_PIPE = ['v4l2src', 'ffmpegcolorspace', 'pngenc'] +# GST_PIPE = ['v4l2src', 'ffmpegcolorspace', 'gdkpixbufsink'] class Camera(): """ A class for representing the video camera """ def __init__(self, imagepath): + # self.imagepath = imagepath GST_PIPE.append('filesink location=%s' % imagepath) self.pipe = gst.parse_launch('!'.join(GST_PIPE)) self.bus = self.pipe.get_bus() + # self.bus.add_signal_watch() + # self.bus.connect('message', self._on_message) + + def _on_message(self, bus, message): + ''' We get a message if a pixbuf is available ''' + if message.structure is not None: + print message.structure.get_name() + if message.structure.get_name() == 'pixbuf': + message.structure['pixbuf'].save(self.imagepath, 'png') def save_camera_input_to_file(self): """ Grab a frame from the camera """ diff --git a/TurtleArt/v4l2.py b/plugins/camera_sensor/v4l2.py index 9c052fd..9c052fd 100644 --- a/TurtleArt/v4l2.py +++ b/plugins/camera_sensor/v4l2.py diff --git a/plugins/plugin.py b/plugins/plugin.py new file mode 100644 index 0000000..3065129 --- /dev/null +++ b/plugins/plugin.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +#Copyright (c) 2011 Collabora Ltd. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject + + +class Plugin(gobject.GObject): + def __init__(self): + gobject.GObject.__init__(self) + + def setup(self): + """ Setup is called once, when the Turtle Window is created. """ + pass + + def start(self): + """ start is called when run button is pressed. """ + pass + + def stop(self): + """ stop is called when stop button is pressed. """ + pass + + def goto_background(self): + """ goto_background is called when the activity is sent to the + background. """ + pass + + def return_to_foreground(self): + """ return_to_foreground is called when the activity returns to + the foreground. """ + pass + + def quit(self): + """ cleanup is called when the activity is exiting. """ + pass diff --git a/devices/__init__.py b/plugins/rfid/__init__.py index e69de29..e69de29 100644 --- a/devices/__init__.py +++ b/plugins/rfid/__init__.py diff --git a/devices/device.py b/plugins/rfid/device.py index 04a82b2..04a82b2 100644 --- a/devices/device.py +++ b/plugins/rfid/device.py diff --git a/icons/sensoroff.svg b/plugins/rfid/icons/sensoroff.svg index ec55f03..ec55f03 100644 --- a/icons/sensoroff.svg +++ b/plugins/rfid/icons/sensoroff.svg diff --git a/icons/sensoron.svg b/plugins/rfid/icons/sensoron.svg index e902656..e902656 100644 --- a/icons/sensoron.svg +++ b/plugins/rfid/icons/sensoron.svg diff --git a/plugins/rfid/rfid.py b/plugins/rfid/rfid.py new file mode 100644 index 0000000..e607eba --- /dev/null +++ b/plugins/rfid/rfid.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +#Copyright (C) 2010,11 Emiliano Pastorino +#Copyright (c) 2011 Walter Bender +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import dbus +from dbus.mainloop.glib import DBusGMainLoop +from gettext import gettext as _ + +from plugins.rfid.rfidutils import strhex2bin, strbin2dec, find_device +from plugins.plugin import Plugin + +from TurtleArt.tapalette import make_palette +from TurtleArt.talogo import primitive_dictionary +from TurtleArt.tautils import debug_output + +import logging +_logger = logging.getLogger('turtleart-activity RFID plugin') + +HAL_SERVICE = 'org.freedesktop.Hal' +HAL_MGR_PATH = '/org/freedesktop/Hal/Manager' +HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager' +HAL_DEV_IFACE = 'org.freedesktop.Hal.Device' +REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\ + 'a-z,A-Z,0-9,_]*serial_usb_[0-9]' + + +class Rfid(Plugin): + + def __init__(self, parent): + self._parent = parent + self._status = False + + """ + The following code will initialize a USB RFID reader. Please note that + in order to make this initialization function work, it is necessary to + set the permission for the ttyUSB device to 0666. You can do this by + adding a rule to /etc/udev/rules.d + + As root (using sudo or su), copy the following text into a new file in + /etc/udev/rules.d/94-ttyUSB-rules + + KERNEL=="ttyUSB[0-9]",MODE="0666" + + You only have to do this once. + """ + + self.rfid_connected = False + self.rfid_device = find_device() + self.rfid_idn = '' + + if self.rfid_device is not None: + _logger.info("RFID device found") + self.rfid_connected = self.rfid_device.do_connect() + if self.rfid_connected: + self.rfid_device.connect("tag-read", self._tag_read_cb) + self.rfid_device.connect("disconnected", self._disconnected_cb) + + loop = DBusGMainLoop() + bus = dbus.SystemBus(mainloop=loop) + hmgr_iface = dbus.Interface(bus.get_object(HAL_SERVICE, + HAL_MGR_PATH), HAL_MGR_IFACE) + + hmgr_iface.connect_to_signal('DeviceAdded', self._device_added_cb) + + self._status = True + + def setup(self): + # set up RFID-specific blocks + primitive_dictionary['rfid'] = self.prim_read_rfid + palette = make_palette('sensor', + colors=["#FF6060", "#A06060"], + help_string=_('Palette of sensor blocks')) + + if self._status: + palette.add_block('rfid', + style='box-style', + label=_('RFID'), + help_string=_('read value from RFID device'), + value_block=True, + prim_name='rfid') + else: + palette.add_block('rfid', + hidden=True, + style='box-style', + label=_('RFID'), + help_string=_('read value from RFID device'), + value_block=True, + prim_name='rfid') + + self._parent.lc.def_prim( + 'rfid', 0, lambda self: primitive_dictionary['rfid']()) + + def _status_report(self): + debug_output('Reporting RFID status: %s' % (str(self._status))) + return self._status + + def _device_added_cb(self, path): + """ + Called from hal connection when a new device is plugged. + """ + if not self.rfid_connected: + self.rfid_device = find_device() + _logger.debug("DEVICE_ADDED: %s" % self.rfid_device) + if self.rfid_device is not None: + _logger.debug("DEVICE_ADDED: RFID device is not None!") + self.rfid_connected = self._device.do_connect() + if self.rfid_connected: + _logger.debug("DEVICE_ADDED: Connected!") + self.rfid_device.connect("tag-read", self._tag_read_cb) + self.rfid_device.connect("disconnected", self._disconnected_cb) + + def _disconnected_cb(self, device, text): + """ + Called when the device is disconnected. + """ + self.rfid_connected = False + self.rfid_device = None + + def _tag_read_cb(self, device, tagid): + """ + Callback for "tag-read" signal. Receives the read tag id. + """ + idbin = strhex2bin(tagid) + self.rfid_idn = strbin2dec(idbin[26:64]) + while self.rfid_idn.__len__() < 9: + self.rfid_idn = '0' + self.rfid_idn + print tagid, idbin, self.rfid_idn + self.tw.lc.update_label_value('rfid', self.rfid_idn) + + # Block primitives used in talogo + + def prim_read_rfid(self): + if self._status: + return self.rfid_idn + else: + return '0' diff --git a/devices/rfidrweusb.py b/plugins/rfid/rfidrweusb.py index bd12631..bd12631 100644 --- a/devices/rfidrweusb.py +++ b/plugins/rfid/rfidrweusb.py diff --git a/TurtleArt/rfidutils.py b/plugins/rfid/rfidutils.py index f2c74b4..4e02619 100644 --- a/TurtleArt/rfidutils.py +++ b/plugins/rfid/rfidutils.py @@ -23,10 +23,10 @@ def find_device(): Return a device instance or None. """ device = None - for i in os.listdir(os.path.join('.', 'devices')): - if not os.path.isdir(os.path.join('.', 'devices', i)): + for i in os.listdir(os.path.join('.', 'plugins/rfid')): + if not os.path.isdir(os.path.join('.', 'plugins/rfid', i)): try: - _tempmod = __import__('devices.%s'%i.split('.')[0], globals(), + _tempmod = __import__('rfid.%s'%i.split('.')[0], globals(), locals(), ['RFIDReader'], -1) devtemp = _tempmod.RFIDReader() if devtemp.get_present() == True: diff --git a/devices/serial/__init__.py b/plugins/rfid/serial/__init__.py index 681ad5c..681ad5c 100644 --- a/devices/serial/__init__.py +++ b/plugins/rfid/serial/__init__.py diff --git a/devices/serial/serialposix.py b/plugins/rfid/serial/serialposix.py index 174e2f7..174e2f7 100644 --- a/devices/serial/serialposix.py +++ b/plugins/rfid/serial/serialposix.py diff --git a/devices/serial/serialutil.py b/plugins/rfid/serial/serialutil.py index fd466f2..fd466f2 100644 --- a/devices/serial/serialutil.py +++ b/plugins/rfid/serial/serialutil.py diff --git a/devices/tis2000.py b/plugins/rfid/tis2000.py index 91d1991..91d1991 100644 --- a/devices/tis2000.py +++ b/plugins/rfid/tis2000.py diff --git a/devices/utils.py b/plugins/rfid/utils.py index 94e5540..94e5540 100644 --- a/devices/utils.py +++ b/plugins/rfid/utils.py diff --git a/devices/__init__.py b/plugins/turtle_blocks_extras/__init__.py index e69de29..e69de29 100644 --- a/devices/__init__.py +++ b/plugins/turtle_blocks_extras/__init__.py diff --git a/plugins/turtle_blocks_extras/icons/extrasoff.svg b/plugins/turtle_blocks_extras/icons/extrasoff.svg new file mode 100644 index 0000000..7975a9d --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/extrasoff.svg @@ -0,0 +1,75 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/plugins/turtle_blocks_extras/icons/extrason.svg b/plugins/turtle_blocks_extras/icons/extrason.svg new file mode 100644 index 0000000..7ee08bf --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/extrason.svg @@ -0,0 +1,158 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/portfoliooff.svg b/plugins/turtle_blocks_extras/icons/mediaoff.svg index b2f460a..b2f460a 100644 --- a/icons/portfoliooff.svg +++ b/plugins/turtle_blocks_extras/icons/mediaoff.svg diff --git a/plugins/turtle_blocks_extras/icons/mediaon.svg b/plugins/turtle_blocks_extras/icons/mediaon.svg new file mode 100644 index 0000000..27dd516 --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/mediaon.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + diff --git a/plugins/turtle_blocks_extras/icons/portfoliooff.svg b/plugins/turtle_blocks_extras/icons/portfoliooff.svg new file mode 100644 index 0000000..d404bab --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/portfoliooff.svg @@ -0,0 +1,72 @@ + + + + + + + image/svg+xml + + + + + + + + + +` + + + + + + + + diff --git a/icons/portfolioon.svg b/plugins/turtle_blocks_extras/icons/portfolioon.svg index fa4ddf6..e174ee2 100644 --- a/icons/portfolioon.svg +++ b/plugins/turtle_blocks_extras/icons/portfolioon.svg @@ -2,6 +2,9 @@ + + + + image/svg+xml + + + + + - + transform="matrix(0.8501594,0,0,0.8501594,4.1206165,2.9055499)" + id="g3816" + style="fill:#f9f9f9;stroke:#0000ff;stroke-opacity:1"> + + + + + - - - + style="fill:#f9f9f9;stroke:#0000ff;stroke-width:2.66399026;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + x1="9.0131855" + x2="45.986813" + y1="8.168005" + y2="8.168005" + id="line2460-0" /> diff --git a/icons/sensoroff.svg b/plugins/turtle_blocks_extras/icons/sensoroff.svg index ec55f03..ec55f03 100644 --- a/icons/sensoroff.svg +++ b/plugins/turtle_blocks_extras/icons/sensoroff.svg diff --git a/icons/sensoron.svg b/plugins/turtle_blocks_extras/icons/sensoron.svg index e902656..e902656 100644 --- a/icons/sensoron.svg +++ b/plugins/turtle_blocks_extras/icons/sensoron.svg diff --git a/plugins/turtle_blocks_extras/turtle_blocks_extras.py b/plugins/turtle_blocks_extras/turtle_blocks_extras.py new file mode 100644 index 0000000..e868ba4 --- /dev/null +++ b/plugins/turtle_blocks_extras/turtle_blocks_extras.py @@ -0,0 +1,1246 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2011, Walter Bender +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +from time import time +import os.path +from gettext import gettext as _ + +try: + from sugar.datastore import datastore +except ImportError: + pass + +from plugins.plugin import Plugin +from TurtleArt.tapalette import make_palette, define_logo_function +from TurtleArt.talogo import primitive_dictionary, logoerror, \ + media_blocks_dictionary +from TurtleArt.taconstants import DEFAULT_SCALE, ICON_SIZE, CONSTANTS +from TurtleArt.tautils import convert, round_int, debug_output +from TurtleArt.tajail import myfunc, myfunc_import + + +def _num_type(x): + """ Is x a number type? """ + if type(x) == int: + return True + if type(x) == float: + return True + if type(x) == ord: + return True + return False + + +def _string_to_num(x): + """ Try to comvert a string to a number """ + xx = convert(x.replace(self.tw.decimal_point, '.'), float) + if type(xx) is float: + return xx + else: + xx, xflag = chr_to_ord(x) + if xflag: + return xx + else: + raise logoerror("#syntaxerror") + + +def _millisecond(): + """ Current time in milliseconds """ + return time() * 1000 + + +class Turtle_blocks_extras(Plugin): + """ a class for defining the extra palettes that distinguish Turtle Blocks + from Turtle Art """ + + def __init__(self, parent): + self.tw = parent + + def setup(self): + self.heap = self.tw.lc.heap + self.keyboard = self.tw.lc.keyboard + self.title_height = int((self.tw.canvas.height / 20) * self.tw.scale) + + # set up Turtle Block palettes + self._flow_palette() + + self._media_palette() + + self._sensor_palette() + + self._extras_palette() + + self._portfolio_palette() + + # Palette definitions + + def _flow_palette(self): + palette = make_palette('flow', + colors=["#FFC000", "#A08000"], + help_string=_('Palette of flow operators')) + + # macro + palette.add_block('while', + style='flow-style-boolean', + label=_('while'), + help_string=_('do-while-True operator that uses \ +boolean operators from Numbers palette')) + + # macro + palette.add_block('until', + style='flow-style-boolean', + label=_('until'), + help_string=_('do-until-True operator that uses \ +boolean operators from Numbers palette')) + + def _media_palette(self): + + palette = make_palette('media', + colors=["#A0FF00", "#80A000"], + help_string=_('Palette of media objects')) + + palette.add_block('journal', + style='box-style-media', + label=' ', + default='None', + special_name=_('journal'), + help_string=_('Sugar Journal media object')) + + palette.add_block('audio', + style='box-style-media', + label=' ', + special_name=_('audio'), + default='None', + help_string=_('Sugar Journal audio object')) + + palette.add_block('video', + style='box-style-media', + label=' ', + special_name=_('video'), + default='None', + help_string=_('Sugar Journal video object')) + + palette.add_block('description', + style='box-style-media', + label=' ', + special_name=_('description'), + default='None', + help_string=_('Sugar Journal description field')) + + palette.add_block('string', + style='box-style', + label=_('text'), + default=_('text'), + special_name=_('text'), + help_string=_('string value')) + + primitive_dictionary['show'] = self._prim_show + palette.add_block('show', + style='basic-style-1arg', + label=_('show'), + default=_('text'), + prim_name='show', + logo_command='label', + help_string=_('draws text or show media from the \ +Journal')) + self.tw.lc.def_prim('show', 1, + lambda self, x: primitive_dictionary['show'](x, True)) + + palette.add_block('showaligned', + hidden=True, + colors=["#A0FF00", "#80A000"], + style='basic-style-1arg', + label=_('show aligned'), + default=_('text'), + prim_name='showaligned', + logo_command='label', + help_string=_('draws text or show media from the \ +Journal')) + self.tw.lc.def_prim('showaligned', 1, + lambda self, x: primitive_dictionary['show'](x, False)) + + # deprecated + primitive_dictionary['write'] = self._prim_write + palette.add_block('write', + hidden=True, + colors=["#A0FF00", "#80A000"], + style='basic-style-1arg', + label=_('show'), + default=[_('text'), 32], + prim_name='write', + logo_command='label', + help_string=_('draws text or show media from the \ +Journal')) + self.tw.lc.def_prim('write', 2, + lambda self, x, y: primitive_dictionary['write'](x, y)) + + primitive_dictionary['setscale'] = self._prim_setscale + palette.add_block('setscale', + style='basic-style-1arg', + label=_('set scale'), + prim_name='setscale', + default=33, + logo_command='setlabelheight', + help_string=_('sets the scale of media')) + self.tw.lc.def_prim('setscale', 1, + lambda self, x: primitive_dictionary['setscale'](x)) + + primitive_dictionary['savepix'] = self._prim_save_picture + palette.add_block('savepix', + style='basic-style-1arg', + label=_('save picture'), + prim_name='savepix', + default=_('picture name'), + help_string=_('saves a picture to the Sugar \ +Journal')) + self.tw.lc.def_prim('savepix', 1, + lambda self, x: primitive_dictionary['savepix'](x)) + + primitive_dictionary['savesvg'] = self._prim_save_svg + palette.add_block('savesvg', + style='basic-style-1arg', + label=_('save SVG'), + prim_name='savesvg', + default=_('picture name'), + help_string=_('saves turtle graphics as an SVG file \ +in the Sugar Journal')) + self.tw.lc.def_prim('savesvg', 1, + lambda self, x: primitive_dictionary['savesvg'](x)) + + palette.add_block('scale', + style='box-style', + label=_('scale'), + prim_name='scale', + value_block=True, + logo_command='labelsize', + help_string=_('holds current scale value')) + self.tw.lc.def_prim('scale', 0, lambda self: self.tw.lc.scale) + + palette.add_block('mediawait', + style='basic-style-extended-vertical', + label=_('media wait'), + prim_name='mediawait', + help_string=_('wait for current video or audio to \ +complete')) + self.tw.lc.def_prim('mediawait', 0, self.tw.lc.media_wait, True) + + def _sensor_palette(self): + + palette = make_palette('sensor', + colors=["#FF6060", "#A06060"], + help_string=_('Palette of sensor blocks')) + + primitive_dictionary['kbinput'] = self._prim_kbinput + palette.add_block('kbinput', + style='basic-style-extended-vertical', + label=_('query keyboard'), + prim_name='kbinput', + help_string=_('query for keyboard input (results \ +stored in keyboard block)')) + self.tw.lc.def_prim('kbinput', 0, + lambda self: primitive_dictionary['kbinput']()) + + palette.add_block('keyboard', + style='box-style', + label=_('keyboard'), + prim_name='keyboard', + value_block=True, + logo_command='make "keyboard readchar', + help_string=_('holds results of query-keyboard \ +block')) + self.tw.lc.def_prim('keyboard', 0, lambda self: self.tw.lc.keyboard) + + primitive_dictionary['readpixel'] = self._prim_readpixel + palette.add_block('readpixel', + style='basic-style-extended-vertical', + label=_('read pixel'), + prim_name='readpixel', + logo_command=':keyboard', + help_string=_('RGB color under the turtle is pushed \ +to the stack')) + self.tw.lc.def_prim('readpixel', 0, + lambda self: primitive_dictionary['readpixel']()) + + primitive_dictionary['see'] = self._prim_see + palette.add_block('see', + style='box-style', + label=_('turtle sees'), + prim_name='see', + help_string=_('returns the color that the turtle \ +"sees"')) + self.tw.lc.def_prim('see', 0, + lambda self: primitive_dictionary['see']()) + + primitive_dictionary['time'] = self._prim_time + palette.add_block('time', + style='box-style', + label=_('time'), + prim_name='time', + value_block=True, + help_string=_('elapsed time (in seconds) since \ +program started')) + self.tw.lc.def_prim('time', 0, + lambda self: primitive_dictionary['time']()) + + def _extras_palette(self): + + palette = make_palette('extras', + colors=["#FF0000", "#A00000"], + help_string=_('Palette of extra options')) + + primitive_dictionary['push'] = self._prim_push + palette.add_block('push', + style='basic-style-1arg', + label=_('push'), + prim_name='push', + logo_command='tapush', + help_string=_('pushes value onto FILO (first-in \ +last-out heap)')) + self.tw.lc.def_prim('push', 1, + lambda self, x: primitive_dictionary['push'](x)) + define_logo_function('tapush', 'to tapush :foo\rmake "taheap fput \ +:foo :taheap\rend\rmake "taheap []\r') + + primitive_dictionary['printheap'] = self._prim_printheap + palette.add_block('printheap', + style='basic-style-extended-vertical', + label=_('show heap'), + prim_name='printheap', + logo_command='taprintheap', + help_string=_('shows values in FILO (first-in \ +last-out heap)')) + self.tw.lc.def_prim('printheap', 0, + lambda self: primitive_dictionary['printheap']()) + define_logo_function('taprintheap', 'to taprintheap \rprint :taheap\r\ +end\r') + + primitive_dictionary['clearheap'] = self._prim_emptyheap + palette.add_block('clearheap', + style='basic-style-extended-vertical', + label=_('empty heap'), + prim_name='clearheap', + logo_command='taclearheap', + help_string=_('emptys FILO (first-in-last-out \ +heap)')) + self.tw.lc.def_prim('clearheap', 0, + lambda self: primitive_dictionary['clearheap']()) + define_logo_function('taclearheap', 'to taclearheap\rmake "taheap []\r\ +end\r') + + primitive_dictionary['pop'] = self._prim_pop + palette.add_block('pop', + style='box-style', + label=_('pop'), + prim_name='pop', + value_block=True, + logo_command='tapop', + help_string=_('pops value off FILO (first-in \ +last-out heap)')) + self.tw.lc.def_prim('pop', 0, + lambda self: primitive_dictionary['pop']()) + define_logo_function('tapop', 'to tapop\rif emptyp :taheap [stop]\r\ +make "tmp first :taheap\rmake "taheap butfirst :taheap\routput :tmp\rend\r') + + primitive_dictionary['print'] = self._prim_print + palette.add_block('comment', + style='basic-style-1arg', + label=_('comment'), + prim_name='comment', + default=_('comment'), + help_string=_('places a comment in your code')) + self.tw.lc.def_prim('comment', 1, + lambda self, x: primitive_dictionary['print'](x, True)) + + palette.add_block('print', + style='basic-style-1arg', + label=_('print'), + prim_name='print', + logo_command='label', + help_string=_('prints value in status block at \ +bottom of the screen')) + self.tw.lc.def_prim('print', 1, + lambda self, x: primitive_dictionary['print'](x, False)) + + primitive_dictionary['myfunction'] = self._prim_myfunction + palette.add_block('myfunc1arg', + style='number-style-var-arg', + label=[_('Python'), 'f(x)', 'x'], + prim_name='myfunction', + default=['x', 100], + help_string=_('a programmable block: used to add \ +advanced single-variable math equations, e.g., sin(x)')) + self.tw.lc.def_prim('myfunction', 2, + lambda self, f, x: primitive_dictionary['myfunction'](f, [x])) + + palette.add_block('myfunc2arg', + hidden=True, + colors=["#FF0000", "#A00000"], + style='number-style-var-arg', + label=[_('Python'), 'f(x,y)', 'x'], + prim_name='myfunction2', + default=['x+y', 100, 100], + help_string=_('a programmable block: used to add \ +advanced multi-variable math equations, e.g., sqrt(x*x+y*y)')) + self.tw.lc.def_prim('myfunction2', 3, + lambda self, f, x, y: primitive_dictionary['myfunction']( + f, [x, y])) + + palette.add_block('myfunc3arg', + hidden=True, + colors=["#FF0000", "#A00000"], + style='number-style-var-arg', + label=[_('Python'), 'f(x,y,z)', 'x'], + prim_name='myfunction3', + default=['x+y+z', 100, 100, 100], + help_string=_('a programmable block: used to add \ +advanced multi-variable math equations, e.g., sin(x+y+z)')) + self.tw.lc.def_prim('myfunction3', 4, + lambda self, f, x, y, z: primitive_dictionary['myfunction']( + f, [x, y, z])) + + primitive_dictionary['userdefined'] = self._prim_myblock + palette.add_block('userdefined', + style='basic-style-var-arg', + label=' ', + prim_name='userdefined', + special_name=_('Python block'), + default=100, + help_string=_('runs code found in the tamyblock.py \ +module found in the Journal')) + self.tw.lc.def_prim('userdefined', 1, + lambda self, x: primitive_dictionary['userdefined']([x])) + + palette.add_block('userdefined2args', + hidden=True, + colors=["#FF0000", "#A00000"], + style='basic-style-var-arg', + label=' ', + prim_name='userdefined2', + special_name=_('Python block'), + default=[100, 100], + help_string=_('runs code found in the tamyblock.py \ +module found in the Journal')) + self.tw.lc.def_prim('userdefined2', 2, + lambda self, x, y: primitive_dictionary['userdefined']([x, y])) + + palette.add_block('userdefined3args', + hidden=True, + colors=["#FF0000", "#A00000"], + style='basic-style-var-arg', + label=' ', + prim_name='userdefined3', + special_name=_('Python block'), + default=[100, 100, 100], + help_string=_('runs code found in the tamyblock.py \ +module found in the Journal')) + self.tw.lc.def_prim('userdefined3', 3, + lambda self, x, y, z: primitive_dictionary['userdefined']( + [x, y, z])) + + palette.add_block('cartesian', + style='basic-style-extended-vertical', + label=_('Cartesian'), + prim_name='cartesian', + help_string=_('displays Cartesian coordinates')) + self.tw.lc.def_prim('cartesian', 0, + lambda self: self.tw.set_cartesian(True)) + + palette.add_block('polar', + style='basic-style-extended-vertical', + label=_('polar'), + prim_name='polar', + help_string=_('displays polar coordinates')) + self.tw.lc.def_prim('polar', 0, + lambda self: self.tw.set_polar(True)) + + palette.add_block('addturtle', + style='basic-style-1arg', + label=_('turtle'), + prim_name='turtle', + default=1, + help_string=_('chooses which turtle to command')) + self.tw.lc.def_prim('turtle', 1, + lambda self, x: self.tw.canvas.set_turtle(x)) + + primitive_dictionary['skin'] = self._prim_reskin + palette.add_block('skin', + hidden=True, + colors=["#FF0000", "#A00000"], + style='basic-style-1arg', + label=_('turtle shell'), + prim_name='skin', + help_string=_("put a custom 'shell' on the turtle")) + self.tw.lc.def_prim('skin', 1, + lambda self, x: primitive_dictionary['skin'](x)) + + # macro + palette.add_block('reskin', + style='basic-style-1arg', + label=_('turtle shell'), + help_string=_("put a custom 'shell' on the turtle")) + + palette.add_block('sandwichtop_no_label', + style='collapsible-top-no-label', + label=[' ', ' '], + special_name=_('top'), + prim_name='nop', + help_string=_('top of a collapsed stack')) + + palette.add_block('sandwichbottom', + style='collapsible-bottom', + label=[' ', ' '], + prim_name='nop', + special_name=_('bottom'), + help_string=_('bottom of a collapsible stack')) + + palette.add_block('sandwichcollapsed', + hidden=True, + colors=["#FF0000", "#A00000"], + style='invisible', + label=' ', + prim_name='nop', + help_string=_('bottom block in a collapsed stack: \ +click to open')) + + # deprecated blocks + palette.add_block('sandwichtop', + hidden=True, + colors=["#FF0000", "#A00000"], + style='collapsible-top', + label=_('top of stack'), + default=_('label'), + prim_name='comment', + help_string=_('top of stack')) + + palette.add_block('sandwichtop_no_arm', + hidden=True, + colors=["#FF0000", "#A00000"], + style='collapsible-top-no-arm', + label=_('top of a collapsible stack'), + default=_('label'), + prim_name='comment', + help_string=_('top of stack')) + + palette.add_block('sandwichtop_no_arm_no_label', + hidden=True, + colors=["#FF0000", "#A00000"], + style='collapsible-top-no-arm-no-label', + label=[' ', _('click to open')], + prim_name='nop', + help_string=_('top of stack')) + + def _portfolio_palette(self): + + palette = make_palette('portfolio', + colors=["#0606FF", "#0606A0"], + help_string=_('Palette of presentation templates')) + + palette.add_block('hideblocks', + style='basic-style-extended-vertical', + label=_('hide blocks'), + prim_name='hideblocks', + help_string=_('declutters canvas by hiding blocks')) + self.tw.lc.def_prim('hideblocks', 0, lambda self: self.tw.hideblocks()) + + palette.add_block('showblocks', + style='basic-style-extended-vertical', + label=_('show blocks'), + prim_name='showblocks', + help_string=_('restores hidden blocks')) + self.tw.lc.def_prim('showblocks', 0, lambda self: self.tw.showblocks()) + + palette.add_block('fullscreen', + style='basic-style-extended-vertical', + label=_('full screen'), + prim_name='fullscreen', + help_string=_('hides the Sugar toolbars')) + self.tw.lc.def_prim('fullscreen', 0, + lambda self: self.tw.set_fullscreen()) + + primitive_dictionary['bulletlist'] = self._prim_list + palette.add_block('list', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='bullet-style', + label=_('list'), + prim_name='bulletlist', + default=['∙ ', '∙ '], + help_string=_('presentation bulleted list')) + self.tw.lc.def_prim('bulletlist', 1, + primitive_dictionary['bulletlist'], True) + + # macros + palette.add_block('picturelist', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: list of \ +bullets')) + + palette.add_block('picture1x1a', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select \ +Journal object (no description)')) + + palette.add_block('picture1x1', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select \ +Journal object (with description)')) + + palette.add_block('picture2x2', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select four \ +Journal objects')) + + palette.add_block('picture2x1', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select two \ +Journal objects')) + + palette.add_block('picture1x2', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select two \ +Journal objects')) + + # Display-dependent constants + palette.add_block('leftpos', + style='box-style', + label=_('left'), + prim_name='lpos', + logo_command='lpos', + help_string=_('xcor of left of screen')) + self.tw.lc.def_prim('lpos', 0, lambda self: CONSTANTS['leftpos']) + + palette.add_block('bottompos', + style='box-style', + label=_('bottom'), + prim_name='bpos', + logo_command='bpos', + help_string=_('ycor of bottom of screen')) + self.tw.lc.def_prim('bpos', 0, lambda self: CONSTANTS['bottompos']) + + palette.add_block('width', + style='box-style', + label=_('width'), + prim_name='hres', + logo_command='width', + help_string=_('the canvas width')) + self.tw.lc.def_prim('hres', 0, lambda self: CONSTANTS['width']) + + palette.add_block('rightpos', + style='box-style', + label=_('right'), + prim_name='rpos', + logo_command='rpos', + help_string=_('xcor of right of screen')) + self.tw.lc.def_prim('rpos', 0, lambda self: CONSTANTS['rightpos']) + + palette.add_block('toppos', + style='box-style', + label=_('top'), + prim_name='tpos', + logo_command='tpos', + help_string=_('ycor of top of screen')) + self.tw.lc.def_prim('tpos', 0, lambda self: CONSTANTS['toppos']) + + palette.add_block('height', + style='box-style', + label=_('height'), + prim_name='vres', + logo_command='height', + help_string=_('the canvas height')) + self.tw.lc.def_prim('vres', 0, lambda self: CONSTANTS['height']) + + palette.add_block('titlex', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='box-style', + label=_('title x'), + logo_command='titlex', + prim_name='titlex') + self.tw.lc.def_prim('titlex', 0, lambda self: CONSTANTS['titlex']) + + palette.add_block('titley', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='box-style', + label=_('title y'), + logo_command='titley', + prim_name='titley') + self.tw.lc.def_prim('titley', 0, lambda self: CONSTANTS['titley']) + + palette.add_block('leftx', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='box-style', + label=_('left x'), + prim_name='leftx', + logo_command='leftx') + self.tw.lc.def_prim('leftx', 0, lambda self: CONSTANTS['leftx']) + + palette.add_block('topy', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='box-style', + label=_('top y'), + prim_name='topy', + logo_command='topy') + self.tw.lc.def_prim('topy', 0, lambda self: CONSTANTS['topy']) + + palette.add_block('rightx', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='box-style', + label=_('right x'), + prim_name='rightx', + logo_command='rightx') + self.tw.lc.def_prim('rightx', 0, lambda self: CONSTANTS['rightx']) + + palette.add_block('bottomy', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='box-style', + label=_('bottom y'), + prim_name='bottomy', + logo_command='bottomy') + self.tw.lc.def_prim('bottomy', 0, lambda self: CONSTANTS['bottomy']) + + # deprecated blocks + + primitive_dictionary['t1x1'] = self._prim_t1x1 + palette.add_block('template1x1', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='portfolio-style-1x1', + label=' ', + prim_name='t1x1', + default=[_('Title'), 'None'], + special_name=_('presentation 1x1'), + help_string=_('presentation template: select \ +Journal object (with description)')) + self.tw.lc.def_prim('t1x1', 2, + lambda self, a, b: primitive_dictionary['t1x1'](a, b)) + + primitive_dictionary['t1x1a'] = self._prim_t1x1a + palette.add_block('template1x1a', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='portfolio-style-1x1', + label=' ', + prim_name='t1x1a', + default=[_('Title'), 'None'], + special_name=_('presentation 1x1'), + help_string=_('presentation template: select \ +Journal object (no description)')) + self.tw.lc.def_prim('t1x1a', 2, + lambda self, a, b: primitive_dictionary['t1x1a'](a, b)) + + primitive_dictionary['2x1'] = self._prim_t2x1 + palette.add_block('template2x1', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='portfolio-style-2x1', + label=' ', + prim_name='t2x1', + default=[_('Title'), 'None', 'None'], + special_name=_('presentation 2x1'), + help_string=_("presentation template: select two \ +Journal objects")) + self.tw.lc.def_prim('t2x1', 3, + lambda self, a, b, c: primitive_dictionary['t2x1'](a, b, c)) + + primitive_dictionary['1x2'] = self._prim_t1x2 + palette.add_block('template1x2', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='portfolio-style-1x2', + label=' ', + prim_name='t1x2', + default=[_('Title'), 'None', 'None'], + special_name=_('presentation 1x2'), + help_string=_("presentation template: select two \ +Journal objects")) + self.tw.lc.def_prim('t1x2', 3, + lambda self, a, b, c: primitive_dictionary['t1x2'](a, b, c)) + + primitive_dictionary['t2x2'] = self._prim_t2x2 + palette.add_block('template2x2', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='portfolio-style-2x2', + label=' ', + prim_name='t2x2', + default=[_('Title'), 'None', 'None', 'None', 'None'], + special_name=_('presentation 2x2'), + help_string=_("presentation template: select four \ +Journal objects")) + self.tw.lc.def_prim('t2x2', 5, + lambda self, a, b, c, d, e: primitive_dictionary['t2x2']( + a, b, c, d, e)) + + palette.add_block('templatelist', + hidden=True, + colors=["#0606FF", "#0606A0"], + style='bullet-style', + label=' ', + prim_name='bullet', + default=[_('Title'), '∙ '], + special_name=_('presentation bulleted list'), + help_string=_('presentation template: list of \ +bullets')) + self.tw.lc.def_prim('bullet', 1, self._prim_list, True) + + # Block primitives + + def _prim_emptyheap(self): + """ Empty FILO """ + self.tw.lc.heap = [] + + def _prim_kbinput(self): + """ Query keyboard """ + if len(self.tw.keypress) == 1: + self.tw.lc.keyboard = ord(self.tw.keypress[0]) + else: + try: + self.tw.lc.keyboard = {'Escape': 27, 'space': 32, ' ': 32, + 'Return': 13, 'KP_Up': 2, 'KP_Down': 4, 'KP_Left': 1, + 'KP_Right': 3}[self.tw.keypress] + except KeyError: + self.tw.lc.keyboard = 0 + self.tw.lc.update_label_value('keyboard', self.tw.lc.keyboard) + self.tw.keypress = '' + + def _prim_list(self, blklist): + """ Expandable list block """ + self._prim_showlist(blklist) + self.tw.lc.ireturn() + yield True + + def _prim_myblock(self, x): + """ Run Python code imported from Journal """ + if self.tw.lc.bindex is not None and \ + self.tw.lc.bindex in self.tw.myblock: + try: + if len(x) == 1: + myfunc_import(self, self.tw.myblock[self.tw.lc.bindex], + x[0]) + else: + myfunc_import(self, self.tw.myblock[self.tw.lc.bindex], x) + except: + raise logoerror("#syntaxerror") + + def _prim_myfunction(self, f, x): + """ Programmable block """ + try: + y = myfunc(f, x) + if str(y) == 'nan': + debug_output('Python function returned NAN', + self.tw.running_sugar) + self.tw.lc.stop_logo() + raise logoerror("#notanumber") + else: + return y + except ZeroDivisionError: + self.tw.lc.stop_logo() + raise logoerror("#zerodivide") + except ValueError, e: + self.tw.lc.stop_logo() + raise logoerror('#' + str(e)) + except SyntaxError, e: + self.tw.lc.stop_logo() + raise logoerror('#' + str(e)) + except NameError, e: + self.tw.lc.stop_logo() + raise logoerror('#' + str(e)) + except OverflowError: + self.tw.lc.stop_logo() + raise logoerror("#overflowerror") + except TypeError: + self.tw.lc.stop_logo() + raise logoerror("#notanumber") + + def _prim_pop(self): + """ Pop value off of FILO """ + if len(self.tw.lc.heap) == 0: + raise logoerror("#emptyheap") + else: + if len(self.tw.lc.heap) == 1: + self.tw.lc.update_label_value('pop') + else: + self.tw.lc.update_label_value('pop', self.tw.lc.heap[-2]) + return self.tw.lc.heap.pop(-1) + + def _prim_print(self, n, flag): + """ Print object n """ + if flag and (self.tw.hide or self.tw.step_time == 0): + return + if type(n) == str or type(n) == unicode: + if n[0:6] == 'media_' and \ + n[6:].lower not in media_blocks_dictionary: + try: + if self.tw.running_sugar: + try: + dsobject = datastore.get(n[6:]) + except: + debug_output("Couldn't open %s" % (n[6:]), + self.tw.running_sugar) + self.tw.showlabel('status', dsobject.metadata['title']) + dsobject.destroy() + else: + self.tw.showlabel('status', n[6:]) + except IOError: + self.tw.showlabel('status', n) + else: + self.tw.showlabel('status', n) + elif type(n) == int: + self.tw.showlabel('status', n) + else: + self.tw.showlabel('status', + str(round_int(n)).replace('.', self.tw.decimal_point)) + + def _prim_printheap(self): + """ Display contents of heap """ + heap_as_string = str(self.tw.lc.heap) + if len(heap_as_string) > 80: + self.tw.showlabel('status', str(self.tw.lc.heap)[0:79] + '…') + else: + self.tw.showlabel('status', str(self.tw.lc.heap)) + + def _prim_push(self, val): + """ Push value onto FILO """ + self.tw.lc.heap.append(val) + self.tw.lc.update_label_value('pop', val) + + def _prim_readpixel(self): + """ Read r, g, b, a from the canvas and push b, g, r to the stack """ + r, g, b, a = self.tw.canvas.get_pixel() + self.tw.lc.heap.append(b) + self.tw.lc.heap.append(g) + self.tw.lc.heap.append(r) + + def _prim_reskin(self, media): + """ Reskin the turtle with an image from a file """ + scale = int(ICON_SIZE * float(self.tw.lc.scale) / DEFAULT_SCALE) + if scale < 1: + return + self.tw.lc.filepath = None + dsobject = None + if os.path.exists(media[6:]): # is it a path? + self.tw.lc.filepath = media[6:] + elif self.tw.running_sugar: # is it a datastore object? + try: + dsobject = datastore.get(media[6:]) + except: + debug_output("Couldn't open skin %s" % (media[6:]), + self.tw.running_sugar) + if dsobject is not None: + self.tw.lc.filepath = dsobject.file_path + if self.tw.lc.filepath == None: + self.tw.showlabel('nojournal', self.tw.lc.filepath) + return + pixbuf = None + try: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.tw.lc.filepath, + scale, scale) + except: + self.tw.showlabel('nojournal', self.tw.lc.filepath) + debug_output("Couldn't open skin %s" % (self.tw.lc.filepath), + self.tw.running_sugar) + if pixbuf is not None: + self.tw.active_turtle.set_shapes([pixbuf]) + pen_state = self.tw.active_turtle.get_pen_state() + if pen_state: + self.tw.canvas.setpen(False) + self.tw.canvas.forward(0) + if pen_state: + self.tw.canvas.setpen(True) + + def _prim_save_picture(self, name): + """ Save canvas to file as PNG """ + self.tw.save_as_image(name) + + def _prim_save_svg(self, name): + """ Save SVG to file """ + self.tw.canvas.svg_close() + self.tw.save_as_image(name, svg=True) + + def _prim_see(self): + """ Read r, g, b from the canvas and return a corresponding palette + color """ + r, g, b, a = self.tw.canvas.get_pixel() + color_index = self.tw.canvas.get_color_index(r, g, b) + self.tw.lc.update_label_value('see', color_index) + return color_index + + def _prim_setscale(self, scale): + """ Set the scale used by the show block """ + self.tw.lc.scale = scale + self.tw.lc.update_label_value('scale', scale) + + def _prim_show(self, string, center=False): + """ Show is the general-purpose media-rendering block. """ + if type(string) == str or type(string) == unicode: + if string in ['media_', 'descr_', 'audio_', 'video_', + 'media_None', 'descr_None', 'audio_None', + 'video_None']: + pass + elif string[0:6] in ['media_', 'descr_', 'audio_', 'video_']: + self.tw.lc.filepath = None + self.tw.lc.dsobject = None + if string[6:].lower() in media_blocks_dictionary: + media_blocks_dictionary[string[6:].lower()]() + elif os.path.exists(string[6:]): # is it a path? + self.tw.lc.filepath = string[6:] + elif self.tw.running_sugar: # is it a datastore object? + try: + self.tw.lc.dsobject = datastore.get(string[6:]) + except: + debug_output("Couldn't find dsobject %s" % ( + string[6:]), self.tw.running_sugar) + if self.tw.lc.dsobject is not None: + self.tw.lc.filepath = self.tw.lc.dsobject.file_path + if self.tw.lc.filepath == None: + if self.tw.lc.dsobject is not None: + self.tw.showlabel('nojournal', + self.tw.lc.dsobject.metadata['title']) + else: + self.tw.showlabel('nojournal', string[6:]) + debug_output("Couldn't open %s" % (string[6:]), + self.tw.running_sugar) + elif string[0:6] == 'media_': + self.tw.lc.insert_image(center) + elif string[0:6] == 'descr_': + mimetype = None + if self.tw.lc.dsobject is not None and \ + 'mime_type' in self.tw.lc.dsobject.metadata: + mimetype = self.tw.lc.dsobject.metadata['mime_type'] + description = None + if self.tw.lc.dsobject is not None and \ + 'description' in self.tw.lc.dsobject.metadata: + description = self.tw.lc.dsobject.metadata[ + 'description'] + self.tw.lc.insert_desc(mimetype, description) + elif string[0:6] == 'audio_': + self.tw.lc.play_sound() + elif string[0:6] == 'video_': + self.tw.lc.play_video() + if self.tw.lc.dsobject is not None: + self.tw.lc.dsobject.destroy() + else: # assume it is text to display + x, y = self.tw.lc.x2tx(), self.tw.lc.y2ty() + if center: + y -= self.tw.canvas.textsize + self.tw.canvas.draw_text(string, x, y, + int(self.tw.canvas.textsize * \ + self.tw.lc.scale / 100.), + self.tw.canvas.width - x) + elif type(string) == float or type(string) == int: + string = round_int(string) + x, y = self.tw.lc.x2tx(), self.tw.lc.y2ty() + if center: + y -= self.tw.canvas.textsize + self.tw.canvas.draw_text(string, x, y, + int(self.tw.canvas.textsize * \ + self.tw.lc.scale / 100.), + self.tw.canvas.width - x) + + def _prim_showlist(self, sarray): + """ Display list of media objects """ + x = self.tw.canvas.xcor / self.tw.coord_scale + y = self.tw.canvas.ycor / self.tw.coord_scale + for s in sarray: + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(s) + y -= int(self.tw.canvas.textsize * self.tw.lead) + + def _prim_time(self): + """ Number of seconds since program execution has started or + clean (prim_clear) block encountered """ + elapsed_time = int(time() - self.tw.lc.start_time) + self.tw.lc.update_label_value('time', elapsed_time) + return elapsed_time + + # Deprecated blocks + + def _prim_t1x1(self, title, media): + """ title, one image, and description """ + xo = self.tw.calc_position('t1x1')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) \ + / self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render media object + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media) + if self.tw.running_sugar: + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media.replace('media_', 'descr_')) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t2x1(self, title, media1, media2): + """ title, two images (horizontal), two descriptions """ + xo = self.tw.calc_position('t2x1')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render four quadrents + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2) + y = -self.title_height + if self.tw.running_sugar: + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2.replace('media_', 'descr_')) + x = -(self.tw.canvas.width / 2) + xo + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1.replace('media_', 'descr_')) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t1x2(self, title, media1, media2): + """ title, two images (vertical), two desciptions """ + xo = self.tw.calc_position('t1x2')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render four quadrents + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + if self.tw.running_sugar: + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1.replace('media_', 'descr_')) + y = -self.title_height + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2.replace('media_', 'descr_')) + x = -(self.tw.canvas.width / 2) + xo + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t2x2(self, title, media1, media2, media3, media4): + """ title and four images """ + xo = self.tw.calc_position('t2x2')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render four quadrents + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2) + y = -self.title_height + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media4) + x = -(self.tw.canvas.width / 2) + xo + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media3) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t1x1a(self, title, media1): + """ title, one media object """ + xo = self.tw.calc_position('t1x1a')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 90 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render media object + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_write(self, string, fsize): + """ Write string at size """ + x = self.tw.canvas.width / 2 + int(self.tw.canvas.xcor) + y = self.tw.canvas.height / 2 - int(self.tw.canvas.ycor) + self.tw.canvas.draw_text(string, x, y - 15, int(fsize), + self.tw.canvas.width) diff --git a/pysamples/COPYING b/pysamples/COPYING new file mode 100644 index 0000000..9cdf1e6 --- /dev/null +++ b/pysamples/COPYING @@ -0,0 +1,21 @@ +Copyright (c) 2008-11, Walter Bender +Copyright (c) 2008-11, TOny Forster + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/pysamples/copy_from_heap.py b/pysamples/copy_from_heap.py index 8483fde..fbca999 100644 --- a/pysamples/copy_from_heap.py +++ b/pysamples/copy_from_heap.py @@ -1,35 +1,16 @@ -#Copyright (c) 2010, Walter Bender, Tony Forster +#Copyright (c) 2010-11, Walter Bender, Tony Forster -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected. -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. +# Usage: Import this code into a Python (user-definable) block; when +# this code is run, the FILO heap will be copied to the clipboard. -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. -# This procedure is invoked when the user-definable block on the "extras" -# palette is selected. - -def myblock(lc, x): - - ########################################################################### - # - # Copy heap to clipboard - # - ########################################################################### +def myblock(tw, x): # second argument is ignored + ''' Copy heap to clipboard ''' from gtk import Clipboard - from tautils import data_to_string + from TurtleArt.tautils import data_to_string - Clipboard().set_text(data_to_string(lc.heap)) + Clipboard().set_text(data_to_string(tw.lc.heap)) diff --git a/pysamples/dotted_line.py b/pysamples/dotted_line.py index 9db7f9d..febd409 100644 --- a/pysamples/dotted_line.py +++ b/pysamples/dotted_line.py @@ -1,42 +1,23 @@ -#Copyright (c) 2009-10, Walter Bender +#Copyright (c) 2009-11, Walter Bender -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: - -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. - -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. - -# # This procedure is invoked when the user-definable block on the "extras" # palette is selected. Some examples of how to use this block are included # below. Try uncommenting an example or write your own Python code. -# +# # To uncomment code, remove the '# ' in the Python code. Take care to preserve # the proper indentations. # -# +# # NOTES: -# +# # Turtle Art is created in object oriented Python code. This is based # around the definition of classes and the creation of object(s) which # are instance(s) of that class. These objects then have properties and # methods which are defined by their class. -# +# # See http://docs.python.org/tutorial/classes.html for a description of # classes in Python. -# +# # Class Defined in Instance Created in # TurtleArtWindow tawindow.py tw TurtleArtActivity.py # LogoCode talogo.py lc tawindow.py @@ -44,72 +25,72 @@ # Turtles, Turtle taturtle.py turtles tawindow.py, # tacanvas.py # Blocks, Block tablock.py block_list tawindow.py -# -# +# +# # Class TurtleArtWindow -- useful properties and methods (from within -# tamyblock.py, lc.tw is the class instance) -# +# tamyblock.py, tw is the class instance) +# # Methods and data attributes Example -# set_fullscreen(self) lc.tw.set_fullscreen() +# set_fullscreen(self) tw.set_fullscreen() # Note: Hides the Sugar toolbar -# set_cartesian(self, flag) lc.tw.set_cartesian(True) +# set_cartesian(self, flag) tw.set_cartesian(True) # Note: True will make the overlay visible; # False will make it invisible -# set_polar(self, flag) lc.tw.set_polar(True) +# set_polar(self, flag) tw.set_polar(True) # Note: True will make the overlay visible; # False will make it invisible -# hideshow_button(self, flag) lc.tw.hideshow_button() +# hideshow_button(self, flag) tw.hideshow_button() # Note: Toggles visibility of blocks and palettes -# self.active_turtle lc.tw.active_turtle +# self.active_turtle tw.active_turtle # Note: The active turtle instance -# -# +# +# # Class TurtleGraphics -- useful properties and methods (from within -# tamyblock.py, lc.tw.canvas is the class instance) -# +# tamyblock.py, tw.canvas is the class instance) +# # Methods and data attributes Example -# clearscreen(self) lc.tw.canvas.clearscreen() +# clearscreen(self) tw.canvas.clearscreen() # Note: Clears the screen and resets all turtle and # pen attributes to default values -# setpen(self, flag) lc.tw.canvas.setpen(True) +# setpen(self, flag) tw.canvas.setpen(True) # Note: True will set the pen "down", enabling drawing; # False will set the pen "up" -# forward(self, n) lc.tw.canvas.forward(100) +# forward(self, n) tw.canvas.forward(100) # Note: Move the turtle forward 100 units -# arc(self, a, r) lc.tw.canvas.arc(120, 50) +# arc(self, a, r) tw.canvas.arc(120, 50) # Note: Move the turtle along an arc of 120 degrees # (clockwise) and radius of 50 units -# setheading(self, a) lc.tw.canvas.setheading(180) +# setheading(self, a) tw.canvas.setheading(180) # Note: Set the turtle heading to 180 # (towards the bottom of the screen) -# self.heading lc.tw.canvas.heading +# self.heading tw.canvas.heading # Note: The current heading -# setpensize(self, n) lc.tw.canvas.setpensize(25) +# setpensize(self, n) tw.canvas.setpensize(25) # Note: Set the turtle pensize to 25 units -# self.pensize lc.tw.canvas.pensize +# self.pensize tw.canvas.pensize # Note: The current pensize -# setcolor(self, c) lc.tw.canvas.color(70) +# setcolor(self, c) tw.canvas.color(70) # Note: Set the pen color to 70 (blue) -# self.color lc.tw.canvas.color +# self.color tw.canvas.color # Note: The current pen color -# setshade(self, s) lc.tw.canvas.shade(50) +# setshade(self, s) tw.canvas.shade(50) # Note: Set the pen shade to 50 -# self.shade lc.tw.canvas.shade +# self.shade tw.canvas.shade # Note: The current pen shade -# fillscreen(self, c, s) lc.tw.canvas.fillscreen(70, 90) +# fillscreen(self, c, s) tw.canvas.fillscreen(70, 90) # Note: Fill the screen with color 70, shade 90 (light blue) -# setxy(self, x, y) lc.tw.canvas.setxy(100,100) +# setxy(self, x, y) tw.canvas.setxy(100,100) # Note: Move the turtle to position (100, 100) -# self.xcor lc.tw.canvas.xcor +# self.xcor tw.canvas.xcor # Note: The current x coordinate of the turtle # (scaled to current units) -# self.ycor lc.tw.canvas.ycor +# self.ycor tw.canvas.ycor # Note: The current y coordinate of the turtle # (scaled to current units) -# self.set_turtle(name) lc.tw.canvas.set_turtle(1) +# self.set_turtle(name) tw.canvas.set_turtle(1) # Note: Set the current turtle to turtle '1' -# -# +# +# # Other useful Python functions # Module Example # from math import pow pow(2,3) returns 2 to the 3rd power @@ -118,34 +99,35 @@ # Note: See http://docs.python.org/library/math.html # from time import localtime localtime().tm_hour returns the current hour # Note: See http://docs.python.org/library/time.html -# lc.heap.append(data) adds data to the heap +# tw.lc.heap.append(data) adds data to the heap # Note: See http://docs.python.org/tutorial/datastructures.html -# data = lc.heap.pop(-1) pops data off the heap +# data = tw.lc.heap.pop(-1) pops data off the heap # Note: See http://docs.python.org/tutorial/datastructures.html # -def myblock(lc, x): +# Usage: Import this code into a Python (user-definable) block; when +# this code is run, the turtle will draw a dotted line of the length +# of the numeric argument block docked to the Python block. + - ########################################################################### - # - # Draw a dotted line of length x. - # - ########################################################################### +def myblock(tw, line_length): + ''' Draw a dotted line of length line_length. ''' - try: # make sure x is a number - x = float(x) + try: # make sure line_length is a number + line_length = float(line_length) except ValueError: return - if lc.tw.canvas.pendown: + if tw.canvas.pendown: dist = 0 - while dist+lc.tw.canvas.pensize < x: # repeat drawing dots - lc.tw.canvas.setpen(True) - lc.tw.canvas.forward(1) - lc.tw.canvas.setpen(False) - lc.tw.canvas.forward((lc.tw.canvas.pensize*2)-1) - dist += (lc.tw.canvas.pensize*2) - lc.tw.canvas.forward(x-dist) # make sure we have moved exactly x - lc.tw.canvas.setpen(True) + while dist + tw.canvas.pensize < line_length: # repeat drawing dots + tw.canvas.setpen(True) + tw.canvas.forward(1) + tw.canvas.setpen(False) + tw.canvas.forward((tw.canvas.pensize * 2) - 1) + dist += (tw.canvas.pensize * 2) + # make sure we have moved exactly line_length + tw.canvas.forward(line_length - dist) + tw.canvas.setpen(True) else: - lc.tw.canvas.forward(x) + tw.canvas.forward(line_length) return diff --git a/pysamples/grecord.py b/pysamples/grecord.py new file mode 100644 index 0000000..84b1a5c --- /dev/null +++ b/pysamples/grecord.py @@ -0,0 +1,227 @@ +#Copyright (c) 2008, Media Modifications Ltd. +#Copyright (c) 2011, Walter Bender + +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected. + +# Usage: Import this code into a Python (user-definable) block; Pass +# it 'start' to start recording; 'stop' to stop recording; 'play' to +# play back your recording; or 'save' to save your recording to the +# Sugar Journal. + + +def myblock(tw, arg): + ''' Record and playback a sound (Sugar only) ''' + import os + import time + + import gtk + import gst + + import gobject + gobject.threads_init() + + from TurtleArt.tautils import get_path + from TurtleArt.tagplay import play_audio_from_file + from sugar.datastore import datastore + from sugar import profile + + from gettext import gettext as _ + + class Grecord: + ''' A class for creating a gstreamer session for recording audio. ''' + + def __init__(self, tw): + ''' Set up the stream. We save to a raw .wav file and then + convert the sound to .ogg for saving. ''' + datapath = get_path(tw.parent, 'instance') + self.capture_file = os.path.join(datapath, 'output.wav') + self.save_file = os.path.join(datapath, 'output.ogg') + self._eos_cb = None + + self._can_limit_framerate = False + self._recording = False + + self._audio_transcode_handler = None + self._transcode_id = None + + self._pipeline = gst.Pipeline("Record") + self._create_audiobin() + self._pipeline.add(self._audiobin) + + bus = self._pipeline.get_bus() + bus.add_signal_watch() + bus.connect('message', self._bus_message_handler) + + def _create_audiobin(self): + ''' Assemble all the pieces we need. ''' + src = gst.element_factory_make("alsasrc", "absrc") + + # attempt to use direct access to the 0,0 device, solving some A/V + # sync issues + src.set_property("device", "plughw:0,0") + hwdev_available = src.set_state(gst.STATE_PAUSED) != \ + gst.STATE_CHANGE_FAILURE + src.set_state(gst.STATE_NULL) + if not hwdev_available: + src.set_property("device", "default") + + srccaps = gst.Caps("audio/x-raw-int,rate=16000,channels=1,depth=16") + + # guarantee perfect stream, important for A/V sync + rate = gst.element_factory_make("audiorate") + + # without a buffer here, gstreamer struggles at the start of the + # recording and then the A/V sync is bad for the whole video + # (possibly a gstreamer/ALSA bug -- even if it gets caught up, it + # should be able to resync without problem) + queue = gst.element_factory_make("queue", "audioqueue") + queue.set_property("leaky", True) # prefer fresh data + queue.set_property("max-size-time", 5000000000) # 5 seconds + queue.set_property("max-size-buffers", 500) + queue.connect("overrun", self._log_queue_overrun) + + enc = gst.element_factory_make("wavenc", "abenc") + + sink = gst.element_factory_make("filesink", "absink") + sink.set_property("location", self.capture_file) + + self._audiobin = gst.Bin("audiobin") + self._audiobin.add(src, rate, queue, enc, sink) + + src.link(rate, srccaps) + gst.element_link_many(rate, queue, enc, sink) + + def _log_queue_overrun(self, queue): + ''' We use a buffer, which may overflow. ''' + cbuffers = queue.get_property("current-level-buffers") + cbytes = queue.get_property("current-level-bytes") + ctime = queue.get_property("current-level-time") + + def is_recording(self): + ''' Are we recording? ''' + return self._recording + + def _get_state(self): + ''' What is the state of our gstreamer pipeline? ''' + return self._pipeline.get_state()[1] + + def start_recording_audio(self): + ''' Start the stream in order to start recording. ''' + if self._get_state() == gst.STATE_PLAYING: + return + self._pipeline.set_state(gst.STATE_PLAYING) + self._recording = True + + def stop_recording_audio(self): + ''' Stop recording and then convert the results into a + .ogg file using a new stream. ''' + self._pipeline.set_state(gst.STATE_NULL) + self._recording = False + + if not os.path.exists(self.capture_file) or \ + os.path.getsize(self.capture_file) <= 0: + return + + # Remove previous transcoding results. + if os.path.exists(self.save_file): + os.remove(self.save_file) + + line = 'filesrc location=' + self.capture_file + ' name=audioFilesrc ! wavparse name=audioWavparse ! audioconvert name=audioAudioconvert ! vorbisenc name=audioVorbisenc ! oggmux name=audioOggmux ! filesink name=audioFilesink' + audioline = gst.parse_launch(line) + + vorbis_enc = audioline.get_by_name('audioVorbisenc') + + audioFilesink = audioline.get_by_name('audioFilesink') + audioFilesink.set_property("location", self.save_file) + + audioBus = audioline.get_bus() + audioBus.add_signal_watch() + self._audio_transcode_handler = audioBus.connect( + 'message', self._onMuxedAudioMessageCb, audioline) + self._transcode_id = gobject.timeout_add( + 200, self._transcodeUpdateCb, audioline) + audioline.set_state(gst.STATE_PLAYING) + + def _transcodeUpdateCb(self, pipe): + ''' Where are we in the transcoding process? ''' + position, duration = self._query_position(pipe) + if position != gst.CLOCK_TIME_NONE: + value = position * 100.0 / duration + value = value/100.0 + return True + + def _query_position(self, pipe): + ''' Where are we in the stream? ''' + try: + position, format = pipe.query_position(gst.FORMAT_TIME) + except: + position = gst.CLOCK_TIME_NONE + + try: + duration, format = pipe.query_duration(gst.FORMAT_TIME) + except: + duration = gst.CLOCK_TIME_NONE + + return (position, duration) + + def _onMuxedAudioMessageCb(self, bus, message, pipe): + ''' Clean up at end of stream.''' + if message.type != gst.MESSAGE_EOS: + return True + + gobject.source_remove(self._audio_transcode_handler) + self._audio_transcode_handler = None + gobject.source_remove(self._transcode_id) + self._transcode_id = None + pipe.set_state(gst.STATE_NULL) + pipe.get_bus().remove_signal_watch() + pipe.get_bus().disable_sync_message_emission() + + os.remove(self.capture_file) + return False + + def _bus_message_handler(self, bus, message): + ''' Handle any messages associated with the stream. ''' + t = message.type + if t == gst.MESSAGE_EOS: + if self._eos_cb: + cb = self._eos_cb + self._eos_cb = None + cb() + elif t == gst.MESSAGE_ERROR: + # TODO: if we come out of suspend/resume with errors, then + # get us back up and running... TODO: handle "No space + # left on the resource.gstfilesink.c" err, debug = + # message.parse_error() + pass + + # We store the audio-record stream instance as tw.grecord so that + # we can use it repeatedly. + if not hasattr(tw, 'grecord'): + tw.grecord = Grecord(tw) + + # Sometime we need to parse multiple arguments, e.g., save, savename + save_name = _('Turtle Art') + ' ' + _('sound') + if type(arg) == type([]): + cmd = arg[0].lower() + if len(arg) > 1: + save_name = str(arg[1]) + else: + cmd = arg.lower() + + if cmd == 'start' or cmd == _('start').lower(): + tw.grecord.start_recording_audio() + elif cmd == 'stop' or cmd == _('stop').lower(): + tw.grecord.stop_recording_audio() + elif cmd == 'play' or cmd == _('play').lower(): + play_audio_from_file(tw.lc, tw.grecord.save_file) + elif cmd == 'save' or cmd == _('save').lower(): + if os.path.exists(tw.grecord.save_file) and tw.running_sugar: + dsobject = datastore.create() + dsobject.metadata['title'] = save_name + dsobject.metadata['icon-color'] = profile.get_color().to_string() + dsobject.metadata['mime_type'] = 'audio/ogg' + dsobject.set_file_path(tw.grecord.save_file) + datastore.write(dsobject) + dsobject.destroy() diff --git a/pysamples/load_block.py b/pysamples/load_block.py new file mode 100644 index 0000000..a457504 --- /dev/null +++ b/pysamples/load_block.py @@ -0,0 +1,78 @@ +#Copyright (c) 2011, Walter Bender + +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected. + +# Usage: Import this code into a Python (user-definable) block; when +# this code is run, the turtle will create a block at the current +# location of the turtle. The first argument to the Python block +# should be a string containing the name of the desired +# block. Arguments to the block can be passed by expanding the Python +# block to include up to two additional arguments. Note that the +# turtle is moved to the bottom of the block after it is loaded in +# order position another block to the bottom of the stack. + +# For example, try the following to place forward 100, right 90 on the canvas: +# start +# Python(forward, 100) <-- Python load_block.py expanded to two arguments +# Python(right, 90) <-- Python load_block.py expanded to two arguments + + +def myblock(tw, blkname): + ''' Load a block on to the canvas ''' + + from TurtleArt.tapalette import block_names, block_primitives, \ + special_names, content_blocks + from TurtleArt.tautils import find_group + + def make_block(tw, name, x, y, defaults): + x_pos = x + 20 + y_pos = y + 20 + tw._new_block(name, x_pos, y_pos, defaults) + + # Find the block we just created and attach it to a stack. + tw.drag_group = None + spr = tw.sprite_list.find_sprite((x_pos, y_pos)) + if spr is not None: + blk = tw.block_list.spr_to_block(spr) + if blk is not None: + tw.drag_group = find_group(blk) + tw._snap_to_dock() + + # Disassociate new block from mouse. + tw.drag_group = None + return blk.height + + def find_block(tw, blkname, x, y, defaults=None): + """ Create a new block. It is a bit more work than just calling + _new_block(). We need to: + (1) translate the label name into the internal block name; + (2) 'dock' the block onto a stack where appropriate; and + (3) disassociate the new block from the mouse. """ + + print blkname + for name in block_names: + # Translate label name into block/prim name. + if blkname in block_names[name]: + if (name in block_primitives and \ + block_primitives[name] == name) or \ + name in content_blocks: + return make_block(tw, name, x, y, defaults) + for name in special_names: + # Translate label name into block/prim name. + if blkname in special_names[name]: + return make_block(tw, name, x, y, defaults) + return -1 + + # Place the block at the active turtle (x, y) and move the turtle + # into position to place the next block in the stack. + x, y = tw.active_turtle.get_xy() + if type(blkname) == type([]): + name = blkname[0] + value = blkname[1:] + dy = int(find_block(tw, name, x, y, value)) + else: + name = blkname + dy = int(find_block(tw, name, x, y)) + + tw.active_turtle.move((x, y - dy)) diff --git a/pysamples/load_journal_entry_to_heap.py b/pysamples/load_journal_entry_to_heap.py index 7416554..3dd3bb5 100644 --- a/pysamples/load_journal_entry_to_heap.py +++ b/pysamples/load_journal_entry_to_heap.py @@ -1,35 +1,18 @@ -#Copyright (c) 2010, Walter Bender, Tony Forster +#Copyright (c) 2010-11, Walter Bender, Tony Forster -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected. -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. +# Usage: Import this code into a Python (user-definable) block; when +# this code is run, the chooser will be opened for selecting a file +# from the Journal. The contents of that file will be loaded onto the +# FILO heap. -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. -# This procedure is invoked when the user-definable block on the "extras" -# palette is selected. +def myblock(tw, x): # ignore second argument + ''' Load heap from journal (Sugar only) ''' -def myblock(lc, x): - - ########################################################################### - # - # Load heap from journal - # - ########################################################################### - - from tautils import chooser + from TurtleArt.tautils import chooser # Choose a datastore object and push data to heap (Sugar only) - chooser(lc.tw.parent, '', lc.push_file_data_to_heap) + chooser(tw.parent, '', tw.lc.push_file_data_to_heap) diff --git a/pysamples/paste_to_heap.py b/pysamples/paste_to_heap.py index 9255a93..0371078 100644 --- a/pysamples/paste_to_heap.py +++ b/pysamples/paste_to_heap.py @@ -1,33 +1,15 @@ -#Copyright (c) 2010, Walter Bender, Tony Forster - -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: - -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. - -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. +#Copyright (c) 2010-11, Walter Bender, Tony Forster # This procedure is invoked when the user-definable block on the "extras" # palette is selected. -def myblock(lc, x): +# Usage: Import this code into a Python (user-definable) block; when +# this code is run, the contents of the clipboard will be appended to +# the FILO heap. + - ########################################################################### - # - # Paste from clipboard to heap - # - ########################################################################### +def myblock(tw, x): # ignore second argument + ''' Paste from clipboard to heap ''' from gtk import Clipboard from tautils import data_from_string @@ -35,5 +17,5 @@ def myblock(lc, x): text = Clipboard().wait_for_text() if text is not None: for val in data_from_string(text): - lc.heap.append(val) - lc.update_label_value('pop', val) + tw.lc.heap.append(val) + tw.lc.update_label_value('pop', val) diff --git a/pysamples/push_mouse_event.py b/pysamples/push_mouse_event.py index 485061f..b14315c 100644 --- a/pysamples/push_mouse_event.py +++ b/pysamples/push_mouse_event.py @@ -1,40 +1,26 @@ -#Copyright (c) 2009-10, Walter Bender, Tony Forster +#Copyright (c) 2009-11, Walter Bender, Tony Forster -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected. -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. +# Usage: Import this code into a Python (user-definable) block; when +# this code is run, the current mouse status will be pushed to the +# FILO heap. If a mouse button event occurs, a y, x, and 1 are pushed +# to the heap. If no button is pressed, 0 is pushed to the heap. -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. +# To use these data, pop the heap in a compare block to determine if a +# button has been pushed. If a 1 was popped from the heap, pop the x +# and y coordinates. -# -# This procedure is invoked when the user-definable block on the "extras" -# palette is selected. -def myblock(lc, x): +def myblock(tw, x): # ignore second argument + ''' Push mouse event to stack ''' - ########################################################################### - # - # Push mouse event to stack - # - ########################################################################### - - if lc.tw.mouse_flag == 1: + if tw.mouse_flag == 1: # push y first so x will be popped first - lc.heap.append((lc.tw.canvas.height / 2) - lc.tw.mouse_y) - lc.heap.append(lc.tw.mouse_x - (lc.tw.canvas.width / 2)) - lc.heap.append(1) # mouse event - lc.tw.mouse_flag = 0 + tw.lc.heap.append((tw.canvas.height / 2) - tw.mouse_y) + tw.lc.heap.append(tw.mouse_x - (tw.canvas.width / 2)) + tw.lc.heap.append(1) # mouse event + tw.mouse_flag = 0 else: - lc.heap.append(0) # no mouse event + tw.lc.heap.append(0) # no mouse event diff --git a/pysamples/push_time.py b/pysamples/push_time.py index ae22684..5f86be4 100644 --- a/pysamples/push_time.py +++ b/pysamples/push_time.py @@ -1,37 +1,21 @@ -#Copyright (c) 2009-10, Walter Bender +#Copyright (c) 2009-11, Walter Bender -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected. -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. +# Usage: Import this code into a Python (user-definable) block; when +# this code is run, the current hour, minute, and second are pushed to +# the FILO heap. To use these values, pop second, then minute, then +# hour from the FILO. -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. -# This procedure is invoked when the user-definable block on the "extras" -# palette is selected. +def myblock(tw, x): # ignore second argument + ''' Push hours, minutes, seconds onto the FILO. ''' -def myblock(lc, x): - - ########################################################################### - # - # Push hours, minutes, seconds onto the FILO. # Use three 'pop' blocks to retrieve these values. # Note: because we use a FILO (first in, last out heap), # the first value you pop off of the FILO will be seconds. - # - ########################################################################### - lc.heap.append(localtime().tm_hour) - lc.heap.append(localtime().tm_min) - lc.heap.append(localtime().tm_sec) + tw.lc.heap.append(localtime().tm_hour) + tw.lc.heap.append(localtime().tm_min) + tw.lc.heap.append(localtime().tm_sec) diff --git a/pysamples/save_heap_to_journal_entry.py b/pysamples/save_heap_to_journal_entry.py index c4f2d50..a06d4d0 100644 --- a/pysamples/save_heap_to_journal_entry.py +++ b/pysamples/save_heap_to_journal_entry.py @@ -1,33 +1,16 @@ -#Copyright (c) 2010, Walter Bender, Tony Forster +#Copyright (c) 2010-11, Walter Bender, Tony Forster -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected. -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. +# Usage: Import this code into a Python (user-definable) block; when +# this code is run, the contents of the FILO heap are saved to a +# Journal entry named by the value of the argument to the Python +# block. -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. -# This procedure is invoked when the user-definable block on the "extras" -# palette is selected. - -def myblock(lc, x): - - ########################################################################### - # - # Save heap to journal (Sugar only) - # - ########################################################################### +def myblock(tw, title): + ''' Save heap to journal (Sugar only) ''' import os.path from gettext import gettext as _ @@ -36,18 +19,19 @@ def myblock(lc, x): from sugar.datastore import datastore from sugar import profile - from tautils import get_path, data_to_file + from TurtleArt.tautils import get_path, data_to_file # Save JSON-encoded heap to temporary file - heap_file = os.path.join(get_path(activity, 'instance'), str(x) + '.txt') - data_to_file(lc.heap, heap_file) + heap_file = os.path.join(get_path(activity, 'instance'), + str(title) + '.txt') + data_to_file(tw.lc.heap, heap_file) # Create a datastore object dsobject = datastore.create() # Write any metadata (specifically set the title of the file # and specify that this is a plain text file). - dsobject.metadata['title'] = str(x) + dsobject.metadata['title'] = str(title) dsobject.metadata['icon-color'] = profile.get_color().to_string() dsobject.metadata['mime_type'] = 'text/plain' dsobject.set_file_path(heap_file) diff --git a/pysamples/set_rgb.py b/pysamples/set_rgb.py index f524ec6..d4baa48 100644 --- a/pysamples/set_rgb.py +++ b/pysamples/set_rgb.py @@ -1,50 +1,19 @@ -#Copyright (c) 2009-10, Walter Bender +#Copyright (c) 2009-11, Walter Bender -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected and expanded to 3 arguments. -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. +# Usage: Import this code into a Python (user-definable) block. +# First, expand the Python block to reveal three numerics arguments. +# Set these values to the desired red, green, and blue. When the code +# is run, the red, green, and blue values are used to set the pen +# color. -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. -# -# This procedure is invoked when the user-definable block on the "extras" -# palette is selected and expanded to 3 arguments. - -def myblock(lc, x): - - ########################################################################### - # - # Set rgb color from values - # - ########################################################################### - - # assuming x is an array [r, g, b] - b = int(x[2]) - while b < 0: - b += 256 - while b > 255: - b -= 256 - g = int(x[1]) - while g < 0: - g += 256 - while g > 255: - g -= 256 - r = int(x[0]) - while r < 0: - r += 256 - while r > 255: - r -= 256 - rgb = "#%02x%02x%02x" % (r,g,b) - lc.tw.canvas.fgcolor = lc.tw.canvas.cm.alloc_color(rgb) +def myblock(tw, rgb_array): + ''' Set rgb color from values ''' + + rgb = "#%02x%02x%02x" % ((int(rgb_array[0]) % 256), + (int(rgb_array[1]) % 256), + (int(rgb_array[2]) % 256)) + tw.canvas.fgcolor = tw.canvas.cm.alloc_color(rgb) diff --git a/pysamples/sinewave.py b/pysamples/sinewave.py index eea975d..4f14c4c 100644 --- a/pysamples/sinewave.py +++ b/pysamples/sinewave.py @@ -1,33 +1,15 @@ -#Copyright (c) 2010, Tony Forster +#Copyright (c) 2010-11, Tony Forster -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected. -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. +# Usage: Import this code into a Python (user-definable) block and +# pass a frequency in Hertz to the Python block. A tone will play over +# the speaker at the specified frequency. -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. -# This procedure is invoked when the user-definable block on the "extras" -# palette is selected. - -def myblock(lc, x): - - ########################################################################### - # - # Plays a sound at frequency x - # - ########################################################################### +def myblock(tw, frequency): + ''' Plays a sound at frequency frequency ''' import os - os.system('speaker-test -t sine -l 1 -f %d' % (int(x))) + os.system('speaker-test -t sine -l 1 -f %d' % (int(frequency))) diff --git a/pysamples/speak.py b/pysamples/speak.py new file mode 100644 index 0000000..30762a9 --- /dev/null +++ b/pysamples/speak.py @@ -0,0 +1,57 @@ +#Copyright (c) 2009-11, Walter Bender, Tony Forster + +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected. + +# Usage: Import this code into a Python (user-definable) block and +# pass a string to be read by the voice synthesizer. If a second +# argument is passed, by expanding the Python block, it is used to specify +# the pitch level of the speaker. Valid range is 0 to 99. + + +def myblock(tw, arg): + ''' Text to speech ''' + + TABLE = {'af': 'afrikaans', 'cy': 'welsh-test', 'el': 'greek', + 'es': 'spanish', 'hi': 'hindi-test', 'hy': 'armenian', + 'ku': 'kurdish', 'mk': 'macedonian-test', 'pt': 'brazil', + 'sk': 'slovak', 'sw': 'swahili', 'bs': 'bosnian', 'da': 'danish', + 'en': 'english', 'fi': 'finnish', 'hr': 'croatian', + 'id': 'indonesian-test', 'la': 'latin', 'nl': 'dutch-test', + 'sq': 'albanian', 'ta': 'tamil', 'vi': 'vietnam-test', + 'ca': 'catalan', 'de': 'german', 'eo': 'esperanto', + 'fr': 'french', 'hu': 'hungarian', 'is': 'icelandic-test', + 'lv': 'latvian', 'no': 'norwegian', 'ro': 'romanian', + 'sr': 'serbian', 'zh': 'Mandarin', 'cs': 'czech', 'it': 'italian', + 'pl': 'polish', 'ru': 'russian_test', 'sv': 'swedish', + 'tr': 'turkish'} + import os + + pitch = None + if type(arg) == type([]): + text = arg[0] + if len(arg) > 1: + pitch = int(arg[1]) + if pitch > 99: + pitch = 99 + elif pitch < 0: + pitch = 0 + else: + text = arg + + # Turtle Art numbers are passed as float, + # but they may be integer values. + if type(text) == float and int(text) == text: + text = int(text) + + lang = os.environ['LANG'][0:2] + if lang in TABLE: + language_option = '-v ' + TABLE[lang] + else: + language_option = '' + if pitch is None: + os.system('espeak %s "%s" --stdout | aplay' % (language_option, + text)) + else: + os.system('espeak %s "%s" -p "%s" --stdout | aplay' % ( + language_option, text, pitch)) diff --git a/pysamples/svg_end_group.py b/pysamples/svg_end_group.py new file mode 100644 index 0000000..e4682b3 --- /dev/null +++ b/pysamples/svg_end_group.py @@ -0,0 +1,17 @@ +#Copyright (c) 2009-11, Walter Bender + +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected and expanded to 3 arguments. + +# Usage: Import this code into a Python (user-definable) block. +# First, expand the Python block to reveal three numerics arguments. +# Set these values to the desired red, green, and blue. When the code +# is run, the red, green, and blue values are used to set the pen +# color. + + +def myblock(tw, x): + ''' Add end group to SVG output ''' + + tw.svg_string += '' + diff --git a/pysamples/svg_start_group.py b/pysamples/svg_start_group.py new file mode 100644 index 0000000..57500bc --- /dev/null +++ b/pysamples/svg_start_group.py @@ -0,0 +1,17 @@ +#Copyright (c) 2009-11, Walter Bender + +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected and expanded to 3 arguments. + +# Usage: Import this code into a Python (user-definable) block. +# First, expand the Python block to reveal three numerics arguments. +# Set these values to the desired red, green, and blue. When the code +# is run, the red, green, and blue values are used to set the pen +# color. + + +def myblock(tw, x): + ''' Add start group to SVG output ''' + + tw.svg_string += '' + diff --git a/pysamples/uturn.py b/pysamples/uturn.py new file mode 100644 index 0000000..0b164a0 --- /dev/null +++ b/pysamples/uturn.py @@ -0,0 +1,35 @@ +#Copyright (c) 2011, Walter Bender + +# This procedure is invoked when the user-definable block on the +# "extras" palette is selected. + +# Usage: Import this code into a Python (user-definable) block; when +# it is run, a u-turn block will be added to the Turtle Palette. You +# can use the u-turn block as you would any other block. + + +def myblock(tw, arg): + ''' Add a uturn block to the 'turtle' palette ''' + + from TurtleArt.tapalette import make_palette, palette_name_to_index + from TurtleArt.talogo import primitive_dictionary + from gettext import gettext as _ + + # Choose a palette for the new block. + palette = make_palette('turtle') + + # Create a new block prototype. + palette.add_block('uturn', + style='basic-style-extended-vertical', + label=_('uturn'), + prim_name='uturn', + help_string=_('make a uturn')) + + # Add its primitive to the LogoCode dictionary. + tw.lc.def_prim('uturn', 0, + lambda self: primitive_dictionary['set']( + 'heading', tw.canvas.seth, tw.canvas.heading + 180)) + + # Regenerate the palette, which will now include the new block. + tw.show_toolbar_palette(palette_name_to_index('turtle'), + regenerate=True) diff --git a/samples/dice.ta b/samples/dice.ta new file mode 100644 index 0000000..7d45893 --- /dev/null +++ b/samples/dice.ta @@ -0,0 +1,99 @@ +[[0, ["start", 2.0], 743, 65, [null, 24]], +[1, ["random", 0], 273, 616, [7, 2, 3, null]], +[2, ["number", 1], 333, 616, [1, null]], +[3, ["number", 6], 333, 658, [1, null]], +[4, ["random", 0], 273, 534, [7, 5, 6, null]], +[5, ["number", 1], 333, 534, [4, null]], +[6, ["number", 6], 333, 576, [4, null]], +[7, ["plus2", 20], 219, 534, [11, 4, 1]], +[8, ["storein", 0], 85, 698, [15, 13, 14, null]], +[9, ["number", 1], 237, 782, [14, null]], +[10, "box", 237, 740, [14, 12, null]], +[11, "storeinbox1", 85, 534, [16, 7, 15]], +[12, "box1", 295, 740, [10, null]], +[13, "box1", 183, 698, [8, null]], +[14, ["plus2", 0], 183, 740, [8, 10, 9]], +[15, ["vspace", 40], 85, 576, [11, 8]], +[16, "repeat", 22, 474, [18, 27, 11, 17]], +[17, ["vspace", 0], 22, 552, [16, null]], +[18, "hat", 22, 424, [null, 19, 16]], +[19, ["string", "toss dice"], 83, 432, [18, null]], +[20, "stack", 743, 233, [31, 21, 22]], +[21, ["string", "toss dice"], 804, 233, [20, null]], +[22, "stack", 743, 275, [20, 23, null]], +[23, ["string", "plot results"], 804, 275, [22, null]], +[24, ["storein", 0], 743, 107, [0, 25, 26, 31]], +[25, ["string", "trials"], 841, 107, [24, null]], +[26, ["number", 1600], 841, 149, [24, null]], +[27, "box", 71, 474, [16, 28, null]], +[28, ["string", "trials"], 129, 474, [27, null]], +[29, "hat", 1086, 452, [null, 30, 36]], +[30, ["string", "clear bins"], 1147, 460, [29, null]], +[31, "stack", 743, 191, [24, 32, 20]], +[32, ["string", "clear bins"], 804, 191, [31, null]], +[33, ["storein", 0], 1149, 604, [42, 35, 34, 38]], +[34, ["number", 0], 1247, 646, [33, null]], +[35, "box1", 1247, 604, [33, null]], +[36, "storeinbox1", 1086, 502, [29, 37, 42]], +[37, ["number", 1], 1220, 502, [36, null]], +[38, "storeinbox1", 1149, 688, [33, 41, null]], +[39, ["number", 1], 1337, 730, [41, null]], +[40, "box1", 1337, 688, [41, null]], +[41, ["plus2", 0], 1283, 688, [38, 40, 39]], +[42, "repeat", 1086, 544, [36, 43, 33, 44]], +[43, ["number", 12], 1135, 544, [42, null]], +[44, ["vspace", 0], 1086, 622, [42, null]], +[45, "hat", 528, 428, [null, 46, 82]], +[46, ["string", "plot results"], 589, 436, [45, null]], +[47, "penup", 591, 664, [58, 49]], +[48, "pendown", 591, 996, [90, 78]], +[49, ["setxy2", 40], 591, 706, [47, 54, 50, 86]], +[50, ["number", 0], 656, 828, [49, null]], +[51, "storeinbox1", 528, 562, [83, 52, 58]], +[52, ["number", 2], 662, 562, [51, null]], +[53, "box1", 764, 748, [55, null]], +[54, ["product2", 0], 656, 706, [49, 57, 55]], +[55, ["minus2", 0], 710, 748, [54, 53, 56]], +[56, ["number", 6], 788, 790, [55, null]], +[57, ["number", 50], 710, 706, [54, null]], +[58, "repeat", 528, 604, [51, 59, 47, 97]], +[59, ["number", 11], 577, 604, [58, null]], +[60, ["vspace", 40], 591, 1284, [73, 74]], +[61, "repeat", 591, 1164, [72, 62, 64, 73]], +[62, ["number", 2], 640, 1164, [61, null]], +[63, ["vspace", 0], 591, 1080, [78, 72]], +[64, "forward", 654, 1224, [61, 85, 66]], +[65, "box1", 791, 1224, [85, null]], +[66, "right", 654, 1266, [64, 67, 68]], +[67, ["number", 90], 729, 1266, [66, null]], +[68, "forward", 654, 1308, [66, 69, 70]], +[69, ["number", 40], 733, 1308, [68, null]], +[70, "right", 654, 1350, [68, 71, null]], +[71, ["number", 90], 729, 1350, [70, null]], +[72, "startfill", 591, 1122, [63, 61]], +[73, "stopfill", 591, 1242, [61, 60]], +[74, "storeinbox1", 591, 1406, [60, 77, null]], +[75, ["number", 1], 779, 1448, [77, null]], +[76, "box1", 779, 1406, [77, null]], +[77, ["plus2", 0], 725, 1406, [74, 76, 75]], +[78, "setcolor", 591, 1038, [48, 80, 63]], +[79, ["number", 10], 730, 1080, [80, null]], +[80, ["product2", 0], 676, 1038, [78, 81, 79]], +[81, "box1", 730, 1038, [80, null]], +[82, "clean", 528, 478, [45, 83]], +[83, "setshade", 528, 520, [82, 84, 51]], +[84, ["number", 25], 609, 520, [83, null]], +[85, "box", 733, 1224, [64, 65, null]], +[86, "back", 591, 870, [49, 87, 88]], +[87, ["number", 50], 649, 870, [86, null]], +[88, "show", 591, 912, [86, 89, 90]], +[89, "box1", 664, 912, [88, null]], +[90, "forward", 591, 954, [88, 91, 48]], +[91, ["number", 50], 670, 954, [90, null]], +[92, "penup", 528, 1444, [97, 94]], +[93, "pendown", 528, 1570, [94, null]], +[94, ["setxy2", 0], 528, 1486, [92, 95, 96, 93]], +[95, ["number", 0], 593, 1486, [94, null]], +[96, ["number", -50], 593, 1528, [94, null]], +[97, ["vspace", 360], 528, 682, [58, 92]], +[-1, ["turtle", "Yertle"], 0.0, -50.0, 0.0, 120.0, 25.0, 5]] \ No newline at end of file diff --git a/samples/hoops.ta b/samples/hoops.ta new file mode 100644 index 0000000..2596cb4 --- /dev/null +++ b/samples/hoops.ta @@ -0,0 +1,101 @@ +[[0, ["start", 2.0], 639, 62, [null, 100]], +[1, "skin", 639, 398, [24, 2, 49]], +[2, ["journal", "./samples/images/basketball.png"], 821, 398, [1, null]], +[3, ["setxy2", 60], 19, 243, [16, 5, 33, null]], +[4, "xcor", 138, 243, [5, null]], +[5, ["plus2", 0], 84, 243, [3, 4, 63]], +[6, ["setxy2", 0], 564, 780, [14, 26, 7, 29]], +[7, ["number", 0], 629, 822, [6, null]], +[8, "seth", 220, 827, [12, 11, 36]], +[9, ["number", 90], 372, 869, [11, null]], +[10, "heading", 372, 827, [11, null]], +[11, ["plus2", 0], 318, 827, [8, 10, 9]], +[12, "wait", 220, 785, [18, 13, 8]], +[13, ["number", 0.25], 291, 785, [12, null]], +[14, "penup", 564, 738, [53, 6]], +[15, "pendown", 564, 948, [47, null]], +[16, "hat", 19, 193, [null, 17, 3]], +[17, ["string", "parabola"], 80, 201, [16, null]], +[18, "stack", 220, 743, [40, 19, 12]], +[19, ["string", "parabola"], 281, 743, [18, null]], +[20, "show", 639, 314, [21, 23, 24]], +[21, "setscale", 639, 272, [68, 22, 20]], +[22, ["number", 100], 735, 272, [21, null]], +[23, ["journal", "./samples/images/basketball-court1-a.png"], 712, 314, [20, null]], +[24, "setscale", 639, 356, [20, 25, 1]], +[25, ["number", 33], 735, 356, [24, null]], +[26, "leftpos", 629, 780, [6, null]], +[27, "storeinbox2", 220, 911, [36, 35, 38]], +[28, ["number", 1], 432, 953, [35, null]], +[29, "storeinbox2", 564, 864, [6, 30, 47]], +[30, ["number", 40.0], 698, 864, [29, null]], +[31, "box2", 408, 911, [35, null]], +[32, "box2", 192, 447, [64, null]], +[33, ["plus2", 0], 84, 405, [3, 34, 64]], +[34, "ycor", 138, 405, [33, null]], +[35, ["minus2", 0], 354, 911, [27, 31, 28]], +[36, ["vspace", 0], 220, 869, [8, 27]], +[37, "forever", 15, 615, [45, 39, null]], +[38, ["vspace", 0], 220, 953, [27, 85]], +[39, "ifelse", 116, 633, [37, 42, 40, 41, null]], +[40, ["vspace", 0], 220, 701, [39, 18]], +[41, "stopstack", 272, 701, [39, null]], +[42, ["greater2", 0], 206, 599, [39, 44, 43, null]], +[43, ["number", -1], 276, 641, [42, null]], +[44, "ycor", 252, 599, [42, null]], +[45, "hat", 15, 565, [null, 46, 37]], +[46, ["string", "shoot"], 76, 573, [45, null]], +[47, "stack", 564, 906, [29, 48, 15]], +[48, ["string", "shoot"], 625, 906, [47, null]], +[49, "setpensize", 639, 440, [1, 50, 54]], +[50, ["number", 1], 746, 440, [49, null]], +[51, "storeinbox1", 488, 586, [81, 52, 55]], +[52, "volume", 622, 586, [51, null]], +[53, "if", 488, 670, [55, 56, 14, null]], +[54, "forever", 639, 482, [49, 83, null]], +[55, ["vspace", 0], 488, 628, [51, 53]], +[56, ["greater2", 0], 550, 636, [53, 57, 58, null]], +[57, "box1", 596, 636, [56, null]], +[58, ["number", 300.0], 620, 678, [56, null]], +[59, ["division2", 0], 192, 327, [63, 61, 62]], +[60, ["number", 30.0], 192, 285, [63, null]], +[61, "box1", 246, 327, [59, null]], +[62, "width", 270, 369, [59, null]], +[63, ["product2", 0], 138, 285, [5, 60, 59]], +[64, ["product2", 0], 138, 447, [33, 32, 65]], +[65, ["division2", 0], 192, 489, [64, 66, 67]], +[66, "box1", 246, 489, [65, null]], +[67, "height", 270, 531, [65, null]], +[68, ["fillscreen", 0], 639, 188, [98, 69, 70, 21]], +[69, ["number", 60], 779, 188, [68, null]], +[70, ["number", 80], 779, 230, [68, null]], +[71, "readpixel", 921, 139, [87, 80]], +[72, "pop", 1029, 189, [89, null]], +[73, "pop", 1105, 299, [90, null]], +[74, "pop", 1181, 409, [91, null]], +[75, "if", 921, 223, [80, 89, 76, null]], +[76, ["vspace", 0], 997, 291, [75, 77]], +[77, "if", 997, 333, [76, 90, 78, null]], +[78, ["vspace", 0], 1073, 401, [77, 79]], +[79, "if", 1073, 443, [78, 91, 95, null]], +[80, ["vspace", 0], 921, 181, [71, 75]], +[81, "hat", 488, 536, [null, 82, 51]], +[82, ["string", "trigger"], 549, 544, [81, null]], +[83, "stack", 740, 500, [54, 84, null]], +[84, ["string", "trigger"], 801, 500, [83, null]], +[85, "stack", 220, 995, [38, 86, 99]], +[86, ["string", "test"], 281, 995, [85, null]], +[87, "hat", 921, 89, [null, 88, 71]], +[88, ["string", "test"], 982, 97, [87, null]], +[89, ["equal2", 0], 983, 189, [75, 72, 92, null]], +[90, ["equal2", 0], 1059, 299, [77, 73, 93, null]], +[91, ["equal2", 0], 1135, 409, [79, 74, 94, null]], +[92, ["number", 255.0], 1029, 231, [89, null]], +[93, ["number", 255.0], 1105, 341, [90, null]], +[94, ["number", 255.0], 1181, 451, [91, null]], +[95, "print", 1149, 511, [79, 96, 97]], +[96, ["string", "BASKET"], 1226, 511, [95, null]], +[97, "stopstack", 1149, 553, [95, null]], +[98, "clean", 639, 146, [100, 68]], +[99, "clearheap", 220, 1037, [85, null]], +[100, "hideblocks", 639, 104, [0, 98]]] diff --git a/samples/images/basketball-court1-a.png b/samples/images/basketball-court1-a.png new file mode 100644 index 0000000..dd3d6bb --- /dev/null +++ b/samples/images/basketball-court1-a.png Binary files differ diff --git a/samples/images/basketball.png b/samples/images/basketball.png new file mode 100644 index 0000000..e8af3fd --- /dev/null +++ b/samples/images/basketball.png Binary files differ diff --git a/samples/images/turtle-a.png b/samples/images/turtle-a.png new file mode 100644 index 0000000..e2c897a --- /dev/null +++ b/samples/images/turtle-a.png Binary files differ diff --git a/samples/images/turtle-b.png b/samples/images/turtle-b.png new file mode 100644 index 0000000..3b45dd7 --- /dev/null +++ b/samples/images/turtle-b.png Binary files differ diff --git a/samples/loudness-monitor.ta b/samples/loudness-monitor.ta new file mode 100644 index 0000000..7bb0b5f --- /dev/null +++ b/samples/loudness-monitor.ta @@ -0,0 +1,74 @@ +[[0, ["start", 2.0], 1217, 22, [null, 11]], +[1, ["setxy2", 20], 1434, 84, [8, 3, 6, 46]], +[2, "bottompos", 1546, 166, [6, null]], +[3, "random", 1492, 84, [1, 4, 5, null]], +[4, "leftpos", 1578, 84, [3, null]], +[5, "rightpos", 1578, 126, [3, null]], +[6, ["plus2", 0], 1492, 166, [1, 2, 7]], +[7, ["number", 100], 1546, 208, [6, null]], +[8, "penup", 1434, 42, [10, 1]], +[9, "pendown", 1434, 250, [46, 52]], +[10, "hat1", 1434, 0, [null, 8]], +[11, "stack1", 1217, 64, [0, 12]], +[12, "stack2", 1217, 106, [11, null]], +[13, "hat2", 833, 0, [null, 14]], +[14, "forever", 833, 42, [13, 38, 15]], +[15, ["vspace", 0], 833, 78, [14, null]], +[16, "storeinbox1", 894, 312, [71, 17, 40]], +[17, "volume", 1012, 312, [16, null]], +[18, "storeinbox1", 1434, 460, [47, 19, 42]], +[19, ["number", 0.0], 1552, 460, [18, null]], +[20, "forward", 1224, 433, [28, 27, 21]], +[21, "right", 1224, 475, [20, 22, 23]], +[22, ["number", 90], 1282, 475, [21, null]], +[23, "forward", 1224, 517, [21, 24, 25]], +[24, ["number", 25.0], 1294, 517, [23, null]], +[25, "right", 1224, 559, [23, 26, null]], +[26, ["number", 90], 1282, 559, [25, null]], +[27, "box1", 1294, 433, [20, null]], +[28, "repeat", 1160, 373, [50, 29, 20, 51]], +[29, ["number", 2.0], 1210, 373, [28, null]], +[30, "hat", 1160, 281, [null, 31, 50]], +[31, ["string", "bar"], 1219, 289, [30, null]], +[32, "stack", 894, 102, [38, 33, 64]], +[33, ["string", "bar"], 953, 102, [32, null]], +[34, "stack", 894, 396, [40, 35, 36]], +[35, ["string", "bar"], 953, 396, [34, null]], +[36, "wait", 894, 438, [34, 37, 62]], +[37, ["number", 0.5], 952, 438, [36, null]], +[38, "setcolor", 894, 60, [14, 39, 32]], +[39, "white", 972, 60, [38, null]], +[40, "setcolor", 894, 354, [16, 41, 34]], +[41, "box2", 972, 354, [40, null]], +[42, "storeinbox2", 1434, 502, [18, 43, null]], +[43, "random", 1552, 502, [42, 44, 45, null]], +[44, ["number", 0], 1638, 502, [43, null]], +[45, ["number", 100], 1638, 544, [43, null]], +[46, ["vspace", 0], 1434, 208, [1, 9]], +[47, ["fillscreen", 0], 1434, 376, [52, 48, 49, 18]], +[48, ["number", 60], 1520, 376, [47, null]], +[49, "white", 1520, 418, [47, null]], +[50, "startfill", 1160, 331, [30, 28]], +[51, "stopfill", 1160, 451, [28, null]], +[52, ["storein", 0], 1434, 292, [9, 53, 54, 47]], +[53, ["string", "max"], 1503, 292, [52, null]], +[54, ["number", 0.0], 1503, 334, [52, null]], +[55, "box", 1002, 530, [63, 56, null]], +[56, ["string", "max"], 1056, 530, [55, null]], +[57, ["storein", 0], 946, 590, [61, 58, 59, null]], +[58, ["string", "max"], 1015, 590, [57, null]], +[59, "box1", 1015, 632, [57, null]], +[60, "box1", 978, 488, [63, null]], +[61, "if", 894, 522, [62, 63, 57, null]], +[62, ["vspace", 0], 894, 480, [36, 61]], +[63, ["greater2", 0], 932, 488, [61, 60, 55, null]], +[64, "setcolor", 894, 144, [32, 67, 73]], +[65, ["number", 50.0], 1026, 186, [67, null]], +[66, "color", 1026, 144, [67, null]], +[67, ["plus2", 0], 972, 144, [64, 66, 65]], +[68, "storeinbox1", 894, 228, [73, 69, 71]], +[69, "box", 1012, 228, [68, 70, null]], +[70, ["string", "max"], 1066, 228, [69, null]], +[71, "stack", 894, 270, [68, 72, 16]], +[72, ["string", "bar"], 953, 270, [71, null]], +[73, ["vspace", 0], 894, 186, [64, 68]]] diff --git a/samples/love-speaks-volumes.ta b/samples/love-speaks-volumes.ta new file mode 100644 index 0000000..30e0cb3 --- /dev/null +++ b/samples/love-speaks-volumes.ta @@ -0,0 +1,109 @@ +[[0, ["start", 2.0], 760, 100, [null, 55]], +[1, "hat1", 780, 260, [null, 106]], +[2, "hat2", 120, 200, [null, 80]], +[3, "setcolor", 1164, 992, [51, 4, 16]], +[4, ["number", 0], 1242, 992, [3, null]], +[5, "stack1", 1164, 698, [17, 62]], +[6, "volume", 1233, 572, [23, null]], +[7, "forever", 1100, 344, [102, 29, 103]], +[8, "penup", 780, 378, [67, 101]], +[9, "pendown", 780, 462, [101, 70]], +[10, "penup", 780, 546, [70, 75]], +[11, "pendown", 780, 714, [78, 107]], +[12, "clean", 900, 344, [104, 13]], +[13, ["fillscreen", 0], 900, 386, [12, 15, 14, 35]], +[14, ["number", 80], 986, 428, [13, null]], +[15, "white", 986, 386, [13, null]], +[16, "stack1", 1164, 1034, [3, 42]], +[17, "setcolor", 1164, 656, [64, 18, 5]], +[18, "white", 1242, 656, [17, null]], +[19, ["storein", 0], 1164, 446, [29, 20, 27, 23]], +[20, ["string", "b"], 1233, 446, [19, null]], +[21, ["storein", 0], 900, 554, [35, 22, 41, 38]], +[22, ["string", "b"], 969, 554, [21, null]], +[23, ["storein", 0], 1164, 530, [19, 24, 6, 64]], +[24, ["string", "a"], 1233, 530, [23, null]], +[25, "box", 1283, 908, [63, 26, null]], +[26, ["string", "a"], 1338, 908, [25, null]], +[27, "box", 1233, 488, [19, 28, null]], +[28, ["string", "a"], 1288, 488, [27, null]], +[29, ["storein", 0], 1164, 362, [7, 30, 31, 19]], +[30, ["string", "c"], 1233, 362, [29, null]], +[31, "box", 1233, 404, [29, 32, null]], +[32, ["string", "b"], 1288, 404, [31, null]], +[33, "box", 1283, 614, [64, 34, null]], +[34, ["string", "c"], 1338, 614, [33, null]], +[35, ["storein", 0], 900, 470, [13, 36, 37, 21]], +[36, ["string", "a"], 969, 470, [35, null]], +[37, ["number", 0], 969, 512, [35, null]], +[38, ["storein", 0], 900, 638, [21, 39, 40, 105]], +[39, ["string", "c"], 969, 638, [38, null]], +[40, ["number", 0], 969, 680, [38, null]], +[41, ["number", 0], 969, 596, [21, null]], +[42, "wait", 1164, 1076, [16, 43, null]], +[43, ["number", 0.1], 1222, 1076, [42, null]], +[44, "setshade", 1164, 782, [62, 45, 46]], +[45, ["number", 75], 1250, 782, [44, null]], +[46, "setcolor", 1164, 824, [44, 47, 50]], +[47, ["number", 0], 1242, 824, [46, null]], +[48, "box", 1283, 740, [62, 49, null]], +[49, ["string", "b"], 1338, 740, [48, null]], +[50, "stack1", 1164, 866, [46, 63]], +[51, "setshade", 1164, 950, [63, 52, 3]], +[52, ["number", 50], 1250, 950, [51, null]], +[53, "hat", 900, 260, [null, 54, 104]], +[54, ["string", "setup"], 958, 268, [53, null]], +[55, "stack", 760, 142, [0, 56, 59]], +[56, ["string", "setup"], 818, 142, [55, null]], +[57, "hat", 1100, 260, [null, 58, 102]], +[58, ["string", "loop"], 1158, 268, [57, null]], +[59, "stack", 760, 184, [55, 60, null]], +[60, ["string", "loop"], 818, 184, [59, null]], +[61, "box1", 953, 336, [71, null]], +[62, "storeinbox1", 1164, 740, [5, 48, 44]], +[63, "storeinbox1", 1164, 908, [50, 25, 51]], +[64, "storeinbox1", 1164, 614, [23, 33, 17]], +[65, "forward", 120, 326, [80, 82, 99]], +[66, "box2", 178, 284, [80, null]], +[67, "storeinbox2", 780, 336, [106, 71, 8]], +[68, "box2", 246, 326, [82, null]], +[69, "box2", 906, 420, [73, null]], +[70, "stack2", 780, 504, [9, 10]], +[71, ["division2", 0], 899, 336, [67, 61, 72]], +[72, ["number", 2], 977, 378, [71, null]], +[73, ["division2", 0], 852, 420, [101, 69, 74]], +[74, ["number", 2], 930, 462, [73, null]], +[75, ["setxy2", 0], 780, 588, [10, 76, 77, 78]], +[76, ["number", 0], 838, 588, [75, null]], +[77, ["number", 0], 838, 630, [75, null]], +[78, "seth", 780, 672, [75, 79, 11]], +[79, ["number", 0], 882, 672, [78, null]], +[80, ["arc", 0], 120, 242, [2, 81, 66, 65]], +[81, ["number", 225], 178, 242, [80, null]], +[82, ["product2", 0], 192, 326, [65, 68, 85]], +[83, "sqrt", 300, 410, [85, 84]], +[84, ["number", 2], 354, 410, [83, null]], +[85, ["product2", 0], 246, 368, [82, 86, 83]], +[86, ["number", 1.7], 300, 368, [85, null]], +[87, ["arc", 0], 120, 616, [100, 88, 89, null]], +[88, ["number", 225], 178, 616, [87, null]], +[89, "box2", 178, 658, [87, null]], +[90, "right", 120, 450, [99, 91, 92]], +[91, ["number", 90], 178, 450, [90, null]], +[92, "forward", 120, 492, [90, 93, 100]], +[93, ["product2", 0], 192, 492, [92, 94, 95]], +[94, "box2", 246, 492, [93, null]], +[95, ["product2", 0], 246, 534, [93, 96, 97]], +[96, ["number", 1.7], 300, 534, [95, null]], +[97, "sqrt", 300, 576, [95, 98]], +[98, ["number", 2], 354, 576, [97, null]], +[99, ["vspace", 20], 120, 368, [65, 90]], +[100, ["vspace", 20], 120, 534, [92, 87]], +[101, "forward", 780, 420, [8, 73, 9]], +[102, "sandwichtop_no_arm_no_label", 1082, 310, [57, 7]], +[103, ["sandwichcollapsed", 1], 1100, 344, [7, null]], +[104, "sandwichtop_no_arm_no_label", 882, 310, [53, 12]], +[105, ["sandwichcollapsed", 1], 900, 344, [38, null]], +[106, "sandwichtop_no_arm_no_label", 762, 302, [1, 67]], +[107, ["sandwichcollapsed", 1], 780, 336, [11, null]], +[-1, ["turtle", "Yertle"], 0, 0, 0.0, 0.0, 50.0, 5]] \ No newline at end of file diff --git a/samples/scratch.ta b/samples/scratch.ta new file mode 100644 index 0000000..ed80e40 --- /dev/null +++ b/samples/scratch.ta @@ -0,0 +1,88 @@ +[[0, ["start", 2.0], 0, 180, [null, 14]], +[1, "skin", 720, 474, [20, 2, 7]], +[2, ["journal", "./samples/images/turtle-a.png"], 902, 474, [1, null]], +[3, "skin", 720, 600, [44, 4, 35]], +[4, ["journal", "./samples/images/turtle-b.png"], 902, 600, [3, null]], +[5, "addturtle", 720, 390, [74, 6, 20]], +[6, ["number", 1], 788, 390, [5, null]], +[7, "addturtle", 720, 516, [1, 8, 44]], +[8, ["number", 2], 788, 516, [7, null]], +[9, "show", 720, 306, [11, 10, 74]], +[10, ["journal", "./samples/images/Boston.png"], 793, 306, [9, null]], +[11, "setscale", 720, 264, [45, 12, 9]], +[12, ["number", 100], 816, 264, [11, null]], +[13, "hat1", 720, 180, [null, 45]], +[14, "stack1", 0, 222, [0, 69]], +[15, "stack2", 101, 534, [46, 53]], +[16, "hat2", 360, 180, [null, 75]], +[17, ["setxy2", 20], 461, 282, [21, 19, 42, 28]], +[18, "xcor", 580, 324, [19, null]], +[19, ["plus2", 0], 526, 282, [17, 47, 18]], +[20, "penup", 720, 432, [5, 1]], +[21, "addturtle", 461, 240, [75, 22, 17]], +[22, ["number", 1], 529, 240, [21, null]], +[23, "addturtle", 461, 448, [28, 24, 25]], +[24, ["number", 2], 529, 448, [23, null]], +[25, ["setxy2", 20], 461, 490, [23, 26, 43, 30]], +[26, ["plus2", 0], 526, 490, [25, 48, 27]], +[27, "xcor", 580, 532, [26, null]], +[28, "wait", 461, 406, [17, 29, 23]], +[29, ["number", 0.5], 532, 406, [28, null]], +[30, "wait", 461, 614, [25, 31, 80]], +[31, ["number", 0.25], 532, 614, [30, null]], +[32, ["setxy2", 0], 720, 684, [35, 34, 33, 37]], +[33, ["number", -100], 785, 726, [32, null]], +[34, "leftpos", 785, 684, [32, null]], +[35, "addturtle", 720, 642, [3, 36, 32]], +[36, ["number", 1], 788, 642, [35, null]], +[37, "addturtle", 720, 768, [32, 38, 39]], +[38, ["number", 2], 788, 768, [37, null]], +[39, ["setxy2", 0], 720, 810, [37, 40, 41, null]], +[40, "leftpos", 785, 810, [39, null]], +[41, ["number", -100], 785, 852, [39, null]], +[42, "ycor", 526, 364, [17, null]], +[43, "ycor", 526, 572, [25, null]], +[44, "penup", 720, 558, [7, 3]], +[45, "clean", 720, 222, [13, 11]], +[46, "storeinbox1", 101, 492, [66, 71, 15]], +[47, "box1", 580, 282, [19, null]], +[48, "box1", 580, 490, [26, null]], +[49, "seth", 101, 618, [53, 50, 55]], +[50, ["number", 180], 199, 618, [49, null]], +[51, "seth", 101, 702, [55, 52, 58]], +[52, ["number", 180], 199, 702, [51, null]], +[53, "addturtle", 101, 576, [15, 54, 49]], +[54, ["number", 1], 169, 576, [53, null]], +[55, "addturtle", 101, 660, [49, 56, 51]], +[56, ["number", 2], 169, 660, [55, null]], +[57, "forever", 0, 306, [69, 60, null]], +[58, "storeinbox1", 101, 744, [51, 73, 68]], +[59, ["number", 0], 289, 744, [73, null]], +[60, "addturtle", 101, 324, [57, 61, 62]], +[61, ["number", 1], 169, 324, [60, null]], +[62, "seth", 101, 366, [60, 63, 64]], +[63, ["number", 0], 199, 366, [62, null]], +[64, "addturtle", 101, 408, [62, 65, 66]], +[65, ["number", 2], 169, 408, [64, null]], +[66, "seth", 101, 450, [64, 67, 46]], +[67, ["number", 0], 199, 450, [66, null]], +[68, "stack2", 101, 786, [58, null]], +[69, "storeinbox2", 0, 264, [14, 70, 57]], +[70, ["number", 20], 134, 264, [69, null]], +[71, "box2", 235, 492, [46, null]], +[72, "box2", 313, 786, [73, null]], +[73, ["minus2", 0], 235, 744, [58, 59, 72]], +[74, ["vspace", 0], 720, 348, [9, 5]], +[75, "forever", 360, 222, [16, 21, null]], +[76, ["vspace", 0], 461, 784, [77, 79]], +[77, "if", 461, 698, [80, 82, 78, 76]], +[78, "stopstack", 537, 766, [77, null]], +[79, "if", 461, 826, [76, 85, 81, null]], +[80, ["vspace", 0], 461, 656, [30, 77]], +[81, "stopstack", 537, 894, [79, null]], +[82, ["greater2", 0], 523, 664, [77, 83, 84, null]], +[83, "xcor", 569, 664, [82, null]], +[84, "rightpos", 593, 706, [82, null]], +[85, ["less2", 0], 523, 792, [79, 86, 87, null]], +[86, "xcor", 569, 792, [85, null]], +[87, "leftpos", 593, 834, [85, null]]] diff --git a/samples/spectrum_analyzer.ta b/samples/spectrum_analyzer.ta new file mode 100644 index 0000000..bb850d3 --- /dev/null +++ b/samples/spectrum_analyzer.ta @@ -0,0 +1,53 @@ +[[0, ["start", 2.0], 114, 36, [null, 31]], +[1, ["myfunc1arg", 0], 297, 138, [35, 2, 4, null]], +[2, ["string", "int(x)"], 378, 138, [1, null]], +[3, ["number", 100.0], 456, 222, [4, null]], +[4, ["division2", 0], 378, 180, [1, 5, 3]], +[5, "pitch", 432, 180, [4, null]], +[6, "forever", 114, 120, [31, 35, null]], +[7, "repeat", 644, 157, [10, 8, 16, 9]], +[8, ["number", 30.0], 692, 157, [7, null]], +[9, ["vspace", 0], 644, 235, [7, null]], +[10, "storeinbox1", 644, 115, [30, 11, 7]], +[11, ["number", 2.0], 763, 115, [10, null]], +[12, "storeinbox1", 706, 631, [19, 15, null]], +[13, ["number", 1.0], 879, 673, [15, null]], +[14, "box1", 879, 631, [15, null]], +[15, ["plus2", 0], 825, 631, [12, 14, 13]], +[16, "addturtle", 706, 217, [7, 17, 45]], +[17, "box1", 764, 217, [16, null]], +[18, "penup", 706, 343, [44, 20]], +[19, "pendown", 706, 589, [20, 12]], +[20, ["setxy2", 60], 706, 385, [18, 27, 22, 19]], +[21, ["number", 30.0], 950, 427, [26, null]], +[22, ["number", -200.0], 764, 547, [20, null]], +[23, "leftpos", 818, 507, [27, null]], +[24, "width", 926, 385, [26, null]], +[25, "box1", 980, 467, [42, null]], +[26, ["division2", 0], 872, 385, [28, 24, 21]], +[27, ["plus2", 40], 764, 385, [20, 28, 23]], +[28, ["product2", 20], 818, 385, [27, 26, 52]], +[29, "hat1", 644, 31, [null, 30]], +[30, "clean", 644, 73, [29, 10]], +[31, "stack1", 114, 78, [0, 6]], +[32, "addturtle", 230, 450, [38, 36, 33]], +[33, "forward", 230, 492, [32, 34, null]], +[34, ["number", 1.0], 302, 492, [33, null]], +[35, "storeinbox2", 178, 138, [6, 1, 39]], +[36, "box2", 288, 450, [32, null]], +[37, "box2", 308, 340, [41, null]], +[38, "if", 178, 382, [39, 48, 32, null]], +[39, ["vspace", 80], 178, 180, [35, 38]], +[40, ["number", 1.0], 332, 382, [41, null]], +[41, ["greater2", 0], 262, 340, [48, 37, 40, null]], +[42, ["minus2", 0], 926, 467, [52, 25, 43]], +[43, ["number", 1.0], 1004, 509, [42, null]], +[44, "setcolor", 706, 301, [45, 47, 18]], +[45, "setshade", 706, 259, [16, 46, 44]], +[46, ["number", 25.0], 792, 259, [45, null]], +[47, "box1", 784, 301, [44, null]], +[48, ["and2", 0], 216, 300, [38, 49, 41]], +[49, ["less2", 0], 262, 258, [48, 50, 51, null]], +[50, "box2", 308, 258, [49, null]], +[51, ["number", 21.0], 332, 300, [49, null]], +[52, ["identity2", 0], 872, 467, [28, 42]]] diff --git a/samples/spiralaterals.ta b/samples/spiralaterals.ta new file mode 100644 index 0000000..9099145 --- /dev/null +++ b/samples/spiralaterals.ta @@ -0,0 +1,55 @@ +[[0, ["start", 2.0], 180, 9, [null, 6]], +[1, "hat1", 541, 3, [null, 18]], +[2, "stack1", 306, 673, [28, null]], +[3, "forward", 761, 49, [9, 11, 12]], +[4, "right", 761, 133, [12, 5, null]], +[5, ["number", 90], 836, 133, [4, null]], +[6, "storeinbox1", 180, 51, [0, 7, 51]], +[7, ["number", 20], 314, 51, [6, null]], +[8, "box1", 894, 49, [11, null]], +[9, "hat2", 761, 7, [null, 3]], +[10, "pop", 894, 91, [11, null]], +[11, ["product2", 0], 840, 49, [3, 8, 10]], +[12, ["vspace", 0], 761, 91, [3, 4]], +[13, "stack2", 541, 171, [19, 22]], +[14, "stack2", 541, 87, [18, 19]], +[15, "stack2", 541, 255, [22, 21]], +[16, "stack2", 541, 339, [21, 20]], +[17, "stack2", 541, 423, [20, null]], +[18, "push", 541, 45, [1, 23, 14]], +[19, "push", 541, 129, [14, 24, 13]], +[20, "push", 541, 381, [16, 25, 17]], +[21, "push", 541, 297, [15, 26, 16]], +[22, "push", 541, 213, [13, 27, 15]], +[23, ["number", 1], 617, 45, [18, null]], +[24, ["number", 1], 617, 129, [19, null]], +[25, ["number", 2.0], 617, 381, [20, null]], +[26, ["number", 3.0], 617, 297, [21, null]], +[27, ["number", 1], 617, 213, [22, null]], +[28, "repeat", 243, 613, [30, 29, 2, null]], +[29, ["number", 4], 292, 613, [28, null]], +[30, ["vspace", 0], 243, 571, [41, 28]], +[31, "repeat", 180, 177, [51, 32, 46, null]], +[32, ["number", 400], 229, 177, [31, null]], +[33, ["vspace", 0], 243, 445, [34, 50]], +[34, ["setxy2", 20], 243, 321, [45, 35, 36, 33]], +[35, ["random", 0], 308, 321, [34, 37, 39, null]], +[36, ["random", 0], 308, 403, [34, 38, 40, null]], +[37, "leftpos", 368, 321, [35, null]], +[38, "bottompos", 368, 403, [36, null]], +[39, "rightpos", 368, 363, [35, null]], +[40, "toppos", 368, 445, [36, null]], +[41, "setcolor", 243, 529, [50, 42, 30]], +[42, ["random", 0], 328, 529, [41, 43, 44, null]], +[43, ["number", 0], 388, 529, [42, null]], +[44, ["number", 100], 388, 571, [42, null]], +[45, "penup", 243, 279, [46, 34]], +[46, "seth", 243, 237, [31, 47, 45]], +[47, ["random", 0], 341, 237, [46, 48, 49, null]], +[48, ["number", 0], 401, 237, [47, null]], +[49, ["number", 90], 401, 279, [47, null]], +[50, "pendown", 243, 487, [33, 41]], +[51, ["fillscreen", 0], 180, 93, [6, 52, 53, 31]], +[52, "black", 320, 93, [51, null]], +[53, ["number", 0], 320, 135, [51, null]], +[-1, ["turtle", "Yertle"], 0, -38, 0, 0, 50, 5], [-1, ["turtle", "Walter Bender"], 241, 346, 177.0, 36, 50, 5]] \ No newline at end of file diff --git a/samples/timer.ta b/samples/timer.ta new file mode 100644 index 0000000..cb691ec --- /dev/null +++ b/samples/timer.ta @@ -0,0 +1,82 @@ +[[0, ["fillscreen", 0], 753, 146, [12, 2, 1, 4]], +[1, ["number", 80], 839, 188, [0, null]], +[2, "red", 839, 146, [0, null]], +[3, "white", 831, 230, [4, null]], +[4, "setcolor", 753, 230, [0, 3, 71]], +[5, "white", 843, 407, [7, null]], +[6, "blue", 835, 491, [9, null]], +[7, ["fillscreen", 0], 757, 407, [11, 5, 8, 9]], +[8, ["number", 80], 843, 449, [7, null]], +[9, "setcolor", 757, 491, [7, 6, 74]], +[10, ["start", 2.0], 194, 74, [null, 80]], +[11, "hat1", 757, 365, [null, 7]], +[12, "hat2", 753, 104, [null, 0]], +[13, "show", 258, 760, [68, 77, 79]], +[14, "setscale", 194, 158, [80, 15, 44]], +[15, ["number", 330], 273, 158, [14, null]], +[16, ["userdefined", "pysamples/push_time.py"], 892, 673, [41, 17, 18]], +[17, ["number", 100], 950, 673, [16, null]], +[18, ["storein", 0], 892, 715, [16, 24, 20, 21]], +[19, ["string", "h"], 961, 883, [23, null]], +[20, "pop", 961, 757, [18, null]], +[21, ["storein", 0], 892, 799, [18, 22, 34, 23]], +[22, ["string", "m"], 961, 799, [21, null]], +[23, ["storein", 0], 892, 883, [21, 19, 35, 25]], +[24, ["string", "s"], 961, 715, [18, null]], +[25, "storeinbox1", 892, 967, [23, 30, null]], +[26, "box", 1119, 967, [28, 27, null]], +[27, ["string", "h"], 1174, 967, [26, null]], +[28, ["product2", 0], 1065, 967, [30, 26, 29]], +[29, ["number", 3600], 1119, 1009, [28, null]], +[30, ["plus2", 20], 1011, 967, [25, 28, 32]], +[31, ["product2", 0], 1119, 1049, [32, 36, 33]], +[32, ["plus2", 20], 1065, 1049, [30, 31, 38]], +[33, ["number", 60], 1173, 1091, [31, null]], +[34, "pop", 961, 841, [21, null]], +[35, "pop", 961, 925, [23, null]], +[36, "box", 1173, 1049, [31, 37, null]], +[37, ["string", "m"], 1228, 1049, [36, null]], +[38, "box", 1119, 1131, [32, 39, null]], +[39, ["string", "s"], 1174, 1131, [38, null]], +[40, "box1", 313, 242, [43, null]], +[41, "hat", 892, 623, [null, 42, 16]], +[42, ["string", "time"], 950, 631, [41, null]], +[43, "storeinbox2", 194, 242, [44, 40, 46]], +[44, "stack", 194, 200, [14, 45, 43]], +[45, ["string", "time"], 252, 200, [44, null]], +[46, "stack1", 194, 284, [43, 47]], +[47, "forever", 194, 326, [46, 53, null]], +[48, ["vspace", 40], 258, 470, [58, 66]], +[49, "wait", 258, 844, [79, 50, null]], +[50, ["number", 10], 316, 844, [49, null]], +[51, "box2", 459, 470, [55, null]], +[52, "box1", 435, 428, [55, null]], +[53, "stack", 258, 344, [47, 54, 58]], +[54, ["string", "time"], 316, 344, [53, null]], +[55, ["minus2", 0], 381, 428, [56, 52, 51]], +[56, ["division2", 20], 327, 428, [58, 55, 57]], +[57, ["number", 60], 405, 510, [56, null]], +[58, ["storein", 0], 258, 386, [53, 59, 56, 48]], +[59, ["string", "elapsed"], 327, 386, [58, null]], +[60, ["greater2", 0], 320, 558, [66, 62, 61, null]], +[61, ["number", 20], 390, 600, [60, null]], +[62, "box", 366, 558, [60, 63, null]], +[63, ["string", "elapsed"], 421, 558, [62, null]], +[64, "stack2", 334, 702, [67, null]], +[65, "stack1", 386, 660, [66, null]], +[66, "ifelse", 258, 592, [48, 60, 67, 65, 68]], +[67, ["vspace", 0], 334, 660, [66, 64]], +[68, ["vspace", 20], 258, 678, [66, 13]], +[69, "box", 397, 802, [77, 70, null]], +[70, ["string", "elapsed"], 452, 802, [69, null]], +[71, ["setxy2", 0], 753, 272, [4, 72, 73, null]], +[72, ["number", -100], 811, 272, [71, null]], +[73, ["number", 200], 811, 314, [71, null]], +[74, ["setxy2", 0], 757, 533, [9, 75, 76, null]], +[75, ["number", -100], 815, 533, [74, null]], +[76, ["number", 200], 815, 575, [74, null]], +[77, ["myfunc1arg", 0], 316, 760, [13, 78, 69, null]], +[78, ["string", "int(x)"], 397, 760, [77, null]], +[79, ["vspace", 0], 258, 802, [13, 49]], +[80, "hideblocks", 194, 116, [10, 14]], +[-1, ["turtle", "Yertle"], -100.0, 200.0, 0, 70, 50, 5]] \ No newline at end of file diff --git a/samples/vumeter.ta b/samples/vumeter.ta new file mode 100644 index 0000000..22491c7 --- /dev/null +++ b/samples/vumeter.ta @@ -0,0 +1,68 @@ +[[0, ["start", 2.0], 34, 198, [null, 18]], +[1, "forever", 34, 450, [31, 10, 2]], +[2, ["vspace", 0], 34, 486, [1, null]], +[3, ["setxy2", 0], 160, 528, [10, 4, 8, null]], +[4, ["number", 0], 218, 528, [3, null]], +[5, "volume", 326, 570, [6, null]], +[6, ["division2", 0], 272, 570, [8, 5, 7]], +[7, ["number", 10], 350, 612, [6, null]], +[8, ["minus2", 20], 218, 570, [3, 6, 9]], +[9, ["number", 200], 296, 652, [8, null]], +[10, "repeat", 98, 468, [1, 11, 3, 12]], +[11, ["number", 20], 146, 468, [10, null]], +[12, ["vspace", 20], 98, 546, [10, 20]], +[13, ["setxy2", 0], 652, 319, [17, 14, 15, 29]], +[14, ["number", 0], 710, 319, [13, null]], +[15, ["number", -200], 710, 361, [13, null]], +[16, "penup", 652, 109, [19, 21]], +[17, "pendown", 652, 277, [24, 13]], +[18, "hideblocks", 34, 240, [0, 26]], +[19, "hat1", 652, 67, [null, 16]], +[20, "stack1", 98, 628, [12, null]], +[21, ["setxy2", 0], 652, 151, [16, 22, 23, 24]], +[22, ["number", 0], 710, 151, [21, null]], +[23, "toppos", 710, 193, [21, null]], +[24, "setcolor", 652, 235, [21, 28, 17]], +[25, "white", 120, 282, [26, null]], +[26, ["fillscreen", 0], 34, 282, [18, 25, 27, 37]], +[27, ["number", 80], 120, 324, [26, null]], +[28, "white", 730, 235, [24, null]], +[29, "setcolor", 652, 403, [13, 30, null]], +[30, ["number", 0], 730, 403, [29, null]], +[31, "setpensize", 34, 408, [37, 32, 1]], +[32, ["number", 30], 137, 408, [31, null]], +[33, "hat2", 883, 65, [null, 62]], +[34, "repeat", 883, 359, [42, 35, 43, 36]], +[35, ["number", 6], 931, 359, [34, null]], +[36, ["vspace", 80], 883, 437, [34, 53]], +[37, "stack2", 34, 366, [26, 31]], +[38, ["setxy2", 0], 883, 233, [41, 39, 40, 42]], +[39, ["number", -100], 941, 233, [38, null]], +[40, ["number", -200], 941, 275, [38, null]], +[41, "penup", 883, 191, [60, 38]], +[42, "pendown", 883, 317, [38, 34]], +[43, "right", 945, 419, [34, 44, 45]], +[44, ["number", 90], 1003, 419, [43, null]], +[45, "forward", 945, 461, [43, 46, 47]], +[46, ["number", 20], 1017, 461, [45, null]], +[47, "back", 945, 503, [45, 48, 49]], +[48, ["number", 20], 1003, 503, [47, null]], +[49, "left", 945, 545, [47, 50, 54]], +[50, ["number", 90], 1003, 545, [49, null]], +[51, "forward", 945, 711, [64, 52, null]], +[52, ["number", 100], 1017, 711, [51, null]], +[53, "penup", 883, 639, [36, null]], +[54, "show", 945, 587, [49, 66, 64]], +[55, "ycor", 1165, 629, [57, null]], +[56, ["number", 200], 1165, 671, [57, null]], +[57, ["plus2", 0], 1111, 629, [58, 55, 56]], +[58, ["product2", 20], 1057, 629, [66, 57, 59]], +[59, ["number", 10], 1111, 711, [58, null]], +[60, "setpensize", 883, 149, [62, 61, 41]], +[61, ["number", 5], 986, 149, [60, null]], +[62, "setscale", 883, 107, [33, 63, 60]], +[63, ["number", 25.0], 962, 107, [62, null]], +[64, ["vspace", 20], 945, 629, [54, 51]], +[65, ["string", " "], 1057, 587, [66, null]], +[66, ["plus2", 0], 1003, 587, [54, 65, 58]], +[-1, ["turtle", "Yertle"], 0.0, -179.3354, 0.0, 0.0, 50, 30.0]] \ No newline at end of file diff --git a/turtleart.py b/turtleart.py index b8ffa08..ca9153a 100755 --- a/turtleart.py +++ b/turtleart.py @@ -1,6 +1,7 @@ #!/usr/bin/env python #Copyright (c) 2007-8, Playful Invention Company -#Copyright (c) 2008-10, Walter Bender +#Copyright (c) 2008-11, Walter Bender +#Copyright (c) 2011 Collabora Ltd. #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal @@ -32,7 +33,7 @@ import cStringIO import errno try: - # Try to use XDG Base Directory standard for config files + # Try to use XDG Base Directory standard for config files. import xdg.BaseDirectory CONFIG_HOME = os.path.join(xdg.BaseDirectory.xdg_config_home, 'turtleart') except ImportError, e: @@ -42,33 +43,44 @@ except ImportError, e: argv = sys.argv[:] # Workaround for import behavior of gst in tagplay sys.argv[1:] = [] # Execution of import gst cannot see '--help' or '-h' -from gettext import gettext as _ +import gettext -from TurtleArt.taconstants import OVERLAY_LAYER +# Commenting out bindtextdomain so that GNOME can find the installed .mo files, +# but it will not find the .mo files in the local locale directory when running +# from a git repository or the unzipped .xo file. +# gettext.bindtextdomain('org.laptop.TurtleArtActivity', 'locale') + +gettext.textdomain('org.laptop.TurtleArtActivity') +_ = gettext.gettext + +from TurtleArt.taconstants import OVERLAY_LAYER, DEFAULT_TURTLE_COLORS from TurtleArt.tautils import data_to_string, data_from_string, get_save_name from TurtleArt.tawindow import TurtleArtWindow from TurtleArt.taexporthtml import save_html from TurtleArt.taexportlogo import save_logo -from extra.upload import Uploader -from extra.collaborationplugin import CollaborationPlugin + from util.menubuilder import MenuBuilder + class TurtleMain(): - """ Launch Turtle Art from outside of Sugar """ + ''' Launch Turtle Art from outside of Sugar. ''' - _HELP_MSG = 'turtleart.py: ' + _('usage is') + """ + _HELP_MSG = 'turtleart.py: ' + _('usage is') + ''' \tturtleart.py \tturtleart.py project.ta \tturtleart.py --output_png project.ta - \tturtleart.py -o project""" - _INSTALL_PATH = '/usr/share/turtleart' - _ALTERNATE_INSTALL_PATH = '/usr/local/share/turtleart' + \tturtleart.py -o project''' + _INSTALL_PATH = '/usr/share/sugar/activities/TurtleArt.activity' + _ALTERNATIVE_INSTALL_PATH = \ + '/usr/local/share/sugar/activities/TurtleArt.activity' _ICON_SUBPATH = 'images/turtle.png' + _GNOME_PLUGIN_SUBPATH = 'gnome_plugins' def __init__(self): self._init_vars() self._parse_command_line() self._ensure_sugar_paths() + self._plugins = [] if self.output_png: pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, @@ -85,18 +97,47 @@ class TurtleMain(): self._run_plugins() self._start_gtk() + def get_config_home(self): + return CONFIG_HOME + + def _get_gnome_plugin_home(self): + ''' Use plugin directory associated with execution path. ''' + if os.path.exists(os.path.join(self._dirname, + self._GNOME_PLUGIN_SUBPATH)): + return os.path.join(self._dirname, self._GNOME_PLUGIN_SUBPATH) + else: + return None + + def _get_plugin_candidates(self, path): + ''' Look for plugin files in plugin directory. ''' + plugin_files = [] + if path is not None: + candidates = os.listdir(path) + for c in candidates: + if c[-10:] == '_plugin.py' and c[0] != '#' and c[0] != '.': + plugin_files.append(c.split('.')[0]) + return plugin_files + def _init_plugins(self): - config_file_path = os.path.join(CONFIG_HOME, 'turtleartrc.collab') - self._collab_plugin = CollaborationPlugin(self, config_file_path) - self._uploader = Uploader() + ''' Try launching any plugins we may have found. ''' + for p in self._get_plugin_candidates(self._get_gnome_plugin_home()): + P = p.capitalize() + f = \ + "def f(self): from gnome_plugins.%s import %s; return %s(self)" % (p, P, P) + plugin = {} + try: + exec f in globals(), plugin + self._plugins.append(plugin.values()[0](self)) + except ImportError: + print 'failed to import %s' % (P) def _run_plugins(self): - self._uploader.set_tw(self.tw) - self._collab_plugin.set_tw(self.tw) - self._collab_plugin.setup() + ''' Tell the plugin about the TurtleWindow instance. ''' + for p in self._plugins: + p.set_tw(self.tw) - def _mkdir_p(path): - '''Create a directory in a fashion similar to `mkdir -p`''' + def _mkdir_p(self, path): + '''Create a directory in a fashion similar to `mkdir -p`.''' try: os.makedirs(path) except OSError as exc: @@ -106,7 +147,7 @@ class TurtleMain(): raise def _makepath(self, path): - """ Make a path if it doesn't previously exist """ + ''' Make a path if it doesn't previously exist ''' from os import makedirs from os.path import normpath, dirname, exists @@ -115,8 +156,9 @@ class TurtleMain(): makedirs(dpath) def _start_gtk(self): + ''' Get a main window set up. ''' self.win.connect('configure_event', self.tw.update_overlay_position) - self.tw.win = self.win + self.tw.parent = self.win if self.ta_file is None: self.tw.load_start() else: @@ -127,26 +169,24 @@ class TurtleMain(): gtk.main() def _draw_and_quit(self): + ''' Non-interactive mode: run the project, save it to a file + and quit. ''' self.tw.load_start(self.ta_file) self.tw.lc.trace = 0 self.tw.run_button(0) self.tw.save_as_image(self.ta_file, self.canvas) def _build_window(self): - if os.path.exists(self._INSTALL_PATH): - self.tw = TurtleArtWindow(self.canvas, self._INSTALL_PATH) - elif os.path.exists(self._ALTERNATE_INSTALL_PATH): - self.tw = TurtleArtWindow(self.canvas, self._ALTERNATE_INSTALL_PATH) - else: - self.tw = TurtleArtWindow(self.canvas, os.path.abspath('.')) - + ''' Initialize the TurtleWindow instance. ''' + self.tw = TurtleArtWindow(self.canvas, self._dirname) self.tw.save_folder = os.path.expanduser('~') def _init_vars(self): - # If we are invoked to start a project from Gnome, we should make - # sure our current directory is TA's source dir. - os.chdir(os.path.dirname(__file__)) - + ''' If we are invoked to start a project from Gnome, we should make + sure our current directory is TA's source dir. ''' + self._dirname = self._get_execution_dir() + if self._dirname is not None: + os.chdir(self._dirname) self.ta_file = None self.output_png = False self.i = 0 # FIXME: use a better name for this variable @@ -154,6 +194,7 @@ class TurtleMain(): self.tw = None def _parse_command_line(self): + ''' Try to make sense of the command-line arguments. ''' try: opts, args = getopt.getopt(argv[1:], 'ho', ['help', 'output_png']) @@ -182,16 +223,15 @@ class TurtleMain(): if not os.path.exists(self.ta_file): assert False, ('%s: %s' % (self.ta_file, _('File not found'))) - """ - make sure Sugar paths are present - """ def _ensure_sugar_paths(self): + ''' Make sure Sugar paths are present. ''' tapath = os.path.join(os.environ['HOME'], '.sugar', 'default', 'org.laptop.TurtleArtActivity') map(self._makepath, (os.path.join(tapath, 'data/'), os.path.join(tapath, 'instance/'))) def _read_initial_pos(self): + ''' Read saved configuration. ''' try: data_file = open(os.path.join(CONFIG_HOME, 'turtleartrc'), 'r') except IOError: @@ -212,25 +252,27 @@ class TurtleMain(): data_file.write(str(800) + '\n') data_file.write(str(550) + '\n') data_file.seek(0) - self.x = int(data_file.readline()) - self.y = int(data_file.readline()) - self.width = int(data_file.readline()) - self.height = int(data_file.readline()) + try: + self.x = int(data_file.readline()) + self.y = int(data_file.readline()) + self.width = int(data_file.readline()) + self.height = int(data_file.readline()) + except ValueError: + self.x = 50 + self.y = 50 + self.width = 800 + self.height = 550 def _setup_gtk(self): + ''' Set up a scrolled window in which to run Turtle Blocks. ''' win = gtk.Window(gtk.WINDOW_TOPLEVEL) win.set_default_size(self.width, self.height) win.move(self.x, self.y) win.maximize() win.set_title(_('Turtle Art')) - if os.path.exists(os.path.join(self._INSTALL_PATH, self._ICON_SUBPATH)): - win.set_icon_from_file(os.path.join(self._INSTALL_PATH, + if os.path.exists(os.path.join(self._dirname, self._ICON_SUBPATH)): + win.set_icon_from_file(os.path.join(self._dirname, self._ICON_SUBPATH)) - else: - try: - win.set_icon_from_file(self._ICON_SUBPATH) - except IOError: - pass win.connect('delete_event', self._quit_ta) vbox = gtk.VBox(False, 0) @@ -257,27 +299,30 @@ class TurtleMain(): self.canvas = canvas def _get_menu_bar(self): + ''' Instead of Sugar toolbars, use GNOME menus. ''' menu = gtk.Menu() MenuBuilder.make_menu_item(menu, _('New'), self._do_new_cb) MenuBuilder.make_menu_item(menu, _('Open'), self._do_open_cb) MenuBuilder.make_menu_item(menu, _('Save'), self._do_save_cb) - MenuBuilder.make_menu_item(menu, _('Save As'), self._do_save_as_cb) - MenuBuilder.make_menu_item(menu, _('Save as image'), self._do_save_picture_cb) - MenuBuilder.make_menu_item(menu, _('Save as HTML'), self._do_save_html_cb) - MenuBuilder.make_menu_item(menu, _('Save as Logo'), self._do_save_logo_cb) - if self._uploader.enabled(): - MenuBuilder.make_menu_item(menu, _('Upload to Web'), - self._uploader.do_upload_to_web) + MenuBuilder.make_menu_item(menu, _('Save as'), self._do_save_as_cb) + MenuBuilder.make_menu_item(menu, _('Save as image'), + self._do_save_picture_cb) + MenuBuilder.make_menu_item(menu, _('Save as HTML'), + self._do_save_html_cb) + MenuBuilder.make_menu_item(menu, _('Save as Logo'), + self._do_save_logo_cb) MenuBuilder.make_menu_item(menu, _('Quit'), self.destroy) activity_menu = MenuBuilder.make_sub_menu(menu, _('File')) menu = gtk.Menu() MenuBuilder.make_menu_item(menu, _('Cartesian coordinates'), self._do_cartesian_cb) - MenuBuilder.make_menu_item(menu, _('Polar coordinates'), self._do_polar_cb) + MenuBuilder.make_menu_item(menu, _('Polar coordinates'), + self._do_polar_cb) MenuBuilder.make_menu_item(menu, _('Rescale coordinates'), self._do_rescale_cb) - MenuBuilder.make_menu_item(menu, _('Grow blocks'), self._do_resize_cb, 1.5) + MenuBuilder.make_menu_item(menu, _('Grow blocks'), + self._do_resize_cb, 1.5) MenuBuilder.make_menu_item(menu, _('Shrink blocks'), self._do_resize_cb, 0.667) MenuBuilder.make_menu_item(menu, _('Reset block size'), @@ -290,9 +335,12 @@ class TurtleMain(): edit_menu = MenuBuilder.make_sub_menu(menu, _('Edit')) menu = gtk.Menu() - MenuBuilder.make_menu_item(menu, _('Show palette'), self._do_palette_cb) - MenuBuilder.make_menu_item(menu, _('Hide palette'), self._do_hide_palette_cb) - MenuBuilder.make_menu_item(menu, _('Show/hide blocks'), self._do_hideshow_cb) + MenuBuilder.make_menu_item(menu, _('Show palette'), + self._do_palette_cb) + MenuBuilder.make_menu_item(menu, _('Hide palette'), + self._do_hide_palette_cb) + MenuBuilder.make_menu_item(menu, _('Show/hide blocks'), + self._do_hideshow_cb) tool_menu = MenuBuilder.make_sub_menu(menu, _('Tools')) menu = gtk.Menu() @@ -303,19 +351,20 @@ class TurtleMain(): MenuBuilder.make_menu_item(menu, _('Stop'), self._do_stop_cb) turtle_menu = MenuBuilder.make_sub_menu(menu, _('Turtle')) - collaboration_menu = self._collab_plugin.get_menu() - menu_bar = gtk.MenuBar() menu_bar.append(activity_menu) menu_bar.append(edit_menu) menu_bar.append(view_menu) menu_bar.append(tool_menu) menu_bar.append(turtle_menu) - menu_bar.append(collaboration_menu) + + # Add menus for plugins + for p in self._plugins: + menu_bar.append(p.get_menu()) return menu_bar def _quit_ta(self, widget=None, e=None): - """ Save changes on exit """ + ''' Save changes on exit ''' project_empty = self.tw.is_project_empty() if not project_empty: if self.tw.is_new_project(): @@ -326,11 +375,11 @@ class TurtleMain(): gtk.main_quit() def _show_save_dialog(self, new_project=True): - """ Dialog for save project """ + ''' Dialog for save project ''' dlg = gtk.MessageDialog(parent=None, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK_CANCEL, - message_format= \ - _('You have unsaved work. Would you like to save before quitting?')) + message_format=_( + 'You have unsaved work. Would you like to save before quitting?')) dlg.set_title(_('Save project?')) dlg.set_property('skip-taskbar-hint', False) @@ -343,38 +392,38 @@ class TurtleMain(): self._save_changes() def _do_new_cb(self, widget): - """ Callback for new project. """ + ''' Callback for new project. ''' self.tw.new_project() self.tw.load_start() def _do_open_cb(self, widget): - """ Callback for open project. """ + ''' Callback for open project. ''' self.tw.load_file(True) def _do_save_cb(self, widget): - """ Callback for save project. """ + ''' Callback for save project. ''' self.tw.save_file() def _do_save_as_cb(self, widget): - """ Callback for save-as project. """ + ''' Callback for save-as project. ''' self._save_as() def _save_as(self): - """ Save as is called from callback and quit """ + ''' Save as is called from callback and quit ''' self.tw.save_file_name = None self.tw.save_file() def _save_changes(self): - """ Save changes to current project """ + ''' Save changes to current project ''' self.tw.save_file_name = None self.tw.save_file(self.tw._loaded_project) def _do_save_picture_cb(self, widget): - """ Callback for save canvas. """ + ''' Callback for save canvas. ''' self.tw.save_as_image() def _do_save_html_cb(self, widget): - """ Callback for save project to HTML. """ + ''' Callback for save project to HTML. ''' html = save_html(self, self.tw, False) if len(html) == 0: return @@ -390,7 +439,7 @@ class TurtleMain(): self.tw.saved_pictures = [] def _do_save_logo_cb(self, widget): - """ Callback for save project to Logo. """ + ''' Callback for save project to Logo. ''' logocode = save_logo(self.tw) if len(logocode) == 0: return @@ -402,7 +451,7 @@ class TurtleMain(): f.close() def _do_resize_cb(self, widget, factor): - """ Callback to resize blocks. """ + ''' Callback to resize blocks. ''' if factor == -1: self.tw.block_scale = 2.0 else: @@ -410,7 +459,7 @@ class TurtleMain(): self.tw.resize_blocks() def _do_cartesian_cb(self, button): - """ Callback to display/hide Cartesian coordinate overlay. """ + ''' Callback to display/hide Cartesian coordinate overlay. ''' if self.tw.cartesian is True: if self.tw.coord_scale == 1: self.tw.overlay_shapes['Cartesian_labeled'].hide() @@ -426,7 +475,7 @@ class TurtleMain(): self.tw.cartesian = True def _do_polar_cb(self, button): - """ Callback to display/hide Polar coordinate overlay. """ + ''' Callback to display/hide Polar coordinate overlay. ''' if self.tw.polar is True: self.tw.overlay_shapes['polar'].hide() self.tw.polar = False @@ -435,7 +484,7 @@ class TurtleMain(): self.tw.polar = True def _do_rescale_cb(self, button): - """ Callback to rescale coordinate space. """ + ''' Callback to rescale coordinate space. ''' if self.tw.coord_scale == 1: self.tw.coord_scale = self.tw.height / 200 self.tw.eraser_button() @@ -451,51 +500,51 @@ class TurtleMain(): OVERLAY_LAYER) def _do_palette_cb(self, widget): - """ Callback to show/hide palette of blocks. """ + ''' Callback to show/hide palette of blocks. ''' self.tw.show_palette(self.i) self.i += 1 if self.i == len(self.tw.palettes): self.i = 0 def _do_hide_palette_cb(self, widget): - """ Hide the palette of blocks. """ + ''' Hide the palette of blocks. ''' self.tw.hide_palette() def _do_hideshow_cb(self, widget): - """ Hide/show the blocks. """ + ''' Hide/show the blocks. ''' self.tw.hideshow_button() def _do_eraser_cb(self, widget): - """ Callback for eraser button. """ + ''' Callback for eraser button. ''' self.tw.eraser_button() return def _do_run_cb(self, widget): - """ Callback for run button (rabbit). """ + ''' Callback for run button (rabbit). ''' self.tw.lc.trace = 0 self.tw.run_button(0) return def _do_step_cb(self, widget): - """ Callback for step button (turtle). """ + ''' Callback for step button (turtle). ''' self.tw.lc.trace = 0 self.tw.run_button(3) return def _do_trace_cb(self, widget): - """ Callback for debug button (bug). """ + ''' Callback for debug button (bug). ''' self.tw.lc.trace = 1 self.tw.run_button(6) return def _do_stop_cb(self, widget): - """ Callback for stop button. """ + ''' Callback for stop button. ''' self.tw.lc.trace = 0 self.tw.stop_button() return def _do_copy_cb(self, button): - """ Callback for copy button. """ + ''' Callback for copy button. ''' clipBoard = gtk.Clipboard() data = self.tw.assemble_data_to_save(False, False) if data is not []: @@ -503,7 +552,7 @@ class TurtleMain(): clipBoard.set_text(text) def _do_paste_cb(self, button): - """ Callback for paste button. """ + ''' Callback for paste button. ''' clipBoard = gtk.Clipboard() text = clipBoard.wait_for_text() if text is not None: @@ -518,7 +567,7 @@ class TurtleMain(): self.tw.paste_offset += 20 def _window_event(self, event, data): - """ Callback for resize event. """ + ''' Callback for resize event. ''' data_file = open('.turtleartrc', 'w') data_file.write(str(data.x) + '\n') data_file.write(str(data.y) + '\n') @@ -526,8 +575,40 @@ class TurtleMain(): data_file.write(str(data.height) + '\n') def destroy(self, event, data=None): - """ Callback for destroy event. """ + ''' Callback for destroy event. ''' gtk.main_quit() -if __name__ == "__main__": + def nick_changed(self, nick): + ''' TODO: Rename default turtle in dictionary ''' + pass + + def color_changed(self, colors): + ''' Reskin turtle with collaboration colors ''' + turtle = self.tw.turtles.get_turtle(self.tw.default_turtle_name) + try: + turtle.colors = colors.split(',') + except: + turtle.colors = DEFAULT_TURTLE_COLORS + turtle.custom_shapes = True # Force regeneration of shapes + turtle.reset_shapes() + turtle.show() + + def _get_execution_dir(self): + ''' From whence is the program being executed? ''' + dirname = os.path.dirname(__file__) + if dirname == '': + if os.path.exists(os.path.join('~', 'Activities', + 'TurtleArt.activity')): + return os.path.join('~', 'Activities', 'TurtleArt.activity') + elif os.path.exists(self._INSTALL_PATH): + return self._INSTALL_PATH + elif os.path.exists(self._ALTERNATIVE_INSTALL_PATH): + return self._ALTERNATIVE_INSTALL_PATH + else: + return os.path.abspath('.') + else: + return os.path.abspath(dirname) + + +if __name__ == '__main__': TurtleMain() diff --git a/TurtleArt/RtfParser.py b/util/RtfParser.py index 9a141a4..9a141a4 100644 --- a/TurtleArt/RtfParser.py +++ b/util/RtfParser.py diff --git a/util/configfile.py b/util/configfile.py index 5df8f59..adabb34 100644 --- a/util/configfile.py +++ b/util/configfile.py @@ -1,10 +1,24 @@ #!/usr/bin/env python # # Copyright (c) 2011 Collabora Ltd. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. # +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. import gobject + class ConfigFile(gobject.GObject): """Load/save a simple (key = value) config file""" @@ -15,7 +29,7 @@ class ConfigFile(gobject.GObject): ()), } - def __init__(self, config_file_path, valid_keys = {}): + def __init__(self, config_file_path, valid_keys={}): gobject.GObject.__init__(self) self._config_file_path = config_file_path @@ -29,24 +43,24 @@ class ConfigFile(gobject.GObject): def is_loaded(self): return self._is_loaded - def get(self, key, empty_if_not_loaded = False): - if not self._valid_keys.has_key(key): + def get(self, key, empty_if_not_loaded=False): + if not key in self._valid_keys: raise RuntimeError("Unknown config value %s" % key) - if self._config_hash.has_key(key): + if key in self._config_hash: value = self._config_hash[key] else: if self._valid_keys[key]["type"] == "text": value = "" - elif self._valid_keys[key]["type"] == "boolean": + elif self._valid_keys[key]["type"] == "boolean": value = False - elif self._valid_keys[key]["type"] == "integer": - value = 0 + elif self._valid_keys[key]["type"] == "integer": + value = 0 return value def set(self, key, value): - if not self._valid_keys.has_key(key): + if not key in self._valid_keys: raise RuntimeError("Unknown config value %s" % key) self._config_hash[key] = value @@ -58,10 +72,10 @@ class ConfigFile(gobject.GObject): config_file.close() for line in lines: line = line.strip() - k,v = line.split('=') + k, v = line.split('=') k = k.strip(' ') v = v.strip(' ') - if not self._valid_keys.has_key(k): + if not k in self._valid_keys: raise RuntimeError("Unknown config value %s" % k) value_type = self._valid_keys[k]["type"] if value_type == "text": @@ -73,11 +87,11 @@ class ConfigFile(gobject.GObject): self._config_hash[k] = value self._is_loaded = True self.emit('configuration-loaded') - except Exception,e: + except Exception, e: print e return self._is_loaded - + def save(self): config_file = open(self._config_file_path, 'w') for k in self._config_hash.keys(): @@ -94,14 +108,15 @@ class ConfigFile(gobject.GObject): l = "%s = %s\n" % (k, v) print l + def test_save_load(test_config_file): keys = {} - keys["nick"] = { "type" : "text" } - keys["account_id"] = { "type" : "text" } - keys["server"] = { "type" : "text" } - keys["port"] = { "type" : "text" } - keys["password"] = { "type" : "text" } - keys["register"] = { "type" : "text" } + keys["nick"] = {"type": "text"} + keys["account_id"] = {"type": "text"} + keys["server"] = {"type": "text"} + keys["port"] = {"type": "text"} + keys["password"] = {"type": "text"} + keys["register"] = {"type": "text"} c = ConfigFile(test_config_file) c.set_valid_keys(keys) @@ -113,28 +128,31 @@ def test_save_load(test_config_file): c.set("register", True) c.save() - + c = ConfigFile(test_config_file) c.set_valid_keys(keys) c.load() c.dump_keys() + def _configuration_saved_cb(config_file_obj): print "_configuration_saved_cb called" config_file_obj.dump_keys() + def _configuration_loaded_cb(config_file_obj): print "_configuration_loaded_cb called" config_file_obj.dump_keys() + def test_signals(test_config_file): keys = {} - keys["nick"] = { "type" : "text" } - keys["account_id"] = { "type" : "text" } - keys["server"] = { "type" : "text" } - keys["port"] = { "type" : "text" } - keys["password"] = { "type" : "text" } - keys["register"] = { "type" : "text" } + keys["nick"] = {"type": "text"} + keys["account_id"] = {"type": "text"} + keys["server"] = {"type": "text"} + keys["port"] = {"type": "text"} + keys["password"] = {"type": "text"} + keys["register"] = {"type": "text"} c = ConfigFile(test_config_file) c.connect('configuration-saved', _configuration_saved_cb) @@ -147,12 +165,13 @@ def test_signals(test_config_file): c.set("register", True) c.save() - + c = ConfigFile(test_config_file) c.connect('configuration-loaded', _configuration_loaded_cb) c.set_valid_keys(keys) c.load() + if __name__ == "__main__": test_save_load("/tmp/configfile.0001") test_signals("/tmp/configfile.0002") diff --git a/util/configwizard.py b/util/configwizard.py index 7716362..6c66bd1 100644 --- a/util/configwizard.py +++ b/util/configwizard.py @@ -1,25 +1,41 @@ #!/usr/bin/python +# Copyright (c) 2011 Collabora Ltd. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. from configfile import ConfigFile -import gtk +import gtk + class ConfigWizard(): """Simple configuration wizard window.""" - + def __init__(self, config_file_path): self._config_items = [] self._config_entries = {} self._config_file_path = config_file_path - self._config_file_obj = None + self._config_file_obj = None """ - [ { item_label, item_type, item_name, item_with_value } , ... ] + [ {item_label, item_type, item_name, item_with_value} , ... ] """ def set_config_items(self, items): self._config_items = items keys = {} for i in self._config_items: - keys[i["item_name"]] = { "type" : i["item_type"] } + keys[i["item_name"]] = {"type": i["item_type"]} self._valid_keys = keys def set_config_file_obj(self, obj): @@ -28,7 +44,7 @@ class ConfigWizard(): def get_config_file_obj(self, obj): return self._config_file_obj - def show(self, read_from_disc = False): + def show(self, read_from_disc=False): if read_from_disc: self._config_file_obj = ConfigFile(self._config_file_path) @@ -47,8 +63,8 @@ class ConfigWizard(): row = 1 for i in self._config_items: hbox = self._create_param(i) - table.attach(hbox, 0, 1, row, row+1, xpadding=5, ypadding=2) - row = row +1 + table.attach(hbox, 0, 1, row, row + 1, xpadding=5, ypadding=2) + row = row + 1 hbox = gtk.HBox() save_button = gtk.Button('Save') @@ -59,7 +75,7 @@ class ConfigWizard(): cancel_button.set_size_request(50, 15) cancel_button.connect('pressed', self._close_config_cb) hbox.add(cancel_button) - table.attach(hbox, 0, 1, row, row+1, xpadding=5, ypadding=2) + table.attach(hbox, 0, 1, row, row + 1, xpadding=5, ypadding=2) self._config_popup.show_all() @@ -89,11 +105,12 @@ class ConfigWizard(): self._config_file_obj.save() """ - { item_label, item_type, item_name, item_with_value } + {item_label, item_type, item_name, item_with_value} """ def _create_param(self, opts): param_name = opts["item_name"] - with_value = opts["item_with_value"] if opts.has_key("item_with_value") else True + with_value = opts["item_with_value"] if "item_with_value" in opts \ + else True hbox = gtk.HBox() if opts["item_type"] == "text": entry = gtk.Entry() @@ -117,14 +134,15 @@ class ConfigWizard(): def _close_config_cb(self, widget, event=None): self._config_popup.hide() + def test_wizard_from_config_file_obj(test_config_file): keys = {} - keys["nick"] = { "type" : "text" } - keys["account_id"] = { "type" : "text" } - keys["server"] = { "type" : "text" } - keys["port"] = { "type" : "text" } - keys["password"] = { "type" : "text" } - keys["register"] = { "type" : "text" } + keys["nick"] = {"type": "text"} + keys["account_id"] = {"type": "text"} + keys["server"] = {"type": "text"} + keys["port"] = {"type": "text"} + keys["password"] = {"type": "text"} + keys["register"] = {"type": "text"} c = ConfigFile(test_config_file) c.set_valid_keys(keys) @@ -136,32 +154,36 @@ def test_wizard_from_config_file_obj(test_config_file): c.set("register", True) c.save() - + c = ConfigFile(test_config_file) c.set_valid_keys(keys) c.load() config_w = ConfigWizard(test_config_file) config_items = [ - {"item_label" : "Nickname", "item_type" : "text", "item_name" : "nick" }, - { "item_label" : "Account ID", "item_type" : "text", "item_name" : "account_id" }, - { "item_label" : "Server", "item_type" : "text", "item_name" : "server" }, - { "item_label" : "Port", "item_type" : "text", "item_name" : "port" }, - { "item_label" : "Password", "item_type" : "text", "item_name" : "password" }, - { "item_label" : "Register", "item_type" : "text", "item_name" : "register" } + {"item_label": "Nickname", "item_type": "text", "item_name": "nick"}, + {"item_label": "Account ID", "item_type": "text", + "item_name": "account_id"}, + {"item_label": "Server", "item_type": "text", "item_name": "server"}, + {"item_label": "Port", "item_type": "text", "item_name": "port"}, + {"item_label": "Password", "item_type": "text", + "item_name": "password"}, + {"item_label": "Register", "item_type": "text", + "item_name": "register"} ] config_w.set_config_items(config_items) config_w.set_config_file_obj(c) config_w.show() + def test_wizard_from_config_file_path(test_config_file): keys = {} - keys["nick"] = { "type" : "text" } - keys["account_id"] = { "type" : "text" } - keys["server"] = { "type" : "text" } - keys["port"] = { "type" : "text" } - keys["password"] = { "type" : "text" } - keys["register"] = { "type" : "text" } + keys["nick"] = {"type": "text"} + keys["account_id"] = {"type": "text"} + keys["server"] = {"type": "text"} + keys["port"] = {"type": "text"} + keys["password"] = {"type": "text"} + keys["register"] = {"type": "text"} c = ConfigFile(test_config_file) c.set_valid_keys(keys) @@ -173,19 +195,23 @@ def test_wizard_from_config_file_path(test_config_file): c.set("register", True) c.save() - + config_w = ConfigWizard(test_config_file) config_items = [ - {"item_label" : "Nickname", "item_type" : "text", "item_name" : "nick" }, - { "item_label" : "Account ID", "item_type" : "text", "item_name" : "account_id" }, - { "item_label" : "Server", "item_type" : "text", "item_name" : "server" }, - { "item_label" : "Port", "item_type" : "text", "item_name" : "port" }, - { "item_label" : "Password", "item_type" : "text", "item_name" : "password" }, - { "item_label" : "Register", "item_type" : "text", "item_name" : "register" } + {"item_label": "Nickname", "item_type": "text", "item_name": "nick"}, + {"item_label": "Account ID", "item_type": "text", + "item_name": "account_id"}, + {"item_label": "Server", "item_type": "text", "item_name": "server"}, + {"item_label": "Port", "item_type": "text", "item_name": "port"}, + {"item_label": "Password", "item_type": "text", + "item_name": "password"}, + {"item_label": "Register", "item_type": "text", + "item_name": "register"} ] config_w.set_config_items(config_items) config_w.show(True) + if __name__ == "__main__": #test_wizard_from_config_file_obj("/tmp/configwizard.test.0001") test_wizard_from_config_file_path("/tmp/configwizard.test.0002") diff --git a/util/menubuilder.py b/util/menubuilder.py index 302d510..4ee4550 100644 --- a/util/menubuilder.py +++ b/util/menubuilder.py @@ -1,7 +1,23 @@ #!/usr/bin/python +# Copyright (c) 2011 Collabora Ltd. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. import gtk + class MenuBuilder(): @classmethod def make_sub_menu(cls, menu, name): -- cgit v0.9.1