Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/TurtleArt
diff options
context:
space:
mode:
authorWalter Bender <walter@sugarlabs.org>2010-08-12 17:57:55 (GMT)
committer Walter Bender <walter@sugarlabs.org>2010-08-12 17:57:55 (GMT)
commitc2937d9fdd30b1b896caf66851d6677924d07488 (patch)
treec242386fa59755d4adea7fb414b72466181a2a2e /TurtleArt
parent98caa09b7eadbd14776b73f01095bc59cb39cc18 (diff)
reorg of module heirarchy
Diffstat (limited to 'TurtleArt')
-rw-r--r--TurtleArt/__init__.py0
-rw-r--r--TurtleArt/sprites.py420
-rw-r--r--TurtleArt/tablock.py865
-rw-r--r--TurtleArt/tacanvas.py596
-rw-r--r--TurtleArt/taconstants.py1065
-rw-r--r--TurtleArt/taexporthtml.py147
-rw-r--r--TurtleArt/taexportlogo.py353
-rw-r--r--TurtleArt/tagplay.py176
-rw-r--r--TurtleArt/tajail.py69
-rw-r--r--TurtleArt/talogo.py1366
-rw-r--r--TurtleArt/tamyblock.py245
-rwxr-xr-xTurtleArt/tasprite_factory.py1118
-rw-r--r--TurtleArt/taturtle.py209
-rw-r--r--TurtleArt/tautils.py705
-rw-r--r--TurtleArt/tawindow.py2336
15 files changed, 9670 insertions, 0 deletions
diff --git a/TurtleArt/__init__.py b/TurtleArt/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/TurtleArt/__init__.py
diff --git a/TurtleArt/sprites.py b/TurtleArt/sprites.py
new file mode 100644
index 0000000..ae5447d
--- /dev/null
+++ b/TurtleArt/sprites.py
@@ -0,0 +1,420 @@
+# -*- coding: utf-8 -*-
+
+#Copyright (c) 2007-8, Playful Invention Company.
+#Copyright (c) 2008-10 Walter Bender
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+"""
+
+sprites.py is a simple sprites library for managing graphics objects,
+'sprites', on a canvas. It manages multiple sprites with methods such
+as move, hide, set_layer, etc.
+
+There are two classes:
+
+class Sprites maintains a collection of sprites.
+class Sprite manages individual sprites within the collection.
+
+Example usage:
+ # Import the classes into your program.
+ from sprites import Sprites Sprite
+
+ # In your expose callback event handler, call refresh
+ def _expose_cb(self, win, event):
+ self.sprite_list.refresh(event)
+ return True
+
+ # Create a new sprite collection for a gtk Drawing Area.
+ my_drawing_area = gtk.DrawingArea()
+ self.sprite_list = Sprites(my_drawing_area)
+
+ # Create a "pixbuf" (in this example, from SVG).
+ my_pixbuf = svg_str_to_pixbuf("<svg>...some svg code...</svg>")
+
+ # Create a sprite at position x1, y1.
+ my_sprite = sprites.Sprite(self.sprite_list, x1, y1, my_pixbuf)
+
+ # Move the sprite to a new position.
+ my_sprite.move((x1+dx, y1+dy))
+
+ # Create another "pixbuf".
+ your_pixbuf = svg_str_to_pixbuf("<svg>...some svg code...</svg>")
+
+ # Create a sprite at position x2, y2.
+ your_sprite = sprites.Sprite(self.sprite_list, x2, y2, my_pixbuf)
+
+ # Assign the sprites to layers.
+ # In this example, your_sprite will be on top of my_sprite.
+ my_sprite.set_layer(100)
+ your_sprite.set_layer(200)
+
+ # Now put my_sprite on top of your_sprite.
+ my_sprite.set_layer(300)
+
+# method for converting SVG to a gtk pixbuf
+def svg_str_to_pixbuf(svg_string):
+ pl = gtk.gdk.PixbufLoader('svg')
+ pl.write(svg_string)
+ pl.close()
+ pixbuf = pl.get_pixbuf()
+ return pixbuf
+"""
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import pango
+
+
+class Sprites:
+ """ A class for the list of sprites and everything they share in common """
+
+ def __init__(self, canvas, area=None, gc=None):
+ """ Initialize an empty array of sprites """
+ self.canvas = canvas
+ if area == None:
+ self.area = self.canvas.window
+ self.gc = self.area.new_gc()
+ else:
+ self.area = area
+ self.gc = gc
+ self.cm = self.gc.get_colormap()
+ self.list = []
+
+ def get_sprite(self, i):
+ """ Return a sprint from the array """
+ if i < 0 or i > len(self.list)-1:
+ return(None)
+ else:
+ return(self.list[i])
+
+ def length_of_list(self):
+ """ How many sprites are there? """
+ return(len(self.list))
+
+ def append_to_list(self, spr):
+ """ Append a new sprite to the end of the list. """
+ self.list.append(spr)
+
+ def insert_in_list(self, spr, i):
+ """ Insert a sprite at position i. """
+ if i < 0:
+ self.list.insert(0, spr)
+ elif i > len(self.list) - 1:
+ self.list.append(spr)
+ else:
+ self.list.insert(i, spr)
+
+ def remove_from_list(self, spr):
+ """ Remove a sprite from the list. """
+ if spr in self.list:
+ self.list.remove(spr)
+
+ def find_sprite(self, pos):
+ """ Search based on (x, y) position. Return the 'top/first' one. """
+ list = self.list[:]
+ list.reverse()
+ for spr in list:
+ if spr.hit(pos):
+ return spr
+ return None
+
+ def refresh(self, event):
+ """ Handle expose event refresh """
+ self.redraw_sprites(event.area)
+
+ def redraw_sprites(self, area=None):
+ """ Redraw the sprites that intersect area. """
+ for spr in self.list:
+ if area == None:
+ spr.draw()
+ else:
+ intersection = spr.rect.intersect(area)
+ if intersection.width > 0 or intersection.height > 0:
+ spr.draw()
+
+
+class Sprite:
+ """ A class for the individual sprites """
+
+ def __init__(self, sprites, x, y, image):
+ """ Initialize an individual sprite """
+ self._sprites = sprites
+ self.rect = gtk.gdk.Rectangle(int(x), int(y), 0, 0)
+ self._scale = [12]
+ self._rescale = [True]
+ self._horiz_align = ["center"]
+ self._vert_align = ["middle"]
+ self._fd = None
+ self._bold = False
+ self._italic = False
+ self._color = None
+ self._margins = [0, 0, 0, 0]
+ self.layer = 100
+ self.labels = []
+ self.images = []
+ self._dx = [] # image offsets
+ self._dy = []
+ self.set_image(image)
+ if self._sprites is not None:
+ self._sprites.append_to_list(self)
+
+ def set_image(self, image, i=0, dx=0, dy=0):
+ """ Add an image to the sprite. """
+ while len(self.images) < i + 1:
+ self.images.append(None)
+ self._dx.append(0)
+ self._dy.append(0)
+ self.images[i] = image
+ self._dx[i] = dx
+ self._dy[i] = dy
+ if isinstance(self.images[i], gtk.gdk.Pixbuf):
+ w = self.images[i].get_width()
+ h = self.images[i].get_height()
+ else:
+ w, h = self.images[i].get_size()
+ if i == 0: # Always reset width and height when base image changes.
+ self.rect.width = w + dx
+ self.rect.height = h + dy
+ else:
+ if w + dx > self.rect.width:
+ self.rect.width = w + dx
+ if h + dy > self.rect.height:
+ self.rect.height = h + dy
+
+ def move(self, pos):
+ """ Move to new (x, y) position """
+ self.inval()
+ self.rect.x, self.rect.y = int(pos[0]), int(pos[1])
+ self.inval()
+
+ def move_relative(self, pos):
+ """ Move to new (x+dx, y+dy) position """
+ self.inval()
+ self.rect.x += int(pos[0])
+ self.rect.y += int(pos[1])
+ self.inval()
+
+ def get_xy(self):
+ """ Return current (x, y) position """
+ return (self.rect.x, self.rect.y)
+
+ def get_dimensions(self):
+ """ Return current size """
+ return (self.rect.width, self.rect.height)
+
+ def get_layer(self):
+ """ Return current layer """
+ return self.layer
+
+ def set_shape(self, image, i=0):
+ """ Set the current image associated with the sprite """
+ self.inval()
+ self.set_image(image, i)
+ self.inval()
+
+ def set_layer(self, layer):
+ """ Set the layer for a sprite """
+ if self._sprites is None:
+ return
+ self._sprites.remove_from_list(self)
+ self.layer = layer
+ for i in range(self._sprites.length_of_list()):
+ if layer < self._sprites.get_sprite(i).layer:
+ self._sprites.insert_in_list(self, i)
+ self.inval()
+ return
+ self._sprites.append_to_list(self)
+ self.inval()
+
+ def set_label(self, new_label, i=0):
+ """ Set the label drawn on the sprite """
+ self._extend_labels_array(i)
+ if type(new_label) is str or type(new_label) is unicode:
+ # pango doesn't like nulls
+ self.labels[i] = new_label.replace("\0", " ")
+ else:
+ self.labels[i] = str(new_label)
+ self.inval()
+
+ def set_margins(self, l=0, t=0, r=0, b=0):
+ """ Set the margins for drawing the label """
+ self._margins = [l, t, r, b]
+
+ def _extend_labels_array(self, i):
+ """ Append to the labels attribute list """
+ if self._fd is None:
+ self.set_font('Sans')
+ if self._color is None:
+ self._color = self._sprites.cm.alloc_color('black')
+ while len(self.labels) < i + 1:
+ self.labels.append(" ")
+ self._scale.append(self._scale[0])
+ self._rescale.append(self._rescale[0])
+ self._horiz_align.append(self._horiz_align[0])
+ self._vert_align.append(self._vert_align[0])
+
+ def set_font(self, font):
+ """ Set the font for a label """
+ self._fd = pango.FontDescription(font)
+
+ def set_label_color(self, rgb):
+ """ Set the font color for a label """
+ self._color = self._sprites.cm.alloc_color(rgb)
+
+ def set_label_attributes(self, scale, rescale=True, horiz_align="center",
+ vert_align="middle", i=0):
+ """ Set the various label attributes """
+ self._extend_labels_array(i)
+ self._scale[i] = scale
+ self._rescale[i] = rescale
+ self._horiz_align[i] = horiz_align
+ self._vert_align[i] = vert_align
+
+ def hide(self):
+ """ Hide a sprite """
+ if self._sprites is None:
+ return
+ self.inval()
+ self._sprites.remove_from_list(self)
+
+ def inval(self):
+ """ Force a region redraw by gtk """
+ if self._sprites is None:
+ return
+ self._sprites.area.invalidate_rect(self.rect, False)
+
+ def draw(self):
+ """ Draw the sprite (and label) """
+ if self._sprites is None:
+ return
+ for i, img in enumerate(self.images):
+ if isinstance(img, gtk.gdk.Pixbuf):
+ self._sprites.area.draw_pixbuf(self._sprites.gc, img, 0, 0,
+ self.rect.x + self._dx[i],
+ self.rect.y + self._dy[i])
+ elif img is not None:
+ self._sprites.area.draw_drawable(self._sprites.gc, img, 0, 0,
+ self.rect.x + self._dx[i],
+ self.rect.y + self._dy[i],
+ -1, -1)
+ if len(self.labels) > 0:
+ self.draw_label()
+
+ def hit(self, pos):
+ """ Is (x, y) on top of the sprite? """
+ x, y = pos
+ if x < self.rect.x:
+ return False
+ if x > self.rect.x + self.rect.width:
+ return False
+ if y < self.rect.y:
+ return False
+ if y > self.rect.y + self.rect.height:
+ return False
+ return True
+
+ def draw_label(self):
+ """ Draw the label based on its attributes """
+ if self._sprites is None:
+ return
+ my_width = self.rect.width - self._margins[0] - self._margins[2]
+ if my_width < 0:
+ my_width = 0
+ my_height = self.rect.height - self._margins[1] - self._margins[3]
+ for i in range(len(self.labels)):
+ pl = self._sprites.canvas.create_pango_layout(str(self.labels[i]))
+ self._fd.set_size(int(self._scale[i] * pango.SCALE))
+ pl.set_font_description(self._fd)
+ w = pl.get_size()[0] / pango.SCALE
+ if w > my_width:
+ if self._rescale[i]:
+ self._fd.set_size(
+ int(self._scale[i] * pango.SCALE * my_width / w))
+ pl.set_font_description(self._fd)
+ w = pl.get_size()[0] / pango.SCALE
+ else:
+ j = len(self.labels[i]) - 1
+ while(w > my_width and j > 0):
+ pl = self._sprites.canvas.create_pango_layout(
+ "…" + self.labels[i][len(self.labels[i]) - j:])
+ self._fd.set_size(int(self._scale[i] * pango.SCALE))
+ pl.set_font_description(self._fd)
+ w = pl.get_size()[0] / pango.SCALE
+ j -= 1
+ if self._horiz_align[i] == "center":
+ x = int(self.rect.x + self._margins[0] + (my_width - w) / 2)
+ elif self._horiz_align[i] == 'left':
+ x = int(self.rect.x + self._margins[0])
+ else: # right
+ x = int(self.rect.x + self.rect.width - w - self._margins[2])
+ h = pl.get_size()[1] / pango.SCALE
+ if self._vert_align[i] == "middle":
+ y = int(self.rect.y + self._margins[1] + (my_height - h) / 2)
+ elif self._vert_align[i] == "top":
+ y = int(self.rect.y + self._margins[1])
+ else: # bottom
+ y = int(self.rect.y + self.rect.height - h - self._margins[3])
+ self._sprites.gc.set_foreground(self._color)
+ self._sprites.area.draw_layout(self._sprites.gc, x, y, pl)
+
+ def label_width(self):
+ """ Calculate the width of a label """
+ max = 0
+ for i in range(len(self.labels)):
+ pl = self._sprites.canvas.create_pango_layout(self.labels[i])
+ self._fd.set_size(int(self._scale[i] * pango.SCALE))
+ pl.set_font_description(self._fd)
+ w = pl.get_size()[0] / pango.SCALE
+ if w > max:
+ max = w
+ return max
+
+ def label_safe_width(self):
+ """ Return maximum width for a label """
+ return self.rect.width - self._margins[0] - self._margins[2]
+
+ def label_safe_height(self):
+ """ Return maximum height for a label """
+ return self.rect.height - self._margins[1] - self._margins[3]
+
+ def label_left_top(self):
+ """ Return the upper-left corner of the label safe zone """
+ return(self._margins[0], self._margins[1])
+
+ def get_pixel(self, pos, i=0):
+ """ Return the pixl at (x, y) """
+ x, y = pos
+ x = x - self.rect.x
+ y = y - self.rect.y
+ if y > self.images[i].get_height() - 1:
+ return(-1, -1, -1, -1)
+ try:
+ array = self.images[i].get_pixels()
+ if array is not None:
+ offset = (y * self.images[i].get_width() + x) * 4
+ r, g, b, a = ord(array[offset]), ord(array[offset + 1]),\
+ ord(array[offset + 2]), ord(array[offset + 3])
+ return(r, g, b, a)
+ else:
+ return(-1, -1, -1, -1)
+ except IndexError:
+ print "Index Error: %d %d" % (len(array), offset)
+ return(-1, -1, -1, -1)
diff --git a/TurtleArt/tablock.py b/TurtleArt/tablock.py
new file mode 100644
index 0000000..4923ecb
--- /dev/null
+++ b/TurtleArt/tablock.py
@@ -0,0 +1,865 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2010 Walter Bender
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import gtk
+from taconstants import *
+from tasprite_factory import SVG, svg_str_to_pixbuf
+import sprites
+from gettext import gettext as _
+
+#
+# A class for the list of blocks and everything they share in common
+#
+class Blocks:
+ def __init__(self, font_scale_factor = 1):
+ self.list = []
+ self.font_scale_factor = font_scale_factor
+
+ def get_block(self, i):
+ if i < 0 or i > len(self.list)-1:
+ return(None)
+ else:
+ return(self.list[i])
+
+ def length_of_list(self):
+ return(len(self.list))
+
+ def append_to_list(self,block):
+ self.list.append(block)
+
+ def remove_from_list(self, block):
+ if block in self.list:
+ self.list.remove(block)
+
+ def print_list(self, block_type=None):
+ for i, block in enumerate(self.list):
+ if block_type is None or block_type == block.type:
+ print "%d: %s" % (i, block.name)
+
+ def set_scale(self, scale):
+ for b in self.list:
+ for i in range(len(b._font_size)):
+ b._font_size[i] *= b.scale*scale/self.font_scale_factor
+ self.font_scale_factor = scale
+
+ #
+ # sprite utilities
+ #
+ def spr_to_block(self, spr):
+ for b in self.list:
+ if spr == b.spr:
+ return b
+ return None
+
+#
+# A class for the individual blocks
+#
+class Block:
+ def __init__(self, block_list, sprite_list, name, x, y, type='block',
+ values=[], scale=BLOCK_SCALE, colors=["#FF0000","#A00000"]):
+ self.spr = None
+ self.shapes = [None, None]
+ self.name = name
+ self.colors = colors
+ self.scale = scale
+ self.docks = None
+ self.connections = None
+ self.status = None
+ self.values = []
+ self.primitive = None
+ self.type = type
+ self.dx = 0
+ self.ex = 0
+ self.ey = 0
+ self._ei = 0
+ self._font_size = [6.0, 4.5]
+ self._image = None
+
+ if OLD_NAMES.has_key(self.name):
+ self.name = OLD_NAMES[self.name]
+
+ for i in range(len(self._font_size)):
+ self._font_size[i] *= self.scale*block_list.font_scale_factor
+
+ for v in (values):
+ self.values.append(v)
+
+ self._new_block_from_factory(sprite_list, x, y)
+
+ if PRIMITIVES.has_key(name):
+ self.primitive = PRIMITIVES[self.name]
+
+ block_list.append_to_list(self)
+
+ # We may want to highlight a block...
+ def highlight(self):
+ if self.spr is not None:
+ self.spr.set_shape(self.shapes[1])
+
+ # Or unhighlight it.
+ def unhighlight(self):
+ if self.spr is not None:
+ self.spr.set_shape(self.shapes[0])
+
+ # We need to resize some blocks on the fly so that the labels fit.
+ def resize(self):
+ if not self.spr is not None:
+ return
+ dx = (self.spr.label_width()-self.spr.label_safe_width())/self.scale
+ if dx !=0:
+ self.dx += dx
+ if self.dx < 0:
+ self.dx = 0
+ self.refresh()
+
+ # Some blocks get a skin.
+ def set_image(self, image, x, y):
+ if not self.spr is not None:
+ return
+ self._image = image
+ self.spr.set_image(image, 1, x, y)
+
+ # The skin might need scaling.
+ # Keep the original here, the scaled version stays with the sprite.
+ def scale_image(self, x, y, w, h):
+ if not self.spr is not None:
+ return
+ if self._image is not None:
+ tmp = self._image.scale_simple(w, h,
+ gtk.gdk.INTERP_NEAREST)
+ self.spr.set_image(tmp, 1, x, y)
+
+ # We may want to rescale blocks as well.
+ def rescale(self, scale):
+ if not self.spr is not None:
+ return
+ for i in range(len(self._font_size)):
+ self._font_size[i] /= self.scale
+ self.dx /= self.scale
+ self.ex /= self.scale
+ self.ey /= self.scale
+ self.scale = scale
+ for i in range(len(self._font_size)):
+ self._font_size[i] *= self.scale
+ self.dx *= self.scale
+ self.ex *= self.scale
+ self.ey *= self.scale
+ self._set_label_attributes()
+ self.svg.set_scale(self.scale)
+ self.refresh()
+ self.spr.draw()
+
+ def refresh(self):
+ if not self.spr is not None:
+ return
+ self._make_block(self.svg)
+ self._set_margins()
+ self.spr.set_shape(self.shapes[0])
+
+ # We may want to add additional slots for arguments ("innies").
+ def add_arg(self, keep_expanding=True):
+ if not self.spr is not None:
+ return
+ h = self.svg.get_height()
+ self._ei += 1
+ if self.type == 'block' and keep_expanding:
+ self.svg.set_show(True)
+ else:
+ self.svg.set_show(False)
+ self.refresh()
+ return self.svg.get_height()-h
+
+ # We may want to grow a block vertically.
+ def expand_in_y(self, dy):
+ if not self.spr is not None:
+ return
+ self.ey += dy
+ if self.type == 'block':
+ self.svg.set_hide(True)
+ self.svg.set_show(True)
+ else:
+ self.svg.set_hide(False)
+ self.svg.set_show(False)
+ self.refresh()
+
+ # We may want to grow a block horizontally.
+ def expand_in_x(self, dx):
+ if not self.spr is not None:
+ return
+ self.ex += dx
+ if self.type == 'block':
+ self.svg.set_hide(True)
+ self.svg.set_show(True)
+ else:
+ self.svg.set_hide(False)
+ self.svg.set_show(False)
+ self.refresh()
+
+ def reset_x(self):
+ if not self.spr is not None:
+ return 0
+ dx = -self.ex
+ self.ex = 0
+ self.svg.set_hide(False)
+ if self.type == 'block':
+ self.svg.set_show(True)
+ else:
+ self.svg.set_show(False)
+ self.refresh()
+ return dx
+
+ def reset_y(self):
+ if not self.spr is not None:
+ return 0
+ dy = -self.ey
+ self.ey = 0
+ self.svg.set_hide(False)
+ if self.type == 'block':
+ self.svg.set_show(True)
+ else: # 'proto'
+ self.svg.set_show(False)
+ self.refresh()
+ return dy
+
+ def get_expand_x_y(self):
+ if not self.spr is not None:
+ return(0, 0)
+ return (self.ex, self.ey)
+
+ def _new_block_from_factory(self, sprite_list, x, y):
+ self.svg = SVG()
+ self.svg.set_scale(self.scale)
+ self.svg.set_gradiant(True)
+ self.svg.set_innie([False])
+ self.svg.set_outie(False)
+ self.svg.set_tab(True)
+ self.svg.set_slot(True)
+
+ if self.name in EXPANDABLE and self.type == 'block':
+ self.svg.set_show(True)
+
+ self._make_block(self.svg)
+ if sprite_list is not None:
+ self.spr = sprites.Sprite(sprite_list, x, y, self.shapes[0])
+ self._set_margins()
+ self._set_label_attributes()
+
+ if (self.name == 'number' or self.name == 'string') and\
+ len(self.values) > 0:
+ for i, v in enumerate(self.values):
+ if v is not None:
+ self._set_labels(i, str(v))
+ elif BLOCK_NAMES.has_key(self.name):
+ for i, n in enumerate(BLOCK_NAMES[self.name]):
+ self._set_labels(i, n)
+
+ # Make sure the labels fit.
+ if self.spr.label_width() > self.spr.label_safe_width():
+ self.resize()
+
+ def _set_margins(self):
+ self.spr.set_margins(self.svg.margins[0], self.svg.margins[1],
+ self.svg.margins[2], self.svg.margins[3])
+
+ def _set_label_attributes(self):
+ if self.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:
+ n = len(BLOCK_NAMES[self.name])
+ for i in range(n):
+ if i == 1: # top
+ self.spr.set_label_attributes(int(self._font_size[1]+0.5), True,
+ "right", "top", i)
+ elif i == 2: # bottom
+ self.spr.set_label_attributes(int(self._font_size[1]+0.5), True,
+ "right", "bottom", i)
+ else:
+ self.spr.set_label_attributes(int(self._font_size[0]+0.5), True,
+ "center", "middle", i)
+
+ def _set_labels(self, i, label):
+ self.spr.set_label(label, i)
+
+ def _make_block(self, svg):
+ self._left = 0
+ self._top = 0
+ self._right = 0
+ self._bottom = 0
+ self._set_colors(svg)
+ 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)
+ 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)
+ elif self.name in COLLAPSIBLE_TOP_NO_ARM:
+ self._make_collapsible_style_top(svg, True)
+ elif self.name in COLLAPSIBLE_BOTTOM:
+ self._make_collapsible_style_bottom(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)
+ print "WARNING: I don't know how to create a %s block" % (self.name)
+
+ def _set_colors(self, svg):
+ if BOX_COLORS.has_key(self.name):
+ self.colors = BOX_COLORS[self.name]
+ else:
+ for p in range(len(PALETTES)):
+ if self.name in PALETTES[p]:
+ self.colors = COLORS[p]
+ self.svg.set_colors(self.colors)
+
+ def _make_basic_style(self, svg, extension=0):
+ self.svg.expand(self.dx+self.ex+extension, self.ey+extension)
+ self._make_basic_block(svg)
+ self.docks = [['flow',True,self.svg.docks[0][0],self.svg.docks[0][1]],
+ ['flow',False,self.svg.docks[1][0],self.svg.docks[1][1]]]
+
+ def _make_basic_style_head(self, svg):
+ self.svg.expand(10+self.dx+self.ex, self.ey)
+ self.svg.set_slot(False)
+ self.svg.set_cap(True)
+ self._make_basic_block(svg)
+ self.docks = [['unavailable', False, 0, 0],
+ ['flow', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]]]
+
+ def _make_basic_style_head_1arg(self, svg):
+ self.svg.expand(10+self.dx+self.ex, self.ey)
+ self.svg.set_innie([True])
+ self.svg.set_slot(False)
+ self.svg.set_cap(True)
+ self._make_basic_block(svg)
+ self.docks = [['unavailable', False, 0, 0],
+ ['string', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_basic_style_tail(self, svg):
+ self.svg.expand(10+self.dx+self.ex, self.ey)
+ self.svg.set_tab(False)
+ self._make_basic_block(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['unavailable', False, 0, 0]]
+
+ def _make_basic_style_1arg(self, svg):
+ self.svg.expand(10+self.dx+self.ex, self.ey)
+ self.svg.set_innie([True])
+ self._make_basic_block(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]]]
+
+ def _make_basic_style_2arg(self, svg):
+ self.svg.expand(10+self.dx+self.ex, self.ey)
+ self.svg.set_innie([True,True])
+ self._make_basic_block(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['number', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['flow', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]]]
+
+ def _make_basic_style_var_arg(self, svg):
+ self.svg.expand(10+self.dx+self.ex, self.ey)
+ innie = [True]
+ for i in range(self._ei):
+ innie.append(True)
+ self.svg.set_innie(innie)
+ self._make_basic_block(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+ for i in range(self._ei):
+ self.docks.append(['number', False, self.svg.docks[i+2][0],
+ self.svg.docks[i+2][1]])
+ self.docks.append(['flow', False, self.svg.docks[self._ei+2][0],
+ self.svg.docks[self._ei+2][1]])
+
+ def _make_bullet_style(self, svg):
+ self.svg.expand(10+self.dx+self.ex, self.ey)
+ innie = [True, True]
+ for i in range(self._ei):
+ innie.append(True)
+ self.svg.set_innie(innie)
+ self._make_basic_block(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['string', False, self.svg.docks[1][0],
+ self.svg.docks[1][1], '['],
+ ['string', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]]]
+ for i in range(self._ei):
+ self.docks.append(['string', False, self.svg.docks[i+3][0],
+ self.svg.docks[i+3][1]])
+ self.docks.append(['flow', False, self.svg.docks[self._ei+3][0],
+ self.svg.docks[self._ei+3][1], ']'])
+
+ def _make_box_style(self, svg):
+ self.svg.expand(60+self.dx+self.ex, self.ey)
+ self._make_basic_box(svg)
+ self.docks = [['number', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['unavailable', False, 0, 0]]
+
+ def _make_media_style(self, svg):
+ self.svg.expand(40+self.dx+self.ex, 10+self.ey)
+ self._make_basic_box(svg)
+ self.docks = [['number', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['unavailable', False, 0, 0]]
+
+ def _make_number_style(self, svg):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ self.svg.set_innie([True,True])
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self._make_basic_block(svg)
+ """
+ NOTE: The "outie" is added last, so the dock order in NUMBER_STYLE
+ blocks needs to be modified.
+ """
+ self.docks = [['number', True, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['number', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_number_style_var_arg(self, svg):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ innie = [True]
+ for i in range(self._ei+1):
+ innie.append(True)
+ self.svg.set_innie(innie)
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self._make_basic_block(svg)
+ self.docks = [['number', True, self.svg.docks[2+self._ei][0],
+ self.svg.docks[2+self._ei][1]],
+ ['number', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]]]
+ for i in range(self._ei+1):
+ self.docks.append(['number', False, self.svg.docks[i+1][0],
+ self.svg.docks[i+1][1]])
+ self.docks.append(['unavailable', False, 0, 0])
+
+ def _make_number_style_block(self, svg):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ self.svg.set_innie([True,True])
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self._make_basic_block(svg)
+ self.docks = [['number', True, self.svg.docks[2][0],
+ self.svg.docks[2][1], '('],
+ ['number', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['unavailable', False, 0, 0, ')']]
+
+ def _make_number_style_1arg(self, svg):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ self.svg.set_innie([True])
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self._make_basic_block(svg)
+ self.docks = [['number', True, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['number', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]]]
+
+ def _make_number_style_1strarg(self, svg):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ self.svg.set_innie([True])
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self._make_basic_block(svg)
+ self.docks = [['number', True, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['string', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['unavailable', False, 0, 0]]
+
+ def _make_number_style_porch(self, svg):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ self.svg.set_innie([True,True])
+ self.svg.set_outie(True)
+ self.svg.set_tab(False)
+ self.svg.set_slot(False)
+ self.svg.set_porch(True)
+ self._make_basic_block(svg)
+ self.docks = [['number', True, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['number', False, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_compare_style(self, svg):
+ self.svg.expand(10+self.dx+self.ex, self.ey)
+ self._make_boolean_compare(svg)
+ self.docks = [['bool', True, self.svg.docks[0][0],
+ self.svg.docks[0][1], '('],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['number', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['unavailable', False, 0, 0, ')']]
+
+ def _make_boolean_style(self, svg):
+ self.svg.expand(10+self.dx+self.ex, self.ey)
+ self._make_boolean_and_or(svg)
+ self.docks = [['bool', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['bool', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['bool', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]]]
+
+ def _make_not_style(self, svg):
+ self.svg.expand(15+self.dx+self.ex, self.ey)
+ self._make_boolean_not(svg)
+ self.docks = [['bool', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['bool', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_flow_style(self, svg):
+ self.svg.expand(10+self.dx+self.ex, self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self._make_basic_flow(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1], '['],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1], ']']]
+
+ def _make_flow_style_tail(self, svg):
+ self.svg.expand(10+self.dx+self.ex, self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(False)
+ self._make_basic_flow(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_flow_style_1arg(self, svg):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_innie([True])
+ self._make_basic_flow(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1], '['],
+ ['flow', False, self.svg.docks[3][0],
+ self.svg.docks[3][1], ']']]
+
+ def _make_flow_style_boolean(self, svg):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_boolean(True)
+ self._make_basic_flow(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['bool', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1], '['],
+ ['flow', False, self.svg.docks[3][0],
+ self.svg.docks[3][1], ']']]
+
+ def _make_flow_style_while(self, svg):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_boolean(True)
+ self._make_basic_flow(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['bool', False, self.svg.docks[1][0],
+ self.svg.docks[1][1], '['],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1], ']['],
+ ['flow', False, self.svg.docks[3][0],
+ self.svg.docks[3][1], ']']]
+
+ def _make_flow_style_else(self, svg):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_else(True)
+ self.svg.set_boolean(True)
+ self._make_basic_flow(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['bool', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[3][0],
+ self.svg.docks[3][1], '['],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1], ']['],
+ ['flow', False, self.svg.docks[4][0],
+ self.svg.docks[4][1], ']']]
+
+ def _make_collapsible_style_top(self, svg, no_arm=False):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ self.svg.set_no_arm(no_arm)
+ self._make_collapsible_top_block(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['number', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]]]
+
+ def _make_collapsible_style_bottom(self, svg):
+ self.svg.expand(self.dx+self.ex, self.ey)
+ self._make_collapsible_bottom_block(svg)
+ self.docks = [['flow',True,self.svg.docks[0][0],self.svg.docks[0][1]],
+ ['flow',False,self.svg.docks[1][0],self.svg.docks[1][1]]]
+
+ # Depreciated block styles
+ def _make_portfolio_style_2x2(self, svg):
+ self.svg.expand(30+self.dx+self.ex, 10+self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_innie([True, True, False, True])
+ self._make_portfolio(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['string', False, self.svg.docks[6][0],
+ self.svg.docks[6][1]],
+ ['media', False, self.svg.docks[5][0],
+ self.svg.docks[5][1]],
+ ['media', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['media', False, self.svg.docks[4][0],
+ self.svg.docks[4][1]],
+ ['media', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['flow', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]]]
+
+ def _make_portfolio_style_2x1(self, svg):
+ self.svg.expand(30+self.dx+self.ex, 10+self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_innie([True, True])
+ self._make_portfolio(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['string', False, self.svg.docks[4][0],
+ self.svg.docks[4][1]],
+ ['media', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]],
+ ['media', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]],
+ ['flow', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]]]
+
+ def _make_portfolio_style_1x2(self, svg):
+ self.svg.expand(30+self.dx+self.ex, 15+self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_innie([True, True, False, True])
+ self.svg.set_draw_innies(False)
+ self._make_portfolio(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['string', False, self.svg.docks[4][0],
+ self.svg.docks[4][1]],
+ ['media', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]],
+ ['media', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_portfolio_style_1x1(self, svg):
+ self.svg.expand(30+self.dx+self.ex, 15+self.ey)
+ self.svg.set_slot(True)
+ self.svg.set_tab(True)
+ self.svg.set_innie([True, True])
+ self.svg.set_draw_innies(False)
+ self._make_portfolio(svg)
+ self.docks = [['flow', True, self.svg.docks[0][0],
+ self.svg.docks[0][1]],
+ ['string', False, self.svg.docks[3][0],
+ self.svg.docks[3][1]],
+ ['media', False, self.svg.docks[2][0],
+ self.svg.docks[2][1]],
+ ['flow', False, self.svg.docks[1][0],
+ self.svg.docks[1][1]]]
+
+ def _make_basic_block(self, svg):
+ self.shapes[0] = svg_str_to_pixbuf(self.svg.basic_block())
+ self.width = self.svg.get_width()
+ self.height = self.svg.get_height()
+ self.svg.set_stroke_width(SELECTED_STROKE_WIDTH)
+ self.svg.set_stroke_color(SELECTED_COLOR)
+ self.shapes[1] = svg_str_to_pixbuf(self.svg.basic_block())
+
+ def _make_collapsible_top_block(self, svg):
+ self.shapes[0] = svg_str_to_pixbuf(self.svg.sandwich_top())
+ self.width = self.svg.get_width()
+ self.height = self.svg.get_height()
+ self.svg.set_stroke_width(SELECTED_STROKE_WIDTH)
+ self.svg.set_stroke_color(SELECTED_COLOR)
+ self.shapes[1] = svg_str_to_pixbuf(self.svg.sandwich_top())
+
+ def _make_collapsible_bottom_block(self, svg):
+ self.shapes[0] = svg_str_to_pixbuf(self.svg.sandwich_bottom())
+ self.width = self.svg.get_width()
+ self.height = self.svg.get_height()
+ self.svg.set_stroke_width(SELECTED_STROKE_WIDTH)
+ self.svg.set_stroke_color(SELECTED_COLOR)
+ self.shapes[1] = svg_str_to_pixbuf(self.svg.sandwich_bottom())
+
+ def _make_basic_box(self, svg):
+ self.shapes[0] = svg_str_to_pixbuf(self.svg.basic_box())
+ self.width = self.svg.get_width()
+ self.height = self.svg.get_height()
+ self.svg.set_stroke_width(SELECTED_STROKE_WIDTH)
+ self.svg.set_stroke_color(SELECTED_COLOR)
+ self.shapes[1] = svg_str_to_pixbuf(self.svg.basic_box())
+
+ def _make_portfolio(self, svg):
+ self.shapes[0] = svg_str_to_pixbuf(self.svg.portfolio())
+ self.width = self.svg.get_width()
+ self.height = self.svg.get_height()
+ self.svg.set_stroke_width(SELECTED_STROKE_WIDTH)
+ self.svg.set_stroke_color(SELECTED_COLOR)
+ self.shapes[1] = svg_str_to_pixbuf(self.svg.portfolio())
+
+ def _make_basic_flow(self, svg):
+ self.shapes[0] = svg_str_to_pixbuf(self.svg.basic_flow())
+ self.width = self.svg.get_width()
+ self.height = self.svg.get_height()
+ self.svg.set_stroke_width(SELECTED_STROKE_WIDTH)
+ self.svg.set_stroke_color(SELECTED_COLOR)
+ self.shapes[1] = svg_str_to_pixbuf(self.svg.basic_flow())
+
+ def _make_boolean_compare(self, svg):
+ self.shapes[0] = svg_str_to_pixbuf(self.svg.boolean_compare())
+ self.width = self.svg.get_width()
+ self.height = self.svg.get_height()
+ self.svg.set_stroke_width(SELECTED_STROKE_WIDTH)
+ self.svg.set_stroke_color(SELECTED_COLOR)
+ self.shapes[1] = svg_str_to_pixbuf(self.svg.boolean_compare())
+
+ def _make_boolean_and_or(self, svg):
+ self.shapes[0] = svg_str_to_pixbuf(self.svg.boolean_and_or())
+ self.width = self.svg.get_width()
+ self.height = self.svg.get_height()
+ self.svg.set_stroke_width(SELECTED_STROKE_WIDTH)
+ self.svg.set_stroke_color(SELECTED_COLOR)
+ self.shapes[1] = svg_str_to_pixbuf(self.svg.boolean_and_or())
+
+ def _make_boolean_not(self, svg):
+ self.shapes[0] = svg_str_to_pixbuf(self.svg.boolean_not())
+ self.width = self.svg.get_width()
+ self.height = self.svg.get_height()
+ self.svg.set_stroke_width(SELECTED_STROKE_WIDTH)
+ self.svg.set_stroke_color(SELECTED_COLOR)
+ self.shapes[1] = svg_str_to_pixbuf(self.svg.boolean_not())
diff --git a/TurtleArt/tacanvas.py b/TurtleArt/tacanvas.py
new file mode 100644
index 0000000..03f69fc
--- /dev/null
+++ b/TurtleArt/tacanvas.py
@@ -0,0 +1,596 @@
+#Copyright (c) 2007-8, Playful Invention Company.
+#Copyright (c) 2008-10, Walter Bender
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import gtk
+from math import sin, cos, pi
+import pango
+import cairo
+
+from sprites import Sprite
+from tasprite_factory import SVG
+from tautils import image_to_base64, data_to_string, round_int
+from taconstants import CANVAS_LAYER, DEFAULT_TURTLE, BLACK, WHITE
+
+import logging
+_logger = logging.getLogger('turtleart-activity')
+
+
+def wrap100(n):
+ """ A variant on mod... 101 -> 99; 199 -> 1 """
+ n = int(n)
+ n %= 200
+ if n > 99:
+ n = 199 - n
+ return n
+
+
+def calc_shade(c, s):
+ """ Convert a color to the current shade (lightness/darkness). """
+ if s < 0:
+ return int(c * (1 + s * 0.8))
+ return int(c + (65536 - c) * s * 0.9)
+
+
+def calc_gray(c, g):
+ """ Gray is a psuedo saturation calculation. """
+ if g == 100:
+ return c
+ return int(((c * g) + (32768 * (100 - g))) / 100)
+
+colors = {}
+DEGTOR = 2 * pi / 360
+
+color_table = (
+ 0xFF0000, 0xFF0D00, 0xFF1A00, 0xFF2600, 0xFF3300,
+ 0xFF4000, 0xFF4D00, 0xFF5900, 0xFF6600, 0xFF7300,
+ 0xFF8000, 0xFF8C00, 0xFF9900, 0xFFA600, 0xFFB300,
+ 0xFFBF00, 0xFFCC00, 0xFFD900, 0xFFE600, 0xFFF200,
+ 0xFFFF00, 0xE6FF00, 0xCCFF00, 0xB3FF00, 0x99FF00,
+ 0x80FF00, 0x66FF00, 0x4DFF00, 0x33FF00, 0x1AFF00,
+ 0x00FF00, 0x00FF0D, 0x00FF1A, 0x00FF26, 0x00FF33,
+ 0x00FF40, 0x00FF4D, 0x00FF59, 0x00FF66, 0x00FF73,
+ 0x00FF80, 0x00FF8C, 0x00FF99, 0x00FFA6, 0x00FFB3,
+ 0x00FFBF, 0x00FFCC, 0x00FFD9, 0x00FFE6, 0x00FFF2,
+ 0x00FFFF, 0x00F2FF, 0x00E6FF, 0x00D9FF, 0x00CCFF,
+ 0x00BFFF, 0x00B3FF, 0x00A6FF, 0x0099FF, 0x008CFF,
+ 0x0080FF, 0x0073FF, 0x0066FF, 0x0059FF, 0x004DFF,
+ 0x0040FF, 0x0033FF, 0x0026FF, 0x001AFF, 0x000DFF,
+ 0x0000FF, 0x0D00FF, 0x1A00FF, 0x2600FF, 0x3300FF,
+ 0x4000FF, 0x4D00FF, 0x5900FF, 0x6600FF, 0x7300FF,
+ 0x8000FF, 0x8C00FF, 0x9900FF, 0xA600FF, 0xB300FF,
+ 0xBF00FF, 0xCC00FF, 0xD900FF, 0xE600FF, 0xF200FF,
+ 0xFF00FF, 0xFF00E6, 0xFF00CC, 0xFF00B3, 0xFF0099,
+ 0xFF0080, 0xFF0066, 0xFF004D, 0xFF0033, 0xFF001A)
+
+
+class TurtleGraphics:
+ """ A class for the Turtle graphics canvas """
+
+ def __init__(self, tw, width, height):
+ """ Create a sprite to hold the canvas. """
+ self.tw = tw
+ self.width = width
+ self.height = height
+ if self.tw.interactive_mode:
+ self.canvas = Sprite(tw.sprite_list, 0, 0,
+ gtk.gdk.Pixmap(self.tw.area, self.width, self.height, -1))
+ else:
+ self.canvas = Sprite(None, 0, 0, self.tw.window)
+ self.canvas.set_layer(CANVAS_LAYER)
+ (self.cx, self.cy) = self.canvas.get_xy()
+ self.canvas.type = 'canvas'
+ self.gc = self.canvas.images[0].new_gc()
+ self.cm = self.gc.get_colormap()
+ self.fgrgb = [255, 0, 0]
+ self.fgcolor = self.cm.alloc_color('red')
+ self.bgrgb = [255, 248, 222]
+ self.bgcolor = self.cm.alloc_color('#fff8de')
+ self.textsize = 48
+ self.textcolor = self.cm.alloc_color('blue')
+ self.tw.active_turtle.show()
+ self.shade = 0
+ self.pendown = True
+ self.xcor = 0
+ self.ycor = 0
+ self.heading = 0
+ self.pensize = 5
+ self.tcolor = 0
+ self.color = 0
+ self.gray = 100
+ self.fill = False
+ self.poly_points = []
+ self.svg = SVG()
+ self.svg.set_fill_color('none')
+ self.tw.svg_string = ''
+ self.clearscreen(False)
+
+ def start_fill(self):
+ """ Start accumulating points of a polygon to fill. """
+ self.fill = True
+ self.poly_points = []
+
+ def stop_fill(self):
+ """ Fill the polygon. """
+ self.fill = False
+ if len(self.poly_points) == 0:
+ return
+ 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.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 = []
+
+ 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)
+ 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.settextsize(48)
+ self.setshade(50, share)
+ self.setpen(True, 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_pen_size(5)
+ self.tw.active_turtle.set_pen_state(True)
+ self.seth(0, share)
+ self.setxy(0, 0, share)
+ self.set_turtle(DEFAULT_TURTLE)
+ self.tw.svg_string = ''
+ self.svg.reset_min_max()
+ self.fill = False
+ self.poly_points = []
+
+ def forward(self, n, share=True):
+ """ Move the turtle forward."""
+ nn = n * self.tw.coord_scale
+ self.gc.set_foreground(self.fgcolor)
+ oldx, oldy = self.xcor, self.ycor
+ try:
+ 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__))
+ 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()
+ if self.tw.sharing() and share:
+ self.tw.activity.send_event("f|%s" % \
+ (data_to_string([self.tw.nick,
+ int(n)])))
+
+ 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__))
+ return
+ self.heading %= 360
+ self.turn_turtle()
+ if self.tw.sharing() and share:
+ self.tw.activity.send_event("r|%s" % \
+ (data_to_string([self.tw.nick, round_int(self.heading)])))
+
+ def right(self, n, share=True):
+ """ Rotate turtle clockwise """
+ try:
+ self.heading += n
+ except TypeError, ValueError:
+ _logger.debug("bad value sent to %s" % (__name__))
+ return
+ self.heading %= 360
+ self.turn_turtle()
+ if self.tw.sharing() and share:
+ self.tw.activity.send_event("r|%s" % \
+ (data_to_string([self.tw.nick, round_int(self.heading)])))
+
+ def arc(self, a, r, share=True):
+ """ Draw an arc """
+ self.gc.set_foreground(self.fgcolor)
+ rr = r * self.tw.coord_scale
+ try:
+ if a < 0:
+ self.larc(-a, rr)
+ else:
+ self.rarc(a, rr)
+ except TypeError, ValueError:
+ _logger.debug("bad value sent to %s" % (__name__))
+ return
+ self.move_turtle()
+ if self.tw.sharing() and share:
+ self.tw.activity.send_event("a|%s" % \
+ (data_to_string([self.tw.nick, [round_int(a), round_int(r)]])))
+
+ def rarc(self, a, r):
+ """ draw a clockwise arc """
+ if r < 0:
+ r = -r
+ a = -a
+ s = 0
+ else:
+ s = 1
+ oldx, oldy = self.xcor, self.ycor
+ cx = self.xcor + r * cos(self.heading * DEGTOR)
+ cy = self.ycor - r * sin(self.heading * DEGTOR)
+ x = self.width / 2 + int(cx - r)
+ y = self.height / 2 - int(cy + r)
+ w = int(2 * r)
+ h = w
+ if self.pendown:
+ self.canvas.images[0].draw_arc(self.gc, False, x, y, w, h,
+ int(180 - self.heading - a) * 64, int(a) * 64)
+ self.invalt(x - self.pensize * self.tw.coord_scale / 2 - 3,
+ y - 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.right(a, False)
+ 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)
+ self.tw.svg_string += "\"\n"
+ self.tw.svg_string += self.svg.style()
+
+ def larc(self, a, r):
+ """ draw a counter-clockwise arc """
+ if r < 0:
+ r = -r
+ a = -a
+ s = 1
+ else:
+ s = 0
+ oldx, oldy = self.xcor, self.ycor
+ cx = self.xcor - r * cos(self.heading * DEGTOR)
+ cy = self.ycor + r * sin(self.heading * DEGTOR)
+ x = self.width / 2 + int(cx-r)
+ y = self.height / 2 - int(cy+r)
+ w = int(2 * r)
+ h = w
+ if self.pendown:
+ self.canvas.images[0].draw_arc(self.gc, False, x, y, w, h,
+ int(360 - self.heading) * 64,
+ int(a) * 64)
+ self.invalt(x - self.pensize * self.tw.coord_scale / 2 - 3,
+ y - 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.right(-a, False)
+ 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)
+ self.tw.svg_string += "\"\n"
+ self.tw.svg_string += self.svg.style()
+
+ def setxy(self, x, y, share=True):
+ """ Move turtle to position x,y """
+ x *= self.tw.coord_scale
+ y *= self.tw.coord_scale
+ try:
+ self.xcor, self.ycor = x, y
+ except TypeError, ValueError:
+ _logger.debug("bad value sent to %s" % (__name__))
+ return
+ self.move_turtle()
+ if self.tw.sharing() and share:
+ self.tw.activity.send_event("x|%s" % \
+ (data_to_string([self.tw.nick, [round_int(x), round_int(y)]])))
+
+ def setpensize(self, ps, share=True):
+ """ Set the pen size """
+ try:
+ if ps < 0:
+ ps = 0
+ self.pensize = ps
+ except TypeError, ValueError:
+ _logger.debug("bad value sent to %s" % (__name__))
+ 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)
+ self.svg.set_stroke_width(self.pensize)
+ if self.tw.sharing() and share:
+ self.tw.activity.send_event("w|%s" % \
+ (data_to_string([self.tw.nick, round_int(ps)])))
+
+ 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__))
+ return
+ self.tw.active_turtle.set_color(c)
+ self.set_fgcolor()
+ self.set_textcolor()
+ if self.tw.sharing() and share:
+ self.tw.activity.send_event("c|%s" % \
+ (data_to_string([self.tw.nick, round_int(c)])))
+
+ 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__))
+ 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)
+ if self.tw.sharing() and share:
+ self.tw.activity.send_event("g|%s" % \
+ (data_to_string([self.tw.nick, round_int(self.gray)])))
+
+ def settextcolor(self, c):
+ """ Set the text color """
+ try:
+ self.tcolor = c
+ except TypeError, ValueError:
+ _logger.debug("bad value sent to %s" % (__name__))
+ return
+ self.set_textcolor()
+
+ def settextsize(self, c):
+ """ Set the text size """
+ try:
+ self.tw.textsize = c
+ except TypeError, ValueError:
+ _logger.debug("bad value sent to %s" % (__name__))
+
+ 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__))
+ return
+ self.tw.active_turtle.set_shade(s)
+ self.set_fgcolor()
+ self.set_textcolor()
+ if self.tw.sharing() and share:
+ self.tw.activity.send_event("s|%s" % \
+ (data_to_string([self.tw.nick, round_int(s)])))
+
+ 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)
+ self.gc.set_foreground(self.fgcolor)
+ self.bgrgb = self.fgrgb[:]
+ self.canvas.images[0].draw_rectangle(self.gc, True, *rect)
+ self.invalt(0, 0, self.width, self.height)
+ self.setcolor(oldc, False)
+ self.setshade(olds, False)
+ self.tw.svg_string = ''
+ self.svg.reset_min_max()
+ self.fill = False
+ self.poly_points = []
+
+ def set_fgcolor(self):
+ """ Set the foreground color """
+ if self.color == WHITE or self.shade == WHITE:
+ r = 0xFF00
+ g = 0xFF00
+ b = 0xFF00
+ elif self.color == BLACK or self.shade == BLACK:
+ r = 0x0000
+ g = 0x0000
+ b = 0x0000
+ else:
+ sh = (wrap100(self.shade) - 50) / 50.0
+ rgb = color_table[wrap100(self.color)]
+ r = (rgb >> 8) & 0xff00
+ r = calc_gray(r, self.gray)
+ r = calc_shade(r, sh)
+ g = rgb & 0xff00
+ g = calc_gray(g, self.gray)
+ g = calc_shade(g, sh)
+ b = (rgb << 8) & 0xff00
+ b = calc_gray(b, self.gray)
+ b = calc_shade(b, sh)
+ self.fgrgb = [r >> 8, g >> 8, b >> 8]
+ self.fgcolor = self.cm.alloc_color(r, g, b)
+ self.svg.set_stroke_color("#%02x%02x%02x" % (self.fgrgb[0],
+ self.fgrgb[1],
+ self.fgrgb[2]))
+
+ def set_textcolor(self):
+ """ Set the text color """
+ sh = (wrap100(self.shade) - 50) / 50.0
+ rgb = color_table[wrap100(self.tcolor)]
+ r, g, b = (rgb >> 8) & 0xff00, rgb & 0xff00, (rgb << 8) & 0xff00
+ r, g, b = calc_shade(r, sh), calc_shade(g, sh), calc_shade(b, sh)
+ self.tw.textcolor = self.cm.alloc_color(r, g, b)
+
+ def setpen(self, bool, share=True):
+ """ Lower or raise the pen """
+ self.pendown = bool
+ if self.tw.sharing() and share:
+ self.tw.activity.send_event("p|%s" % \
+ (data_to_string([self.tw.nick, bool])))
+
+ def draw_pixbuf(self, pixbuf, a, b, x, y, w, h, path):
+ """ Draw a pixbuf """
+ w *= self.tw.coord_scale
+ h *= self.tw.coord_scale
+ self.canvas.images[0].draw_pixbuf(self.gc, pixbuf, a, b, x, y)
+ self.invalt(x, y, w, h)
+ if self.tw.saving_svg:
+ 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))
+ else:
+ self.tw.svg_string += self.svg.image(x - self.width / 2,
+ y, w, h, path)
+
+ def draw_text(self, label, x, y, size, w):
+ """ Draw text """
+ w *= self.tw.coord_scale
+ self.gc.set_foreground(self.tw.textcolor)
+ 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__))
+ return
+ if self.tw.interactive_mode:
+ if type(label) == str or type(label) == unicode:
+ pl = self.tw.window.create_pango_layout(label.replace("\0",
+ " "))
+ elif type(label) == float or type(label) == int:
+ pl = self.tw.window.create_pango_layout(str(label))
+ else:
+ pl = self.tw.window.create_pango_layout(str(label))
+ pl.set_font_description(fd)
+ pl.set_width(int(w) * pango.SCALE)
+ self.canvas.images[0].draw_layout(self.gc, int(x), int(y), pl)
+ w, h = pl.get_pixel_size()
+ self.invalt(x, y, w, h)
+ else: # pixmap doesn't support pango
+ message = str(label).replace("\0"," ")
+ context = self.canvas.images[0].cairo_create()
+ context.set_font_size(size)
+ q, k, w, h = context.text_extents(message)[:4]
+ context.set_source_rgb(0, 0, 0)
+ 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,
+ y + size,
+ size, w, label)
+
+ 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)
+ if x1 < x2:
+ minx, maxx = x1, x2
+ else:
+ minx, maxx = x2, x1
+ if y1 < y2:
+ miny, maxy = y1, y2
+ else:
+ miny, maxy = y2, y1
+ w, h = maxx-minx, maxy-miny
+ self.canvas.images[0].draw_line(self.gc, x1, y1, x2, y2)
+ if self.fill and self.poly_points == []:
+ self.poly_points.append((x1, 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,
+ w + self.pensize * self.tw.coord_scale + 6,
+ h + self.pensize * self.tw.coord_scale + 6)
+
+ def turn_turtle(self):
+ """ Change the orientation of the turtle """
+ self.tw.active_turtle.set_heading(self.heading)
+
+ def move_turtle(self):
+ """ Move the turtle """
+ x, y = self.width / 2 + int(self.xcor), self.height / 2 - int(self.ycor)
+ self.tw.active_turtle.move((self.cx + x - 28, self.cy + y - 30))
+
+ def invalt(self, x, y, w, h):
+ """ Mark a region for refresh """
+ if self.tw.interactive_mode:
+ self.tw.area.invalidate_rect(gtk.gdk.Rectangle(int(x + self.cx),
+ int(y + self.cy),
+ int(w), int(h)),
+ False)
+
+ def set_turtle(self, k, colors=None):
+ """ Select the current turtle and associated pen status """
+ if not self.tw.turtles.dict.has_key(k):
+ # if it is a new turtle, start it in the center of the screen
+ self.tw.active_turtle = self.tw.turtles.get_turtle(k, True, colors)
+ self.seth(0, False)
+ self.setxy(0, 0, False)
+ self.tw.active_turtle.set_pen_state(True)
+ self.tw.active_turtle = self.tw.turtles.get_turtle(k, False)
+ tx, ty = self.tw.active_turtle.get_xy()
+ self.xcor = -self.width / 2 + tx + 28
+ self.ycor = self.height / 2 - ty - 30
+ self.heading = self.tw.active_turtle.get_heading()
+ self.setcolor(self.tw.active_turtle.get_color(), False)
+ self.setgray(self.tw.active_turtle.get_gray(), False)
+ self.setshade(self.tw.active_turtle.get_shade(), False)
+ self.setpensize(self.tw.active_turtle.get_pen_size(), False)
+ self.setpen(self.tw.active_turtle.get_pen_state(), False)
+
+ def svg_close(self):
+ """ Close current SVG graphic """
+ if self.tw.svg_string == '':
+ return
+ self.svg.calc_w_h(False)
+ self.tw.svg_string = "%s%s%s%s" % (self.svg.header(True),
+ self.svg.background("#%02x%02x%02x" %\
+ (self.bgrgb[0], self.bgrgb[1], self.bgrgb[2])),
+ self.tw.svg_string, self.svg.footer())
diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py
new file mode 100644
index 0000000..2998885
--- /dev/null
+++ b/TurtleArt/taconstants.py
@@ -0,0 +1,1065 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2010, 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, 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 _
+
+#
+# Sprite layers
+#
+
+HIDE_LAYER = 100
+CANVAS_LAYER = 500
+OVERLAY_LAYER = 525
+TURTLE_LAYER = 550
+BLOCK_LAYER = 600
+CATEGORY_LAYER = 700
+TAB_LAYER = 710
+STATUS_LAYER = 900
+TOP_LAYER = 1000
+
+#
+# Block-palette categories
+#
+
+PALETTE_NAMES = ['turtle', 'pen', 'colors', 'numbers', 'flow', 'blocks',
+ 'extras', 'portfolio', 'trash']
+
+PALETTES = [['clean', 'forward', 'back', 'show', 'left', 'right',
+ 'seth', 'setxy', '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'],
+ ['hat1', 'stack1', 'hat', 'hat2', 'stack2', 'stack',
+ 'storeinbox1', 'storeinbox2', 'string', 'box1', 'box2', 'box',
+ 'storein', 'start'],
+ ['kbinput', 'push', 'printheap', 'keyboard', 'pop', 'clearheap',
+ 'myfunc1arg', 'userdefined', 'addturtle', 'comment', 'print',
+ 'cartesian', 'width', 'height', 'polar', 'sandwichtop',
+ 'sandwichbottom'],
+ ['journal', 'audio', 'description', 'hideblocks', 'showblocks',
+ 'fullscreen', 'savepix', 'savesvg', '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"], ["#0000FF","#0000A0"], ["#FFFF00","#A0A000"]]
+
+BOX_COLORS = {'red':["#FF0000","#A00000"],'orange':["#FFD000","#AA8000"],
+ 'yellow':["#FFFF00","#A0A000"],'green':["#00FF00","#008000"],
+ 'cyan':["#00FFFF","#00A0A0"],'blue':["#0000FF","#000080"],
+ 'purple':["#FF00FF","#A000A0"], 'white':["#FFFFFF", "#A0A0A0"],
+ 'black':["#000000", "#000000"]}
+
+#
+# Misc. parameters
+#
+PALETTE_HEIGHT = 120
+PALETTE_WIDTH = 175
+SELECTOR_WIDTH = 55
+ICON_SIZE = 55
+SELECTED_COLOR = "#0000FF"
+SELECTED_STROKE_WIDTH = 1.0
+STANDARD_STROKE_WIDTH = 1.0
+BLOCK_SCALE = 2.0
+PALETTE_SCALE = 1.5
+DEFAULT_TURTLE = 1
+HORIZONTAL_PALETTE = 0
+VERTICAL_PALETTE = 1
+BLACK = -9999
+WHITE = -9998
+
+#
+# Block-style definitions
+#
+BASIC_STYLE_HEAD = ['start', 'hat1', 'hat2', 'restore', 'restoreall']
+BASIC_STYLE_HEAD_1ARG = ['hat']
+BASIC_STYLE_TAIL = ['stopstack', 'empty']
+BASIC_STYLE = ['clean', 'penup', 'pendown', 'stack1', 'stack2', 'vspace',
+ 'hideblocks', 'showblocks', 'clearheap', 'printheap', 'kbinput',
+ 'fullscreen', 'sandwichcollapsed', 'cartesian', 'polar', 'startfill',
+ 'stopfill']
+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']
+BASIC_STYLE_VAR_ARG = ['userdefined', 'userdefined2args', 'userdefined3args']
+BULLET_STYLE = ['templatelist', 'list']
+BASIC_STYLE_2ARG = ['arc', 'setxy', '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',
+ 'volume', 'pitch', 'voltage', 'resistance', 'gray']
+BOX_STYLE_MEDIA = ['description', 'audio', 'journal']
+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 = ['sandwichtop2']
+COLLAPSIBLE_BOTTOM = ['sandwichbottom']
+
+# Depreciated block styles
+PORTFOLIO_STYLE_2x2 = ['template2x2']
+PORTFOLIO_STYLE_1x1 = ['template1x1', 'template1x1a']
+PORTFOLIO_STYLE_2x1 = ['template2x1']
+PORTFOLIO_STYLE_1x2 = ['template1x2']
+
+#
+# Blocks that are expandable
+#
+EXPANDABLE = ['vspace', 'hspace', 'templatelist', 'list', 'identity2',
+ 'myfunc1arg', 'myfunc2arg', 'myfunc3arg', 'userdefined',
+ 'userdefined2args', 'userdefined3args']
+
+#
+# Blocks that are 'collapsible'
+#
+COLLAPSIBLE = ['sandwichbottom', 'sandwichcollapsed']
+
+#
+# Depreciated 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', 'journal']
+
+#
+# These blocks get a special skin
+#
+BLOCKS_WITH_SKIN = ['journal', 'audio', 'description', 'nop', 'userdefined',
+ 'userdefined2args', 'userdefined3args']
+
+PYTHON_SKIN = ['nop', 'userdefined', 'userdefined2args', 'userdefined3args']
+
+#
+# Block-name dictionary used for labels
+#
+BLOCK_NAMES = {
+ 'addturtle':[_('turtle')],
+ 'and2':[_('and')],
+ 'arc':[_('arc'), _('angle'), _('radius')],
+ 'audio':[' '],
+ 'back':[_('back')],
+ 'black':[_('black')],
+ 'blue':[_('blue')+' = 70'],
+ 'bottompos':[_('bottom')],
+ 'bottomy':[_('picture bottom')],
+ 'box':[_('box')],
+ 'box1':[_('box 1')],
+ 'box2':[_('box 2')],
+ 'cartesian':[_('Cartesian')],
+ 'clean':[_(' clean ')],
+ 'clearheap':[_('empty heap')],
+ 'color':[_('color')],
+ 'comment':[_('comment')],
+ 'cyan':[_('cyan')+' = 50'],
+ 'decription':[' '],
+ 'division2':['/'],
+ 'empty':[_('empty trash')],
+ 'equal2':['='],
+ 'fillscreen':[_('fill screen'), _('color'), _('shade')],
+ 'forever':[_('forever')],
+ 'forward':[_('forward')],
+ 'fullscreen':[_('full screen')],
+ 'gray':[_('gray')],
+ 'greater2':[">"],
+ 'green':[_('green')+' = 30'],
+ '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'],
+ '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'],
+ 'orange':[_('orange')+' = 10'],
+ 'or2':[_('or')],
+ 'pendown':[_('pen down')],
+ 'pensize':[_('pen size')],
+ 'penup':[_('pen up')],
+ 'picturelist':[' '],
+ 'picture1x1':[' '],
+ 'picture1x1a':[' '],
+ 'picture2x2':[' '],
+ 'picture2x1':[' '],
+ 'picture1x2':[' '],
+ 'pitch':[_('pitch')],
+ 'plus2':['+'],
+ 'polar':[_('polar')],
+ 'pop':[_('pop')],
+ 'printheap':[_('show heap')],
+ 'print':[_('print')],
+ 'product2':['×'],
+ 'purple':[_('purple')+' = 90'],
+ 'push':[_('push')],
+ 'random':[_('random'), _('min'), _('max')],
+ 'red':[_('red')+' = 0'],
+ 'remainder2':[_('mod')],
+ 'repeat':[' ',_('repeat')],
+ 'resistance':[_('resistance')],
+ 'restore':[_('restore last')],
+ 'restoreall':[_('restore all')],
+ 'right':[_('right')],
+ 'rightpos':[_('right')],
+ 'rightx':[_('picture right')],
+ 'savepix':[_('save picture')],
+ 'savesvg':[_('save SVG')],
+ 'sandwichbottom':[' '],
+ 'sandwichcollapsed':[_('click to open')],
+ 'sandwichtop':[_('top of stack')],
+ 'sandwichtop2':[_('top of stack')],
+ 'scale':[_('scale')],
+ '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')],
+ 'shade':[_('shade')],
+ 'show':[_('show')],
+ 'showblocks':[_('show blocks')],
+ 'showaligned':[_('show aligned')],
+ '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')],
+ 'turtle':[_('turtle')],
+ 'until':[_('until')],
+ 'userdefined':[_(' ')],
+ 'userdefined2args':[_(' ')],
+ 'userdefined3args':[_(' ')],
+ 'voltage':[_('voltage')],
+ 'volume':[_('volume')],
+ 'vspace':[' '],
+ 'wait':[_('wait')],
+ 'while':[_('while')],
+ 'while2':[_('while')],
+ 'white':[_('white')],
+ 'width':[_('width')],
+ 'write':[_('write')],
+ 'xcor':[_('xcor')],
+ 'ycor':[_('ycor')],
+ 'yellow':[_('yellow')+' = 20']}
+
+#
+# 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',
+ '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',
+ 'remainder2':'mod',
+ 'repeat':'repeat',
+ 'resistance':'resistance',
+ 'right':'right',
+ 'rightpos':'rpos',
+ 'rightx':'rightx',
+ 'sandwichtop':'comment',
+ 'sandwichtop2':'comment',
+ 'sandwichbottom':'nop',
+ 'sandwichcollapsed':'nop',
+ 'savepix':'savepix',
+ 'savesvg':'savesvg',
+ 'scale':'scale',
+ 'setcolor':'setcolor',
+ 'setgray':'setgray',
+ 'seth':'seth',
+ 'setpensize':'setpensize',
+ 'setscale':'setscale',
+ 'setshade':'setshade',
+ 'settextsize':'settextsize',
+ 'settextcolor':'settextcolor',
+ 'setxy':'setxy',
+ 'shade':'shade',
+ 'show':'show',
+ 'showblocks':'showblocks',
+ 'showaligned':'showaligned',
+ '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')],
+ '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')],
+ 'sandwichtop2':[_('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],
+ '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],
+ 'wait':[1],
+ 'write':[_('text'), 32]}
+
+#
+# Blocks that can interchange strings and numbers for their arguments
+#
+STRING_OR_NUMBER_ARGS = ['plus2', 'equal2', 'less2', 'greater2', 'box',
+ 'template1x1', 'template1x2', 'template2x1', 'list',
+ 'template2x2', 'template1x1a', 'templatelist', 'nop',
+ 'print', 'stack', 'hat', 'addturtle', 'myfunc',
+ 'myfunc1arg', 'myfunc2arg', 'myfunc3arg', 'comment',
+ 'sandwichtop', 'sandwichtop2', 'userdefined',
+ 'userdefined2args', 'userdefined3args', 'storein']
+
+CONTENT_ARGS = ['show', 'showaligned', 'push', 'storein', 'storeinbox1',
+ 'storeinbox2']
+
+#
+# Status blocks
+#
+
+MEDIA_SHAPES = ['audiooff', 'audioon', 'audiosmall',
+ 'journaloff', 'journalon', 'journalsmall',
+ 'descriptionoff', 'descriptionon', 'descriptionsmall',
+ 'pythonoff', 'pythonon', 'pythonsmall',
+ 'list', '1x1', '1x1a', '2x1', '1x2', '2x2']
+
+OVERLAY_SHAPES = ['Cartesian', 'Cartesian_labeled', 'polar']
+
+STATUS_SHAPES = ['status', 'info', 'nostack', 'noinput', 'emptyheap',
+ 'emptybox', 'nomedia', 'nocode', 'overflowerror', 'negroot',
+ 'syntaxerror', 'nofile', 'nojournal', 'zerodivide']
+
+#
+# Emulate Sugar toolbar when running from outside of Sugar
+#
+TOOLBAR_SHAPES = ['hideshowoff', 'eraseron', 'run-fastoff',
+ 'run-slowoff', 'debugoff', 'stopiton']
+
+#
+# Legacy names
+#
+OLD_NAMES = {'product':'product2', 'storeinbox':'storein', 'minus':'minus2',
+ 'division':'division2', 'plus':'plus2', 'and':'and2', 'or':'or2',
+ 'less':'less2', 'greater':'greater2', 'equal':'equal2',
+ 'remainder':'remainder2', 'identity':'identity2',
+ 'division':'division2', 'audiooff':'audio', 'endfill':'stopfill',
+ 'descriptionoff':'description','template3':'templatelist',
+ 'template1':'template1x1', 'template2':'template2x1',
+ 'template6':'template1x2', 'template7':'template2x2',
+ 'template4':'template1x1a', 'hres':'width', 'vres':'height' }
+
+#
+# Define the relative size and postion of media objects
+# (w, h, x, y, dx, dy)
+#
+TITLEXY = (0.9375, 0.875)
+
+#
+# Relative placement of portfolio objects (used by depreciated blocks)
+#
+TEMPLATES = {'t1x1': (0.5, 0.5, 0.0625, 0.125, 1.05, 0),
+ 't2z1': (0.5, 0.5, 0.0625, 0.125, 1.05, 1.05),
+ 't1x2': (0.45, 0.45, 0.0625, 0.125, 1.05, 1.05),
+ 't2x2': (0.45, 0.45, 0.0625, 0.125, 1.05, 1.05),
+ 't1x1a': (0.9, 0.9, 0.0625, 0.125, 0, 0),
+ 'bullet': (1, 1, 0.0625, 0.125, 0, 0.1),
+ 'insertimage': (0.333, 0.333)}
+
+#
+# Names for blocks without names for popup help
+#
+SPECIAL_NAMES = {
+ 'audio':_('audio'),
+ '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'),
+ '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)"),
+ '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"),
+ '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"),
+ 'remainder2':_("modular (remainder) operator"),
+ 'repeat':_("loops specified number of times"),
+ 'resistance':_("sensor input resistance"),
+ 'restore':_("restores most recent blocks from trash"),
+ 'restoreall':_("restore all blocks from trash"),
+ '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"),
+ 'sandwichtop2':_("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"),
+ '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."),
+ 'shade':_("holds current pen shade"),
+ 'show':_("draws text or show media from the Journal"),
+ 'showblocks':_("restores hidden blocks"),
+ '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"),
+ '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}]
+NOISE_KEYS = ['Shift_L', 'Shift_R', 'Control_L', 'Caps_Lock', 'Pause',
+ 'Alt_L', 'Alt_R', 'KP_Enter', 'ISO_Level3_Shift', 'KP_Divide',
+ 'Escape', 'Return', 'KP_Page_Up', 'Up', 'Down', 'Menu',
+ 'Left', 'Right', 'KP_Home', 'KP_End', 'KP_Up', 'Super_L',
+ 'KP_Down', 'KP_Left', 'KP_Right', 'KP_Page_Down', 'Scroll_Lock',
+ 'Page_Down', 'Page_Up']
+WHITE_SPACE = ['space','Tab']
+
+CURSOR = '█'
+RETURN = '⏎'
+
+
+#
+# Macros (groups of blocks)
+#
+MACROS = {
+ 'until':
+ [[0, 'forever', 0, 0, [None, 2, 1]],
+ [1, 'vspace', 0, 0, [0, None]],
+ [2, 'ifelse', 0, 0, [0, None, 3, None, None]],
+ [3, 'vspace', 0, 0, [2, 4]],
+ [4, 'stopstack', 0, 0, [3, None]]],
+ 'while':
+ [[0, 'forever', 0, 0, [None, 2, 1]],
+ [1, 'vspace', 0, 0, [0, None]],
+ [2, 'ifelse', 0, 0, [0, None, 3, 4, None]],
+ [3, 'vspace', 0, 0, [2, None]],
+ [4, 'stopstack', 0, 0, [2, None]]],
+ 'kbinput':
+ [[0, 'forever', 0, 0, [None, 1, None]],
+ [1, 'kbinput', 0, 0, [0, 2]],
+ [2, 'vspace', 0, 0, [1, 3]],
+ [3, 'if', 0, 0, [2, 4, 7, 8]],
+ [4, 'greater2', 0, 0, [3, 5, 6, None]],
+ [5, 'keyboard', 0, 0, [4, None]],
+ [6, ['number', '0'], 0, 0, [4, None]],
+ [7, 'stopstack', 0, 0, [3, None]],
+ [8, 'vspace', 0, 0, [3, 9]],
+ [9, 'wait', 0, 0, [8, 10, None]],
+ [10, ['number', '1'], 0, 0, [9, None]]],
+ 'picturelist':
+ [[0, 'sandwichtop', 0, 0, [None, 1, 2]],
+ [1, ['string', _('bulleted list')], 0, 0, [0, None]],
+ [2, 'setxy', 0, 0, [0, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'setscale', 0, 0, [2, 6, 7]],
+ [6, ['number', '100'], 0, 0, [5, None]],
+ [7, 'show', 0, 0, [5, 8, 9]],
+ [8, ['string',_('Title')], 0, 0, [7, None]],
+ [9, 'setxy', 0, 0, [7, 10, 11, 12]],
+ [10, 'leftx', 0, 0, [9, None]],
+ [11, 'topy', 0, 0, [9, None]],
+ [12, 'setscale', 0, 0, [9, 13, 14]],
+ [13, ['number', '67'], 0, 0, [12, None]],
+ [14, 'list', 0, 0, [12, 15, 16, 17]],
+ [15, ['string','∙ '], 0, 0, [14, None]],
+ [16, ['string','∙ '], 0, 0, [14, None]],
+ [17, 'sandwichbottom', 0, 0, [14, None]]],
+ 'picture1x1a':
+ [[0, 'sandwichtop', 0, 0, [None, 1, 2]],
+ [1, ['string', _('picture')], 0, 0, [0, None]],
+ [2, 'setxy', 0, 0, [0, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'setscale', 0, 0, [2, 6, 7]],
+ [6, ['number', '100'], 0, 0, [5, None]],
+ [7, 'show', 0, 0, [5, 8, 9]],
+ [8, ['string',_('Title')], 0, 0, [7, None]],
+ [9, 'setscale', 0, 0, [7, 10, 11]],
+ [10, ['number', '90'], 0, 0, [9, None]],
+ [11, 'setxy', 0, 0, [9, 12, 13, 14]],
+ [12, 'leftx', 0, 0, [11, None]],
+ [13, 'topy', 0, 0, [11, None]],
+ [14, 'showaligned', 0, 0, [11, 15, 16]],
+ [15, 'journal', 0, 0, [14, None]],
+ [16, 'sandwichbottom', 0, 0, [14, None]]],
+ 'picture2x2':
+ [[0, 'sandwichtop', 0, 0, [None, 1, 2]],
+ [1, ['string', _('2×2 pictures')], 0, 0, [0, None]],
+ [2, 'setxy', 0, 0, [0, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'setscale', 0, 0, [2, 6, 7]],
+ [6, ['number', '100'], 0, 0, [5, None]],
+ [7, 'show', 0, 0, [5, 8, 9]],
+ [8, ['string',_('Title')], 0, 0, [7, None]],
+ [9, 'setscale', 0, 0, [7, 10, 11]],
+ [10, ['number', '35'], 0, 0, [9, None]],
+ [11, 'setxy', 0, 0, [9, 12, 13, 14]],
+ [12, 'leftx', 0, 0, [11, None]],
+ [13, 'topy', 0, 0, [11, None]],
+ [14, 'showaligned', 0, 0, [11, 15, 16]],
+ [15, 'journal', 0, 0, [14, None]],
+ [16, 'setxy', 0, 0, [14, 17, 18, 19]],
+ [17, 'rightx', 0, 0, [16, None]],
+ [18, 'topy', 0, 0, [16, None]],
+ [19, 'showaligned', 0, 0, [16, 20, 21]],
+ [20, 'journal', 0, 0, [19, None]],
+ [21, 'setxy', 0, 0, [19, 22, 23, 24]],
+ [22, 'leftx', 0, 0, [21, None]],
+ [23, 'bottomy', 0, 0, [21, None]],
+ [24, 'showaligned', 0, 0, [21, 25, 26]],
+ [25, 'journal', 0, 0, [24, None]],
+ [26, 'setxy', 0, 0, [24, 27, 28, 29]],
+ [27, 'rightx', 0, 0, [26, None]],
+ [28, 'bottomy', 0, 0, [26, None]],
+ [29, 'showaligned', 0, 0, [26, 30, 31]],
+ [30, 'journal', 0, 0, [29, None]],
+ [31, 'sandwichbottom', 0, 0, [29, None]]],
+ 'picture2x1':
+ [[0, 'sandwichtop', 0, 0, [None, 1, 2]],
+ [1, ['string', _('2×1 pictures')], 0, 0, [0, None]],
+ [2, 'setxy', 0, 0, [0, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'setscale', 0, 0, [2, 6, 7]],
+ [6, ['number', '100'], 0, 0, [5, None]],
+ [7, 'show', 0, 0, [5, 8, 9]],
+ [8, ['string',_('Title')], 0, 0, [7, None]],
+ [9, 'setscale', 0, 0, [7, 10, 11]],
+ [10, ['number', '35'], 0, 0, [9, None]],
+ [11, 'setxy', 0, 0, [9, 12, 13, 14]],
+ [12, 'leftx', 0, 0, [11, None]],
+ [13, 'topy', 0, 0, [11, None]],
+ [14, 'showaligned', 0, 0, [11, 15, 16]],
+ [15, 'journal', 0, 0, [14, None]],
+ [16, 'setxy', 0, 0, [14, 17, 18, 19]],
+ [17, 'leftx', 0, 0, [16, None]],
+ [18, 'bottomy', 0, 0, [16, None]],
+ [19, 'showaligned', 0, 0, [16, 20, 21]],
+ [20, 'description', 0, 0, [19, None]],
+ [21, 'setxy', 0, 0, [19, 22, 23, 24]],
+ [22, 'rightx', 0, 0, [21, None]],
+ [23, 'topy', 0, 0, [21, None]],
+ [24, 'showaligned', 0, 0, [21, 25, 26]],
+ [25, 'journal', 0, 0, [24, None]],
+ [26, 'setxy', 0, 0, [24, 27, 28, 29]],
+ [27, 'rightx', 0, 0, [26, None]],
+ [28, 'bottomy', 0, 0, [26, None]],
+ [29, 'showaligned', 0, 0, [26, 30, 31]],
+ [30, 'description', 0, 0, [29, None]],
+ [31, 'sandwichbottom', 0, 0, [29, None]]],
+ 'picture1x2':
+ [[0, 'sandwichtop', 0, 0, [None, 1, 2]],
+ [1, ['string', _('1×2 pictures')], 0, 0, [0, None]],
+ [2, 'setxy', 0, 0, [0, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'setscale', 0, 0, [2, 6, 7]],
+ [6, ['number', '100'], 0, 0, [5, None]],
+ [7, 'show', 0, 0, [5, 8, 9]],
+ [8, ['string',_('Title')], 0, 0, [7, None]],
+ [9, 'setscale', 0, 0, [7, 10, 11]],
+ [10, ['number', '35'], 0, 0, [9, None]],
+ [11, 'setxy', 0, 0, [9, 12, 13, 14]],
+ [12, 'leftx', 0, 0, [11, None]],
+ [13, 'topy', 0, 0, [11, None]],
+ [14, 'showaligned', 0, 0, [11, 15, 16]],
+ [15, 'journal', 0, 0, [14, None]],
+ [16, 'setxy', 0, 0, [14, 17, 18, 19]],
+ [17, 'rightx', 0, 0, [16, None]],
+ [18, 'topy', 0, 0, [16, None]],
+ [19, 'showaligned', 0, 0, [16, 20, 21]],
+ [20, 'description', 0, 0, [19, None]],
+ [21, 'setxy', 0, 0, [19, 22, 23, 24]],
+ [22, 'leftx', 0, 0, [21, None]],
+ [23, 'bottomy', 0, 0, [21, None]],
+ [24, 'showaligned', 0, 0, [21, 25, 26]],
+ [25, 'journal', 0, 0, [24, None]],
+ [26, 'setxy', 0, 0, [24, 27, 28, 29]],
+ [27, 'rightx', 0, 0, [26, None]],
+ [28, 'bottomy', 0, 0, [26, None]],
+ [29, 'showaligned', 0, 0, [26, 30, 31]],
+ [30, 'description', 0, 0, [29, None]],
+ [31, 'sandwichbottom', 0, 0, [29, None]]],
+ 'picture1x1':
+ [[0, 'sandwichtop', 0, 0, [None, 1, 2]],
+ [1, ['string', _('1×1 pictures')], 0, 0, [0, None]],
+ [2, 'setxy', 0, 0, [0, 3, 4, 5]],
+ [3, 'titlex', 0, 0, [2, None]],
+ [4, 'titley', 0, 0, [2, None]],
+ [5, 'setscale', 0, 0, [2, 6, 7]],
+ [6, ['number', '100'], 0, 0, [5, None]],
+ [7, 'show', 0, 0, [5, 8, 9]],
+ [8, ['string',_('Title')], 0, 0, [7, None]],
+ [9, 'setscale', 0, 0, [7, 10, 11]],
+ [10, ['number', '35'], 0, 0, [9, None]],
+ [11, 'setxy', 0, 0, [9, 12, 13, 14]],
+ [12, 'leftx', 0, 0, [11, None]],
+ [13, 'topy', 0, 0, [11, None]],
+ [14, 'showaligned', 0, 0, [11, 15, 16]],
+ [15, 'journal', 0, 0, [14, None]],
+ [16, 'setxy', 0, 0, [14, 17, 18, 19]],
+ [17, 'rightx', 0, 0, [16, None]],
+ [18, 'topy', 0, 0, [16, None]],
+ [19, 'showaligned', 0, 0, [16, 20, 21]],
+ [20, 'description', 0, 0, [19, None]],
+ [21, 'sandwichbottom', 0, 0, [19, None]]],
+ }
diff --git a/TurtleArt/taexporthtml.py b/TurtleArt/taexporthtml.py
new file mode 100644
index 0000000..47577f0
--- /dev/null
+++ b/TurtleArt/taexporthtml.py
@@ -0,0 +1,147 @@
+#Copyright (c) 2008-9, Walter Bender
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import os.path
+from tautils import data_to_string, save_picture, image_to_base64
+from gettext import gettext as _
+from cgi import escape
+
+def save_html(self, tw, embed_flag=True):
+ """ Either: Save canvas and code or pictures to HTML """
+ self.embed_images = embed_flag
+
+ # A dictionary to define the HTML wrappers around template elements
+ self.html_glue = {
+ 'doctype': "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 " + \
+ "Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n",
+ 'html': ("<html>\n", "</html>\n"),
+ 'html_svg': ("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n",
+ "</html>\n"),
+ 'head': ("<head>\n<!-- Created by Turtle Art -->\n", "</head>\n"),
+ 'meta': "<meta http-equiv=\"content-type\" content=\"text/html; " + \
+ "charset=UTF-8\"/>\n",
+ 'title': ("<title>", "</title>\n"),
+ 'style': ("<style type=\"text/css\">\n<!--\n", "-->\n</style>\n"),
+ 'body': ("<body>\n", "\n</body>\n"),
+ 'div': ("<div>\n", "</div>\n"),
+ 'slide': ("\n<a name=\"slide", "\"></a>\n"),
+ 'h1': ("<h1>", "</h1>\n"),
+ 'table': ("<table cellpadding=\"10\">\n", "</table>\n"),
+ 'tr': ("<tr>\n", "</tr>\n"),
+ 'td': ("<td valign=\"top\" width=\"400\" height=\"300\">\n",
+ "\n</td>\n"),
+ 'img': ("<img width=\"400\" height=\"300\" alt=\"Image\" " + \
+ "src=\"file://", ".png\" />\n"),
+ 'img2': ("<img alt=\"Image\" src=\"image", ".png\" />\n"),
+ 'img3': ("<img alt=\"Image\" src=\"file://", "\" />\n"),
+ 'ul': ("<table>\n", "</table>\n"),
+ 'li': ("<tr><td>", "</td></tr>\n") }
+
+ comment = "<!--\n\
+<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\" [\n\
+ <!ENTITY ns_svg \"http://www.w3.org/2000/svg\">\n\
+ <!ENTITY ns_xlink \"http://www.w3.org/1999/xlink\">\n\
+]>\n\
+-->\n"
+ if self.embed_images == True:
+ self.html_glue['img'] = ("<img width=\"400\" height=\"300\" alt="+ \
+ "\"Image\" src=\"data:image/png;base64,\n",
+ " \"/>\n")
+ self.html_glue['img2'] = ("<img alt=\"Image\" src=\"data:image/png;"+ \
+ "base64,\n", " \"/>\n")
+
+ """
+ If there are saved_pictures, put them into a .html; otherwise, save a
+ screendump and the turtle project code.
+ """
+ code = ""
+ 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:
+ f = open(p, "r")
+ imgdata = f.read()
+ f.close()
+ if p.endswith(('.svg')):
+ tmp = imgdata
+ else:
+ pixbuf = gtk.gdk.pixbuf_new_from_file(p)
+ imgdata = image_to_base64(pixbuf, tw.activity)
+ tmp = self.html_glue['img2'][0]
+ tmp += imgdata
+ tmp += self.html_glue['img2'][1]
+ else:
+ if p.endswith(('.svg')):
+ f = open(p, "r")
+ imgdata = f.read()
+ f.close()
+ tmp = imgdata
+ else:
+ tmp = self.html_glue['img3'][0]
+ tmp += p
+ tmp += self.html_glue['img3'][1]
+ code += tmp + \
+ self.html_glue['h1'][1] + \
+ self.html_glue['div'][1]
+ else:
+ if self.embed_images == True:
+ imgdata = image_to_base64(save_picture(self.tw.canvas), tw.activity)
+ 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]
+
+ 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]
+ if len(tw.saved_pictures) > 0:
+ if tw.saved_pictures[0].endswith(('.svg')):
+ header = self.html_glue['html_svg'][0]
+ style = comment
+
+ code = header + \
+ self.html_glue['head'][0] + \
+ self.html_glue['meta'] + \
+ self.html_glue['title'][0] + \
+ title + \
+ self.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
diff --git a/TurtleArt/taexportlogo.py b/TurtleArt/taexportlogo.py
new file mode 100644
index 0000000..7fde81c
--- /dev/null
+++ b/TurtleArt/taexportlogo.py
@@ -0,0 +1,353 @@
+#Copyright (c) 2008-10, 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.
+
+IGNORE = ["hideblocks", "showblocks", "fullscreen", "polar", "cartesian",
+ "sandwichbottom", "id"]
+
+import math
+from tautils import walk_stack
+try:
+ from sugar.datastore import datastore
+except:
+ pass
+
+def save_logo(tw):
+ """ We need to 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 = ""
+ stack_count = 0
+ show = 0
+
+ # These flags are used to trigger the prepending of additional procedures.
+ random = False
+ fillscreen = False
+ setcolor = False
+ setxy = False
+ pensize = False
+ setpensize = False
+ arc = False
+ heap = False
+ write = False
+ minus = False
+ division = False
+ image = False
+
+ """
+ Walk through the code, substituting UCB Logo for Turtle Art primitives.
+ """
+ for b in bs:
+ this_stack = ""
+ data = walk_stack(tw, b)
+ # 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
+ 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 += "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
+ else:
+ this_stack += d
+ 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 setxy: # Swap and round arguments
+ 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
+
+
diff --git a/TurtleArt/tagplay.py b/TurtleArt/tagplay.py
new file mode 100644
index 0000000..5c86f62
--- /dev/null
+++ b/TurtleArt/tagplay.py
@@ -0,0 +1,176 @@
+#Copyright (c) 2009, Walter Bender (on behalf of Sugar Labs)
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+"""
+Video and audio playback
+
+Based on code snippets from
+http://wiki.sugarlabs.org/go/Development_Team/Almanac/GStreamer
+"""
+import gtk
+import pygtk
+pygtk.require('2.0')
+import pygst
+pygst.require('0.10')
+import gst
+import gst.interfaces
+import gobject
+gobject.threads_init()
+
+try:
+ from sugar.datastore import datastore
+except ImportError:
+ pass
+
+class Gplay:
+
+ def __init__(self):
+ self.window = None
+ self.playing = False
+
+ self.player = gst.element_factory_make("playbin", "playbin")
+ xis = gst.element_factory_make("xvimagesink", "xvimagesink")
+ self.player.set_property("video-sink", xis)
+ bus = self.player.get_bus()
+ bus.enable_sync_message_emission()
+ bus.add_signal_watch()
+ self.SYNC_ID = bus.connect('sync-message::element', \
+ self._onSyncMessageCb)
+
+ def _onSyncMessageCb(self, bus, message):
+ if message.structure is None:
+ return True
+ if message.structure.get_name() == 'prepare-xwindow-id':
+ if self.window is None:
+ return True
+ self.window.set_sink(message.src)
+ message.src.set_property('force-aspect-ratio', True)
+ return True
+
+ def setFile(self, path):
+ uri = "file://" + str(path)
+ if (self.player.get_property('uri') == uri):
+ self.seek(gst.SECOND*0)
+ return
+
+ self.player.set_state(gst.STATE_READY)
+ self.player.set_property('uri', uri)
+ ext = uri[len(uri)-3:]
+ if (ext == "jpg"):
+ self.pause()
+ else:
+ self.play()
+
+ def queryPosition(self):
+ #"Returns a (position, duration) tuple"
+ try:
+ position, format = self.player.query_position(gst.FORMAT_TIME)
+ except:
+ position = gst.CLOCK_TIME_NONE
+
+ try:
+ duration, format = self.player.query_duration(gst.FORMAT_TIME)
+ except:
+ duration = gst.CLOCK_TIME_NONE
+ return (position, duration)
+
+ def seek(self, time):
+ event = gst.event_new_seek(1.0,\
+ gst.FORMAT_TIME,\
+ gst.SEEK_FLAG_FLUSH|gst.SEEK_FLAG_ACCURATE,\
+ gst.SEEK_TYPE_SET,\
+ time,\
+ gst.SEEK_TYPE_NONE, 0)
+ res = self.player.send_event(event)
+ if res:
+ self.player.set_new_stream_time(0L)
+
+ def pause(self):
+ self.playing = False
+ self.player.set_state(gst.STATE_PAUSED)
+
+ def play(self):
+ self.playing = True
+ self.player.set_state(gst.STATE_PLAYING)
+
+ def stop(self):
+ self.playing = False
+ self.player.set_state(gst.STATE_NULL)
+ # self.nextMovie()
+
+ def get_state(self, timeout=1):
+ return self.player.get_state(timeout=timeout)
+
+ def is_playing(self):
+ return self.playing
+
+class PlayVideoWindow(gtk.Window):
+ def __init__(self):
+ gtk.Window.__init__(self)
+ self.imagesink = None
+ self.unset_flags(gtk.DOUBLE_BUFFERED)
+ self.set_flags(gtk.APP_PAINTABLE)
+
+ def set_sink(self, sink):
+ if (self.imagesink != None):
+ assert self.window.xid
+ self.imagesink = None
+ del self.imagesink
+
+ self.imagesink = sink
+ if self.window is not None:
+ self.imagesink.set_xwindow_id(self.window.xid)
+
+def play_audio(lc, filepath):
+ print "loading audio id: " + filepath
+ if lc.gplay == None:
+ lc.gplay = Gplay()
+ lc.gplay.setFile("file:///" + filepath)
+
+def play_movie_from_file(lc, filepath, x, y, w, h):
+ if lc.gplay == None:
+ lc.gplay = Gplay()
+ # wait for current movie to stop playing
+ if lc.gplay.is_playing:
+ print "already playing..."
+ lc.gplay.setFile("file:///" + filepath)
+ if lc.gplay.window == None:
+ gplayWin = PlayVideoWindow()
+ lc.gplay.window = gplayWin
+ gplayWin.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+ gplayWin.set_decorated(False)
+ if lc.tw.running_sugar:
+ gplayWin.set_transient_for(lc.tw.activity)
+ # y position is too high for some reason (toolbox?) adding offset
+ gplayWin.move(x, y+108)
+ gplayWin.resize(w, h)
+ gplayWin.show_all()
+
+
+def stop_media(lc):
+ if lc.gplay == None:
+ return
+ lc.gplay.stop()
+ if lc.gplay.window != None:
+ # We need to destroy the video window
+ # print dir(lc.gplay.window)
+ lc.gplay.window.destroy()
+ lc.gplay = None
+
diff --git a/TurtleArt/tajail.py b/TurtleArt/tajail.py
new file mode 100644
index 0000000..091557b
--- /dev/null
+++ b/TurtleArt/tajail.py
@@ -0,0 +1,69 @@
+#Copyright (c) 2009-10, Walter Bender (on behalf of Sugar Labs)
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+# A naive approach to running myfunc in a jail
+import logging
+_logger = logging.getLogger('turtleart-activity')
+import traceback
+from time import *
+from math import *
+try:
+ from numpy import *
+except ImportError:
+ _logger.error("could not import numpy")
+
+def myfunc(f, args):
+ # check to make sure no import calls are made
+ if len(args) == 1:
+ myf = "def f(x): return " + f.replace("import","")
+ userdefined = {}
+ try:
+ exec myf in globals(), userdefined
+ return userdefined.values()[0](args[0])
+ except:
+ traceback.print_exc()
+ return None
+ elif len(args) == 2:
+ myf = "def f(x,y): return " + f.replace("import","")
+ userdefined = {}
+ try:
+ exec myf in globals(), userdefined
+ return userdefined.values()[0](args[0],args[1])
+ except:
+ traceback.print_exc()
+ return None
+ elif len(args) == 3:
+ myf = "def f(x,y,z): return " + f.replace("import","")
+ userdefined = {}
+ try:
+ exec myf in globals(), userdefined
+ return userdefined.values()[0](args[0],args[1],args[2])
+ except:
+ traceback.print_exc()
+ return None
+
+def myfunc_import(lc, f, x):
+ userdefined = {}
+ try:
+ exec f in globals(), userdefined
+ return userdefined['myblock'](lc, x)
+ except:
+ traceback.print_exc()
+ return None
diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py
new file mode 100644
index 0000000..318bb7e
--- /dev/null
+++ b/TurtleArt/talogo.py
@@ -0,0 +1,1366 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2007-8, Playful Invention Company.
+#Copyright (c) 2008-10, Walter Bender
+#Copyright (c) 2008-10, Raúl Gutiérrez Segalés
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import gtk
+from time import clock
+from math import sqrt
+from random import uniform
+from operator import isNumberType
+from UserDict import UserDict
+try:
+ from sugar.datastore import datastore
+except:
+ pass
+
+from taconstants import PALETTES, PALETTE_NAMES, TAB_LAYER, BLACK, WHITE
+from tagplay import play_audio, play_movie_from_file, stop_media
+from tajail import myfunc, myfunc_import
+from tautils import get_pixbuf_from_journal, movie_media_type, convert, \
+ audio_media_type, text_media_type, round_int, chr_to_ord, \
+ strtype
+from gettext import gettext as _
+
+class noKeyError(UserDict):
+ __missing__ = lambda x, y: 0
+
+class symbol:
+ def __init__(self, name):
+ self.name = name
+ self.nargs = None
+ self.fcn = None
+
+ def __str__(self):
+ return self.name
+ def __repr__(self):
+ return '#' + self.name
+
+class logoerror(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+# Utility functions
+
+def numtype(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 str_to_num(x):
+ """ Try to comvert a string to a number """
+ xx = convert(x, float)
+ if type(xx) is float:
+ return xx
+ else:
+ xx, xflag = chr_to_ord(x)
+ if xflag:
+ return xx
+ else:
+ raise logoerror("#syntaxerror")
+
+def taand(x, y):
+ """ Logical and """
+ return x&y
+
+def taor(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 str_to_num(x) / str_to_num(y)
+ except ZeroDivisionError:
+ raise logoerror("#zerodivide")
+ except ValueError:
+ raise logoerror("#syntaxerror")
+
+def taequal(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 str_to_num(x) == str_to_num(y)
+ except ValueError:
+ raise logoerror("#syntaxerror")
+
+def taless(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 str_to_num(x) < str_to_num(y)
+ except TypeError:
+ raise logoerror("#syntaxerror")
+
+def tamore(x, y):
+ """ Compare numbers and strings """
+ return taless(y, x)
+
+def taplus(x, y):
+ """ Add numbers, concat strings """
+ if numtype(x) and numtype(y):
+ return(x+y)
+ else:
+ if numtype(x):
+ xx = str(round_int(x))
+ else:
+ xx = str(x)
+ if numtype(y):
+ yy = str(round_int(y))
+ else:
+ yy = str(y)
+ return(xx+yy)
+
+def taminus(x, y):
+ """ Numerical subtraction """
+ if numtype(x) and numtype(y):
+ return(x-y)
+ try:
+ return str_to_num(x) - str_to_num(y)
+ except TypeError:
+ raise logoerror("#syntaxerror")
+
+def taproduct(x, y):
+ """ Numerical multiplication """
+ if numtype(x) and numtype(y):
+ return(x*y)
+ try:
+ return str_to_num(x) * str_to_num(y)
+ except TypeError:
+ raise logoerror("#syntaxerror")
+
+def tamod(x, y):
+ """ Numerical mod """
+ if numtype(x) and numtype(y):
+ return(x%y)
+ try:
+ return str_to_num(x) % str_to_num(y)
+ except TypeError:
+ raise logoerror("#syntaxerror")
+ except ValueError:
+ raise logoerror("#syntaxerror")
+
+def tasqrt(x):
+ """ Square root """
+ if numtype(x):
+ if x < 0:
+ raise logoerror("#negroot")
+ return sqrt(x)
+ try:
+ return sqrt(str_to_num(x))
+ except ValueError:
+ raise logoerror("#negroot")
+ except TypeError:
+ raise logoerror("#syntaxerror")
+
+def tarandom(x, y):
+ """ Random integer """
+ if numtype(x) and numtype(y):
+ return(int(uniform(x, y)))
+ xx, xflag = chr_to_ord(x)
+ yy, yflag = chr_to_ord(y)
+ print xx, xflag, yy, yflag
+ if xflag and yflag:
+ return chr(int(uniform(xx, yy)))
+ if not xflag:
+ xx = str_to_num(x)
+ if not yflag:
+ yy = str_to_num(y)
+ try:
+ return(int(uniform(xx, yy)))
+ except TypeError:
+ raise logoerror("#syntaxerror")
+
+def identity(x):
+ """ Identity function """
+ return(x)
+
+def stop_logo(tw):
+ """ Stop logo is called from the Stop button on the toolbar """
+ tw.step_time = 0
+ tw.lc.step = just_stop()
+ tw.turtles.show_all()
+
+def just_stop():
+ """ yield False to stop stack """
+ yield False
+
+def millis():
+ """ Current time in milliseconds """
+ return int(clock()*1000)
+
+"""
+A class for parsing Logo Code
+"""
+class LogoCode:
+ def __init__(self, tw):
+
+ self.tw = tw
+ self.oblist = {}
+
+ DEFPRIM = {
+ '(':[1, lambda self, x: self.prim_opar(x)],
+ 'and':[2, lambda self, x, y: taand(x, y)],
+ 'arc':[2, lambda self, x, y: self.tw.canvas.arc(x, y)],
+ 'back':[1, lambda self, x: self.tw.canvas.forward(-x)],
+ 'black':[0, lambda self: BLACK],
+ 'blue':[0, lambda self: 70],
+ 'bpos':[0, lambda self: -self.tw.canvas.height/(self.tw.coord_scale*2)],
+ 'boty':[0, lambda self: self.tw.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: 50],
+ 'define':[2, self.prim_define],
+ 'division':[2, lambda self, x, y: careful_divide(x, y)],
+ 'equal?':[2, lambda self,x, y: taequal(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.tw.canvas.forward(x)],
+ 'fullscreen':[0, lambda self: self.tw.set_fullscreen()],
+ 'greater?':[2, lambda self, x, y: tamore(x, y)],
+ 'green':[0, lambda self: 30],
+ 'heading':[0, lambda self: self.tw.canvas.heading],
+ 'hideblocks':[0, lambda self: self.tw.hideblocks()],
+ 'hres':[0, lambda self: self.tw.canvas.width/self.tw.coord_scale],
+ '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(x, False)],
+ 'kbinput':[0, lambda self: self.prim_kbinput()],
+ 'keyboard':[0, lambda self: self.keyboard],
+ 'left':[1, lambda self, x: self.tw.canvas.right(-x)],
+ 'leftx':[0, lambda self: self.tw.leftx],
+ 'lpos':[0, lambda self: -self.tw.canvas.width/(self.tw.coord_scale*2)],
+ 'less?':[2, lambda self, x, y: taless(x, y)],
+ 'minus':[2, lambda self, x, y: taminus(x, y)],
+ 'mod':[2, lambda self, x, y: tamod(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: 10],
+ 'or':[2, lambda self, x, y: taor(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)],
+ 'plus':[2, lambda self, x, y: taplus(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: taproduct(x, y)],
+ 'purple':[0, lambda self: 90],
+ 'push':[1, lambda self, x: self.prim_push(x)],
+ 'random':[2, lambda self, x, y: tarandom(x, y)],
+ 'red':[0, lambda self: 0],
+ 'repeat':[2, self.prim_repeat, True],
+ 'right':[1, lambda self, x: self.tw.canvas.right(x)],
+ 'rightx':[0, lambda self: self.tw.rightx],
+ 'rpos':[0, lambda self: self.tw.canvas.width/(self.tw.coord_scale*2)],
+ 'savepix':[1, lambda self, x: self.save_picture(x)],
+ 'savesvg':[1, lambda self, x: self.save_svg(x)],
+ 'scale':[0, lambda self: self.scale],
+ 'setcolor':[1, lambda self, x: self.tw.canvas.setcolor(x)],
+ 'setgray':[1, lambda self, x: self.tw.canvas.setgray(x)],
+ 'seth':[1, lambda self, x: self.tw.canvas.seth(x)],
+ 'setpensize':[1, lambda self, x: self.tw.canvas.setpensize(x)],
+ 'setscale':[1, lambda self, x: self.set_scale(x)],
+ 'setshade':[1, lambda self, x: 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)],
+ 'setxy':[2, lambda self, x, y: self.tw.canvas.setxy(x, y)],
+ '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()],
+ 'sound':[1, lambda self, x: self.play_sound(x)],
+ 'sqrt':[1, lambda self, x: tasqrt(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: self.tw.titlex],
+ 'titley':[0, lambda self: self.tw.titley],
+ 'topy':[0, lambda self: self.tw.topy],
+ 'tpos':[0, lambda self: self.tw.canvas.height/(self.tw.coord_scale*2)],
+ '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_movie(x)],
+ 'vres':[0, lambda self: self.tw.canvas.height/self.tw.coord_scale],
+ 'wait':[1, self.prim_wait, True],
+ # 'while':[2, self.prim_while, 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: 20]}
+
+ for p in iter(DEFPRIM):
+ if len(DEFPRIM[p]) == 2:
+ self.defprim(p, DEFPRIM[p][0], DEFPRIM[p][1])
+ else:
+ self.defprim(p, DEFPRIM[p][0], DEFPRIM[p][1], DEFPRIM[p][2])
+
+ self.symtype = type(self.intern('print'))
+ self.listtype = type([])
+ self.symnothing = self.intern('%nothing%')
+ self.symopar = self.intern('(')
+ self.iline = None
+ self.cfun = None
+ self.arglist = None
+ self.ufun = None
+ self.procstop = False
+ self.running = False
+ self.istack = []
+ self.stacks = {}
+ self.boxes = {'box1': 0, 'box2': 0}
+ self.heap = []
+ self.iresults = None
+ self.step = None
+
+ self.keyboard = 0
+ self.trace = 0
+ self.gplay = None
+ self.ag = None
+ self.filepath = 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 = 33
+
+ def defprim(self, name, args, fcn, rprim=False):
+ """ Define the primitives associated with the blocks """
+ sym = self.intern(name)
+ sym.nargs, sym.fcn = args, fcn
+ sym.rprim = rprim
+
+ def intern(self, string):
+ """ Add any new objects to the symbol list. """
+ if string in self.oblist:
+ return self.oblist[string]
+ sym = symbol(string)
+ self.oblist[string] = sym
+ return sym
+
+ def run_blocks(self, blk, blocks, run_flag):
+ """ Given a block to run... """
+ for k in self.stacks.keys():
+ self.stacks[k] = None
+ self.stacks['stack1'] = None
+ self.stacks['stack2'] = None
+ self.tw.saving_svg = False
+
+ for b in blocks:
+ b.unhighlight()
+ if b.name == 'hat1':
+ code = self.blocks_to_code(b)
+ self.stacks['stack1'] = self.readline(code)
+ if b.name == 'hat2':
+ code = self.blocks_to_code(b)
+ self.stacks['stack2'] = self.readline(code)
+ if b.name == 'hat':
+ if b.connections[1] is not None:
+ code = self.blocks_to_code(b)
+ x = b.connections[1].values[0]
+ if type(convert(x, float, False)) == float:
+ if int(float(x)) == x:
+ x = int(x)
+ self.stacks['stack3'+str(x)] = self.readline(code)
+
+ code = self.blocks_to_code(blk)
+ if run_flag:
+ print "running code: %s" % (code)
+ self.setup_cmd(code)
+ if self.tw.hide is False:
+ self.tw.display_coordinates()
+ else:
+ return code
+
+ def blocks_to_code(self, blk):
+ """ Convert a stack of blocks to pseudocode. """
+ if blk is None:
+ return ['%nothing%', '%nothing%']
+ code = []
+ dock = blk.docks[0]
+ if len(dock)>4: # There could be a '(', ')', '[' or ']'.
+ code.append(dock[4])
+ if blk.name == 'savesvg':
+ self.tw.saving_svg = True
+ if blk.primitive is not None: # make a tuple (prim, blk)
+ code.append((blk.primitive, self.tw.block_list.list.index(blk)))
+ elif len(blk.values)>0: # Extract the value from content blocks.
+ if blk.name == 'number':
+ try:
+ code.append(float(blk.values[0]))
+ except ValueError:
+ code.append(float(ord(blk.values[0][0])))
+ elif blk.name == 'string' or blk.name == 'title':
+ 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':
+ if blk.values[0] is not None:
+ code.append('#sdescr_'+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')
+ else:
+ return ['%nothing%']
+ else:
+ return ['%nothing%']
+ if blk.connections is not None and len(blk.connections) > 0:
+ for i in range(1, len(blk.connections)):
+ b = blk.connections[i]
+ dock = blk.docks[i]
+ if len(dock)>4: # There could be a '(', ')', '[' or ']'.
+ for c in dock[4]:
+ code.append(c)
+ if b is not None:
+ code.extend(self.blocks_to_code(b))
+ elif blk.docks[i][0] not in ['flow', 'unavailable']:
+ code.append('%nothing%')
+ return code
+
+ def setup_cmd(self, string):
+ """ Execute the psuedocode. """
+ self.tw.active_turtle.hide() # Hide the turtle while we are running.
+ self.procstop = False
+ blklist = self.readline(string)
+ self.step = self.start_eval(blklist)
+
+ """
+ Convert the pseudocode into a list of commands.
+ The block associated with the command is stored as the second element
+ in a tuple, e.g., (#forward, 16)
+ """
+ def readline(self, line):
+ res = []
+ while line:
+ token = line.pop(0)
+ bindex = None
+ if type(token) == tuple:
+ (token, bindex) = token
+ if isNumberType(token):
+ res.append(token)
+ elif token.isdigit():
+ res.append(float(token))
+ elif token[0] == '-' and token[1:].isdigit():
+ res.append(-float(token[1:]))
+ elif token[0] == '"':
+ res.append(token[1:])
+ elif token[0:2] == "#s":
+ res.append(token[2:])
+ elif token == '[':
+ res.append(self.readline(line))
+ elif token == ']':
+ return res
+ elif bindex is None or type(bindex) is not int:
+ res.append(self.intern(token))
+ else:
+ res.append((self.intern(token), bindex))
+ return res
+
+ def start_eval(self, blklist):
+ """ Step through the list. """
+ if self.tw.running_sugar:
+ self.tw.activity.stop_turtle_button.set_icon("stopiton")
+ elif self.tw.interactive_mode:
+ self.tw.toolbar_shapes['stopiton'].set_layer(TAB_LAYER)
+ self.running = True
+ self.icall(self.evline, blklist)
+ yield True
+ if self.tw.running_sugar:
+ self.tw.activity.stop_turtle_button.set_icon("stopitoff")
+ elif self.tw.interactive_mode:
+ self.tw.toolbar_shapes['stopiton'].hide()
+ yield False
+ self.running = False
+
+ def icall(self, fcn, *args):
+ """ Add a function and its arguments to the program stack. """
+ self.istack.append(self.step)
+ self.step = fcn(*(args))
+
+ def evline(self, blklist):
+ """ Evaluate a line of code from the list. """
+ oldiline = self.iline
+ self.iline = blklist[:]
+ self.arglist = None
+ while self.iline:
+ token = self.iline[0]
+ bindex = None
+ if type(token) == tuple:
+ (token, bindex) = self.iline[0]
+
+ # If the blocks are visible, highlight the current block.
+ if not self.tw.hide and bindex is not None:
+ self.tw.block_list.list[bindex].highlight()
+
+ # In debugging modes, we pause between steps and show the turtle.
+ if self.tw.step_time > 0:
+ self.tw.active_turtle.show()
+ endtime = millis()+self.an_int(self.tw.step_time)*100
+ while millis()<endtime:
+ yield True
+ self.tw.active_turtle.hide()
+
+ # 'Stand-alone' booleans are handled here.
+ if token == self.symopar:
+ token = self.iline[1]
+ if type(token) == tuple:
+ (token, bindex) = self.iline[1]
+
+ # Process the token and any arguments.
+ self.icall(self.eval)
+ yield True
+
+ # Time to unhighlight the current block.
+ if not self.tw.hide and bindex is not None:
+ self.tw.block_list.list[bindex].unhighlight()
+
+ if self.procstop:
+ break
+ if self.iresult == None:
+ continue
+
+ if bindex is not None:
+ self.tw.block_list.list[bindex].highlight()
+ raise logoerror(str(self.iresult))
+ self.iline = oldiline
+ self.ireturn()
+ if self.tw.hide is False and self.tw.step_time > 0:
+ self.tw.display_coordinates()
+ yield True
+
+ def eval(self):
+ """ Evaluate the next token on the line of code we are processing. """
+ token = self.iline.pop(0)
+ bindex = None
+ if type(token) == tuple:
+ (token, bindex) = token
+
+ # Either we are processing a symbol or a value.
+ if type(token) == self.symtype:
+ # We highlight blocks here in case an error occurs...
+ # print "> ", token
+ if not self.tw.hide and bindex is not None:
+ self.tw.block_list.list[bindex].highlight()
+ self.icall(self.evalsym, token)
+ yield True
+ # and unhighlight if everything was OK.
+ if not self.tw.hide and bindex is not None:
+ self.tw.block_list.list[bindex].unhighlight()
+ res = self.iresult
+ else:
+ # print ": ", token
+ res = token
+
+ self.ireturn(res)
+ yield True
+
+ def evalsym(self, token):
+ """ Process primitive associated with symbol token """
+ self.debug_trace(token)
+ self.undefined_check(token)
+ oldcfun, oldarglist = self.cfun, self.arglist
+ self.cfun, self.arglist = token, []
+
+ if token.nargs == None:
+ raise logoerror("#noinput")
+ for i in range(token.nargs):
+ self.no_args_check()
+ self.icall(self.eval)
+ yield True
+ self.arglist.append(self.iresult)
+ if self.cfun.rprim:
+ if type(self.cfun.fcn) == self.listtype:
+ print "evalsym rprim list: ", token
+ self.icall(self.ufuncall, self.cfun.fcn)
+ yield True
+ else:
+ # print "evalsym rprim: ", token
+ 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)
+ yield True
+
+ def ufuncall(self, body):
+ """ ufuncall """
+ self.ijmp(self.evline, body)
+ yield True
+
+ def doevalstep(self):
+ """ evaluate one step """
+ starttime = millis()
+ try:
+ while (millis()-starttime)<120:
+ try:
+ if self.step is not None:
+ self.step.next()
+ else:
+ return False
+ except StopIteration:
+ self.tw.turtles.show_all()
+ return False
+ except logoerror, e:
+ self.tw.showlabel('syntaxerror', str(e)[1:-1])
+ self.tw.turtles.show_all()
+ return False
+ return True
+
+ def ireturn(self, res=None):
+ """ return value """
+ self.step = self.istack.pop()
+ self.iresult = res
+
+ def ijmp(self, fcn, *args):
+ """ ijmp """
+ self.step = fcn(*(args))
+
+ def debug_trace(self, token):
+ """ Display debugging information """
+ if self.trace:
+ if token.name in PALETTES[PALETTE_NAMES.index('turtle')]:
+ my_string = "%s\n%s=%d\n%s=%d\n%s=%d\n%s=%d" % \
+ (token.name, _('xcor'), int(self.tw.canvas.xcor),
+ _('ycor'), int(self.tw.canvas.ycor), _('heading'),
+ int(self.tw.canvas.heading), _('scale'), int(self.scale))
+ elif token.name in PALETTES[PALETTE_NAMES.index('pen')]:
+ if self.tw.canvas.pendown:
+ penstatus = _('pen down')
+ else:
+ penstatus = _('pen up')
+ my_string = "%s\n%s\n%s=%d\n%s=%d\n%s=%.1f" % \
+ (token.name, penstatus, _('color'),
+ int(self.tw.canvas.color), _('shade'),
+ int(self.tw.canvas.shade), _('pen size'),
+ self.tw.canvas.pensize)
+ else:
+ my_string = "%s\n" % (token.name)
+ for k, v in self.boxes.iteritems():
+ my_string += "%s: %s\n" % (k, str(v))
+ self.tw.showlabel('info', my_string)
+ return
+
+ def undefined_check(self, token):
+ """ Make sure token has a definition """
+ if token.fcn is not None:
+ return False
+ if token.name == '%nothing%':
+ errormsg = ''
+ else:
+ errormsg = "%s %s" % (_("I don't know how to"), _(token.name))
+ raise logoerror(errormsg)
+
+ def no_args_check(self):
+ """ Missing argument ? """
+ if self.iline and self.iline[0] is not self.symnothing:
+ return
+ raise logoerror("#noinput")
+
+ #
+ # Primitives
+ #
+
+ def prim_clear(self):
+ """ Clear screen """
+ stop_media(self)
+ self.tw.canvas.clearscreen()
+
+ 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 = millis()+self.an_int(time*1000)
+ while millis()<endtime:
+ yield True
+ self.tw.active_turtle.hide()
+ self.ireturn()
+ yield True
+
+ def prim_repeat(self, num, blklist):
+ """ Repeat list num times. """
+ num = self.an_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 """
+ y = myfunc(f, x)
+ if y == None:
+ stop_logo(self.tw)
+ raise logoerror("#syntaxerror")
+ else:
+ return y
+
+ 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
+
+ def prim_define(self, name, body):
+ """ Define a primitive """
+ if type(name) is not self.symtype:
+ name = self.intern(name)
+ name.nargs, name.fcn = 0, body
+ name.rprim = True
+
+ def prim_stack(self, x):
+ """ Process a named stack """
+ if type(convert(x, float, False)) == float:
+ if int(float(x)) == x:
+ x = int(x)
+ if (not self.stacks.has_key('stack3'+str(x))) 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', self.heap)
+
+ def an_int(self, n):
+ """ Raise an error if n doesn't convert to int. """
+ if type(n) == int:
+ return n
+ elif type(n) == float:
+ return int(n)
+ elif type(n) == str:
+ return int(ord(n[0]))
+ else:
+ 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:
+ raise logoerror("#emptybox")
+
+ def prim_myblock(self, x):
+ """ Run Python code imported from Journal """
+ if self.tw.myblock is not None:
+ try:
+ if len(x) == 1:
+ y = myfunc_import(self, self.tw.myblock, x[0])
+ else:
+ y = myfunc_import(self, self.tw.myblock, x)
+ except:
+ raise logoerror("#nocode")
+ else:
+ raise logoerror("#nocode")
+ return
+
+ def prim_print(self, n, flag):
+ """ Print 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_':
+ try:
+ if self.tw.running_sugar:
+ dsobject = datastore.get(n[6:])
+ self.tw.showlabel('status', dsobject.metadata['title'])
+ dsobject.destroy()
+ else:
+ self.tw.showlabel('status', n[6:])
+ except:
+ 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', round_int(n))
+
+ 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:
+ self.keyboard = 0
+ self.tw.keypress = ""
+
+ def prim_setbox(self, name, x, val):
+ """ Define value of named box """
+ if x is None:
+ self.boxes[name] = val
+ else:
+ if type(convert(x, float, False)) == float:
+ if int(float(x)) == x:
+ x = int(x)
+ self.boxes[name+str(x)] = val
+
+ def prim_push(self, val):
+ """ Push value onto FILO """
+ self.heap.append(val)
+
+ def prim_pop(self):
+ """ Pop value off of FILO """
+ try:
+ return self.heap.pop(-1)
+ except:
+ raise logoerror ("#emptyheap")
+
+ 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)
+ 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 show(self, string, center=False):
+ """ Show is the general-purpose media-rendering block. """
+ # convert from Turtle coordinates to screen coordinates
+ x = self.tw.canvas.width/2+int(self.tw.canvas.xcor)
+ y = self.tw.canvas.height/2-int(self.tw.canvas.ycor)
+ if type(string) == str or type(string) == unicode:
+ if string == "media_None":
+ pass
+ elif string[0:6] == 'media_':
+ self.insert_image(string, center)
+ elif string[0:6] == 'descr_':
+ self.insert_desc(string)
+ elif string[0:6] == 'audio_':
+ self.play_sound(string)
+ else:
+ 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)
+ 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, media, center):
+ """ Image only (at current x, y) """
+ w = (self.tw.canvas.width * self.scale)/100
+ h = (self.tw.canvas.height * self.scale)/100
+ # convert from Turtle coordinates to screen coordinates
+ x = self.tw.canvas.width/2+int(self.tw.canvas.xcor)
+ y = self.tw.canvas.height/2-int(self.tw.canvas.ycor)
+ if center:
+ x -= w/2
+ y -= h/2
+ if media[0:5] == 'media':
+ self.show_picture(media, x, y, w, h)
+
+ def insert_desc(self, media):
+ """ Description text only (at current x, y) """
+ w = (self.tw.canvas.width * self.scale)/100
+ h = (self.tw.canvas.height * self.scale)/100
+ # convert from Turtle coordinates to screen coordinates
+ x = self.tw.canvas.width/2+int(self.tw.canvas.xcor)
+ y = self.tw.canvas.height/2-int(self.tw.canvas.ycor)
+ if media[0:5] == 'descr':
+ self.show_description(media, x, y, w, h)
+
+ def play_sound(self, audio):
+ """ Sound file from Journal """
+ if audio == "" or audio[6:] == "":
+ raise logoerror("#nomedia")
+ if self.tw.running_sugar:
+ if audio[6:] != "None":
+ try:
+ dsobject = datastore.get(audio[6:])
+ play_audio(self, dsobject.file_path)
+ except:
+ print "Couldn't open id: " + str(audio[6:])
+ else:
+ play_audio(self, audio[6:])
+
+ def show_picture(self, media, x, y, w, h):
+ """ Image file from Journal """
+ if media == "" or media[6:] == "":
+ pass
+ elif media[6:] is not "None":
+ pixbuf = None
+ self.filepath = None
+ if self.tw.running_sugar:
+ try:
+ dsobject = datastore.get(media[6:])
+ if movie_media_type(dsobject.file_path):
+ play_movie_from_file(self, dsobject.file_path,
+ int(x), int(y), int(w), int(h))
+ else:
+ self.filepath = dsobject.file_path
+ pixbuf = get_pixbuf_from_journal(dsobject,
+ int(w), int(h))
+ dsobject.destroy()
+ except:
+ # Maybe it is a pathname instead.
+ try:
+ self.filepath = media[6:0]
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
+ media[6:], int(w), int(h))
+ except:
+ self.filepath = None
+ self.tw.showlabel('nojournal', media[6:])
+ print "Couldn't open Journal object %s" % (media[6:])
+ else:
+ try:
+ if movie_media_type(media):
+ play_movie_from_file(self, media[6:], int(x), int(y),
+ int(w), int(h))
+ else:
+ self.filepath = media[6:]
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
+ media[6:], int(w), int(h))
+ except:
+ self.filepath = None
+ self.tw.showlabel('nofile', media[6:])
+ print "Couldn't open media object %s" % (media[6:])
+ if pixbuf is not None:
+ self.tw.canvas.draw_pixbuf(pixbuf, 0, 0, int(x), int(y),
+ int(w), int(h),
+ self.filepath)
+
+ def show_description(self, media, x, y, w, h):
+ """ Description field from Journal """
+ if media == "" or media[6:] == "":
+ return
+ elif media[6:] != "None":
+ text = None
+ if self.tw.running_sugar:
+ try:
+ dsobject = datastore.get(media[6:])
+ # TODO: handle rtf, pdf, etc. (See #893)
+ if text_media_type(dsobject.file_path):
+ f = open(dsobject.file_path, 'r')
+ text = f.read()
+ f.close()
+ else:
+ text = str(dsobject.metadata['description'])
+ dsobject.destroy()
+ except:
+ print "no description in %s" % (media[6:])
+ else:
+ try:
+ f = open(media[6:], 'r')
+ text = f.read()
+ f.close()
+ except:
+ print "no text in %s?" % (media[6:])
+ if text is not None:
+ self.tw.canvas.draw_text(text, int(x), int(y),
+ self.body_height, int(w))
+
+ # Depreciated block methods
+
+ def draw_title(self, title, x, y):
+ """ slide title """
+ self.tw.canvas.draw_text(title, int(x), int(y),
+ self.title_height,
+ self.tw.canvas.width-x)
+
+ 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)
+ # 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)
+ self.show(media)
+ if self.tw.running_sugar:
+ x = 0
+ self.tw.canvas.setxy(x, y)
+ 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)
+ # 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)
+ self.show(media1)
+ x = 0
+ self.tw.canvas.setxy(x, y)
+ self.show(media2)
+ y = -self.title_height
+ if self.tw.running_sugar:
+ self.tw.canvas.setxy(x, y)
+ self.show(media2.replace("media_","descr_"))
+ x = -(self.tw.canvas.width/2) + xo
+ self.tw.canvas.setxy(x, y)
+ 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)
+ # 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)
+ 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)
+ # 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)
+ self.show(media1)
+ if self.tw.running_sugar:
+ x = 0
+ self.tw.canvas.setxy(x, y)
+ self.show(media1.replace("media_","descr_"))
+ y = -self.title_height
+ self.tw.canvas.setxy(x, y)
+ self.show(media2.replace("media_","descr_"))
+ x = -(self.tw.canvas.width/2) + xo
+ self.tw.canvas.setxy(x, y)
+ 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)
+ # 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)
+ self.show(media1)
+ x = 0
+ self.tw.canvas.setxy(x, y)
+ self.show(media2)
+ y = -self.title_height
+ self.tw.canvas.setxy(x, y)
+ self.show(media4)
+ x = -(self.tw.canvas.width/2) + xo
+ self.tw.canvas.setxy(x, y)
+ 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)
+ # 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)
+ 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)
diff --git a/TurtleArt/tamyblock.py b/TurtleArt/tamyblock.py
new file mode 100644
index 0000000..308ff42
--- /dev/null
+++ b/TurtleArt/tamyblock.py
@@ -0,0 +1,245 @@
+#Copyright (c) 2009-10, 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
+# TurtleGraphics tacanvas.py canvas tawindow.py
+# 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)
+#
+# Methods and data attributes Example
+# set_fullscreen(self) lc.tw.set_fullscreen()
+# Note: Hides the Sugar toolbar
+# set_cartesian(self, flag) lc.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)
+# Note: True will make the overlay visible;
+# False will make it invisible
+# hideshow_button(self, flag) lc.tw.hideshow_button()
+# Note: Toggles visibility of blocks and palettes
+# self.active_turtle lc.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)
+#
+# Methods and data attributes Example
+# clearscreen(self) lc.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)
+# Note: True will set the pen "down", enabling drawing;
+# False will set the pen "up"
+# forward(self, n) lc.tw.canvas.forward(100)
+# Note: Move the turtle forward 100 units
+# arc(self, a, r) lc.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)
+# Note: Set the turtle heading to 180
+# (towards the bottom of the screen)
+# self.heading lc.tw.canvas.heading
+# Note: The current heading
+# setpensize(self, n) lc.tw.canvas.setpensize(25)
+# Note: Set the turtle pensize to 25 units
+# self.pensize lc.tw.canvas.pensize
+# Note: The current pensize
+# setcolor(self, c) lc.tw.canvas.color(70)
+# Note: Set the pen color to 70 (blue)
+# self.color lc.tw.canvas.color
+# Note: The current pen color
+# setshade(self, s) lc.tw.canvas.shade(50)
+# Note: Set the pen shade to 50
+# self.shade lc.tw.canvas.shade
+# Note: The current pen shade
+# fillscreen(self, c, s) lc.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)
+# Note: Move the turtle to position (100, 100)
+# self.xcor lc.tw.canvas.xcor
+# Note: The current x coordinate of the turtle
+# (scaled to current units)
+# self.ycor lc.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)
+# 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
+# Note: See http://docs.python.org/library/math.html
+# from math import sin, pi sin(45*pi/180) returns sin of 45 (0.707)
+# 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
+# Note: See http://docs.python.org/tutorial/datastructures.html
+# data = lc.heap.pop(-1) pops data off the heap
+# Note: See http://docs.python.org/tutorial/datastructures.html
+#
+
+def myblock(lc, x):
+
+ ###########################################################################
+ #
+ # Set rgb color
+ #
+ ###########################################################################
+
+ # r = int(x[0])
+ # while r < 0:
+ # r += 256
+ # while r > 255:
+ # r -= 256
+ # g = int(x[1])
+ # while g < 0:
+ # g += 256
+ # while g > 255:
+ # g -= 256
+ # b = int(x[2])
+ # while b < 0:
+ # b += 256
+ # while b > 255:
+ # b -= 256
+ # rgb = "#%02x%02x%02x" % (r,g,b)
+ # lc.tw.canvas.fgcolor = lc.tw.canvas.cm.alloc_color(rgb)
+ # return
+
+ ###########################################################################
+ #
+ # Draw a dotted line of length x.
+ #
+ ###########################################################################
+
+ try: # make sure x is a number
+ x = float(x)
+ except ValueError:
+ return
+ if lc.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)
+ else:
+ lc.tw.canvas.forward(x)
+ return
+
+ ###########################################################################
+ #
+ # Push an uppercase version of a string onto the heap.
+ # Use a 'pop' block to use the new string.
+ #
+ ###########################################################################
+
+ # if type(x) != str:
+ # X = str(x).upper()
+ # else:
+ # X = x.upper()
+ # lc.heap.append(X)
+ # return
+
+ ###########################################################################
+ #
+ # 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 will pop will be seconds.
+ #
+ ###########################################################################
+
+ # lc.heap.append(localtime().tm_hour)
+ # lc.heap.append(localtime().tm_min)
+ # lc.heap.append(localtime().tm_sec)
+ # return
+
+ ###########################################################################
+ #
+ # Add a third dimension (gray) to the color model.
+ #
+ ###########################################################################
+
+ # val = 0.3 * lc.tw.rgb[0] + 0.6 * lc.tw.rgb[1] + 0.1 * lc.tw.rgb[2]
+ # if x != 100:
+ # x = int(x)%100
+ # r = int((val*(100-x) + lc.tw.rgb[0]*x)/100)
+ # g = int((val*(100-x) + lc.tw.rgb[1]*x)/100)
+ # b = int((val*(100-x) + lc.tw.rgb[2]*x)/100)
+ # reallocate current color
+ # rgb = "#%02x%02x%02x" % (r,g,b)
+ # lc.tw.canvas.fgcolor = lc.tw.canvas.cm.alloc_color(rgb)
+ # return
+
+ ###########################################################################
+ #
+ # Save an image named x to the Sugar Journal.
+ #
+ ###########################################################################
+
+ # lc.tw.save_as_image(str(x))
+ # return
+
+ ###########################################################################
+ #
+ # Push mouse event to stack
+ #
+ ###########################################################################
+
+ # if lc.tw.mouse_flag == 1:
+ # lc.heap.append(lc.tw.mouse_y)
+ # lc.heap.append(lc.tw.mouse_x)
+ # lc.heap.append(1) # mouse event
+ # lc.tw.mouseflag = 0
+ # else:
+ # lc.heap.append(0) # no mouse event
+ # return
diff --git a/TurtleArt/tasprite_factory.py b/TurtleArt/tasprite_factory.py
new file mode 100755
index 0000000..b7c0523
--- /dev/null
+++ b/TurtleArt/tasprite_factory.py
@@ -0,0 +1,1118 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#Copyright (c) 2009,10 Walter Bender
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import os
+from gettext import gettext as _
+
+class SVG:
+ def __init__(self):
+ self._x = 0
+ self._y = 0
+ self._min_x = 10000
+ self._min_y = 10000
+ self._max_x = -10000
+ self._max_y = -10000
+ self._width = 0
+ self._height = 0
+ self.docks = []
+ self._scale = 1
+ self._orientation = 0
+ self._radius = 8
+ self._stroke_width = 1
+ self._innie = [False]
+ self._outie = False
+ self._innie_x1 = (9-self._stroke_width)/2
+ self._innie_y1 = 3
+ self._innie_x2 = (9-self._stroke_width)/2
+ self._innie_y2 = (9-self._stroke_width)/2
+ self._innie_spacer = 9
+ self._slot = True
+ self._cap = False
+ self._tab = True
+ self._bool = False
+ self._slot_x = 10
+ self._slot_y = 2
+ self._porch = False
+ self._porch_x = self._innie_x1+self._innie_x2+4*self._stroke_width
+ # self._porch_y = self._innie_y1+self._innie_y2+4*self._stroke_width
+ self._porch_y = self._innie_y2
+ self._expand_x = 0
+ self._expand_y = 0
+ self._no_arm = False
+ self._else = False
+ self._draw_innies = True
+ self._hide = False
+ self._show = False
+ self._show_x = 0
+ self._show_y = 0
+ self._hide_x = 0
+ self._hide_y = 0
+ self._dot_radius = 8
+ self._fill = "#00FF00"
+ self._stroke = "#00A000"
+ self._gradiant = False
+ self.margins = [0, 0, 0, 0]
+
+ def basic_block(self):
+ self.reset_min_max()
+ (x, y) = self._calculate_x_y()
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, y)
+ svg += self._corner(1, -1)
+ svg += self._do_slot()
+ svg += self._rline_to(self._expand_x, 0)
+ xx = self._x
+ svg += self._corner(1, 1)
+ for i in range(len(self._innie)):
+ if self._innie[i] is True:
+ svg += self._do_innie()
+ if i==0 and self._porch is True:
+ svg += self._do_porch(False)
+ elif len(self._innie)-1 > i:
+ svg += self._rline_to(0, 2*self._innie_y2+self._innie_spacer)
+ svg += self._rline_to(0, self._expand_y)
+ svg += self._corner(-1, 1)
+ svg += self.line_to(xx, self._y)
+ svg += self._rline_to(-self._expand_x, 0)
+ if self._tab:
+ svg += self._do_tab()
+ else:
+ svg += self._do_tail()
+ svg += self._corner(-1, -1)
+ svg += self._rline_to(0, -self._expand_y)
+ if True in self._innie:
+ svg += self.line_to(x, self._radius+self._innie_y2+\
+ self._stroke_width/2.0)
+ svg += self._do_outie()
+ self.calc_w_h()
+ svg += self._close_path()
+ svg += self.style()
+ if self._show is True:
+ svg += self._show_dot()
+ if self._hide is True:
+ svg += self._hide_dot()
+
+ svg += self.footer()
+ return self.header() + svg
+
+ def basic_flow(self):
+ self.reset_min_max()
+ (x, y) = self._calculate_x_y()
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, y)
+ svg += self._corner(1, -1)
+ svg += self._do_slot()
+ xx = self._x
+ svg += self._rline_to(self._expand_x, 0)
+ if self._bool:
+ svg += self._corner(1, 1, 90, 0, 1, True, False)
+ elif True in self._innie:
+ svg += self._corner(1, 1)
+ for i in range(len(self._innie)):
+ if self._innie[i] is True:
+ svg += self._do_innie()
+ svg += self._rline_to(0, self._innie_spacer)
+ else:
+ self.margins[2] =\
+ int((self._x-self._stroke_width+0.5)*self._scale)
+ if self._bool is True:
+ svg += self._rline_to(0,self._radius/2.0)
+ svg += self._do_boolean()
+ svg += self._rline_to(0,self._stroke_width)
+ if self._else:
+ svg += self._rline_to(self._radius*3+self._slot_x*2, 0)
+ else:
+ svg += self._rline_to(self._radius+self._slot_x, 0)
+ hh = self._x
+ svg += self._corner(1, 1)
+ svg += self._rline_to(-self._radius,0)
+ if self._else:
+ svg += self._do_tab()
+ svg += self._rline_to(-self._radius*2, 0)
+ svg += self._do_tab()
+ svg += self._inverse_corner(-1, 1, 90, 0, 0, True, False)
+ svg += self._rline_to(0, self._expand_y)
+ svg += self._corner(-1, 1, 90, 0, 1, False, True)
+ svg += self.line_to(xx, self._y)
+ if self._tab:
+ svg += self._do_tab()
+ else:
+ svg += self._do_tail()
+ svg += self._corner(-1, -1)
+ svg += self._rline_to(0, -self._expand_y)
+ if True in self._innie:
+ svg += self.line_to(x, self._radius+self._innie_y2+\
+ self._stroke_width)
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ if self._hide is True:
+ svg += self._hide_dot()
+ if self._show is True:
+ svg += self._show_dot()
+ svg += self.footer()
+ return self.header() + svg
+
+ def portfolio(self):
+ self.reset_min_max()
+ (x, y) = self._calculate_x_y()
+ self.margins[0] = int(x+2*self._stroke_width+0.5)
+ self.margins[1] = int(y+self._stroke_width+0.5+self._slot_y)
+ self.margins[2] = 0
+ self.margins[3] = 0
+ x += self._innie_x1+self._innie_x2
+ svg = self.new_path(x, y)
+ svg += self._corner(1, -1)
+ svg += self._do_slot()
+ xx = self._x
+ svg += self._rline_to(self._expand_x, 0)
+ svg += self._corner(1, 1)
+ svg += self._rline_to(0, self._expand_y)
+ for i in range(len(self._innie)):
+ if self._innie[i] is True and i > 0 and self._draw_innies:
+ svg += self._do_innie()
+ svg += self._rline_to(0, 2*self._innie_y2+self._innie_spacer)
+ else:
+ svg += self._rline_to(0, 2*self._innie_y2+self._innie_spacer)
+ svg += self._corner(-1, 1)
+ svg += self.line_to(xx, self._y)
+ svg += self._do_tab()
+ svg += self._corner(-1, -1)
+ for i in range(len(self._innie)):
+ if self._innie[len(self._innie)-i-1] is True:
+ svg += self._rline_to(0, -2*self._innie_y2-self._innie_spacer)
+ svg += self._do_reverse_innie()
+ else:
+ svg += self._rline_to(0, -2*self._innie_y2-self._innie_spacer)
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ svg += self.footer()
+ return self.header() + svg
+
+ def basic_box(self):
+ self.reset_min_max()
+ self.set_outie(True)
+ x = self._stroke_width/2.0+self._innie_x1+self._innie_x2
+ self.margins[0] = int((x+self._stroke_width+0.5)*self._scale)
+ self.margins[1] = int((self._stroke_width+0.5)*self._scale)
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, self._stroke_width/2.0)
+ svg += self._rline_to(self._expand_x, 0)
+ svg += self._rline_to(0, 2*self._radius+self._innie_y2+self._expand_y)
+ svg += self._rline_to(-self._expand_x, 0)
+ svg += self.line_to(x, self._radius+self._innie_y2+\
+ self._stroke_width/2.0)
+ svg += self._do_outie()
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ svg += self.footer()
+ return self.header() + svg
+
+ def boolean_and_or(self):
+ self.reset_min_max()
+ svg = self._start_boolean(self._stroke_width/2.0,
+ self._radius*5.5+self._stroke_width/2.0+\
+ self._innie_y2+self._innie_spacer)
+ svg += self._rline_to(0,-self._radius*3.5-self._innie_y2-\
+ self._innie_spacer-self._stroke_width)
+ svg += self._rarc_to(1, -1)
+ svg += self._rline_to(self._radius/2.0+self._expand_x, 0)
+ xx = self._x
+ svg += self._rline_to(0,self._radius/2.0)
+ svg += self._do_boolean()
+ svg += self._rline_to(0,self._radius*1.5+self._innie_y2+\
+ self._innie_spacer)
+ svg += self._do_boolean()
+ svg += self._rline_to(0,self._radius/2.0)
+ svg += self.line_to(xx, self._y)
+ svg += self._rline_to(-self._expand_x, 0)
+ svg += self._end_boolean()
+ self.margins[0] = int((self._radius+self._stroke_width+0.5)*self._scale)
+ self.margins[1] = int(self._stroke_width*self._scale)
+ self.margins[2] = int(self._stroke_width*self._scale)
+ self.margins[3] = int(self._stroke_width*self._scale)
+ return self.header() + svg
+
+ def boolean_not(self):
+ self.reset_min_max()
+ svg = self._start_boolean(self._stroke_width/2.0,
+ self._radius*2.0+self._stroke_width/2.0)
+ svg += self._rline_to(0,-self._stroke_width)
+ svg += self._rarc_to(1, -1)
+ svg += self._rline_to(self._radius/2.0+self._expand_x, 0)
+ xx = self._x
+ svg += self._rline_to(0,self._radius/2.0)
+ svg += self._do_boolean()
+ svg += self._rline_to(0,self._radius/2.0)
+ svg += self.line_to(xx, self._y)
+ svg += self._rline_to(-self._expand_x, 0)
+ svg += self._end_boolean()
+ self.margins[0] = int((self._radius+self._stroke_width+0.5)*self._scale)
+ self.margins[1] = int(self._stroke_width*self._scale)
+ self.margins[2] = int((self._radius+self._stroke_width+0.5)*self._scale)
+ self.margins[3] = int(self._stroke_width*self._scale)
+ return self.header() + svg
+
+ def boolean_compare(self):
+ self.reset_min_max()
+ yoffset = self._radius*2+2*self._innie_y2+\
+ self._innie_spacer+self._stroke_width/2.0
+ 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)
+ svg += self._rarc_to(1, -1)
+ svg += self._rline_to(self._radius/2.0+self._expand_x, 0)
+ svg += self._rline_to(0,self._radius)
+ xx = self._x
+ svg += self._do_innie()
+ if self._porch is True:
+ svg += self._do_porch()
+ 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)
+ svg += self._end_boolean()
+ self.margins[0] = int((self._radius+self._stroke_width)*self._scale)
+ self.margins[1] = int(self._stroke_width*self._scale)
+ self.margins[2] = int(self._stroke_width*self._scale)
+ self.margins[3] = int(self._stroke_width*self._scale)
+ return self.header() + svg
+
+ def turtle(self, colors):
+ self.reset_min_max()
+ self._fill, self._stroke = colors[0], colors[1]
+
+ svg = "%s%s%s%s%s%s%s%s" % (" <path d=\"M 27.5 48.3 ",
+ "C 26.9 48.3 26.4 48.2 25.9 48.2 L 27.2 50.5 L 28.6 48.2 ",
+ "C 28.2 48.2 27.9 48.3 27.5 48.3 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke,
+ "\" />\n")
+ svg +="%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 40.2 11.7 ",
+ "C 38.0 11.7 36.2 13.3 35.8 15.3 ",
+ "C 37.7 16.7 39.3 18.4 40.5 20.5 ",
+ "C 42.8 20.4 44.6 18.5 44.6 16.2 ",
+ "C 44.6 13.7 42.6 11.7 40.2 11.7 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg +="%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 40.7 39.9 ",
+ "C 39.5 42.1 37.9 44.0 35.9 45.4 ",
+ "C 36.4 47.3 38.1 48.7 40.2 48.7 ",
+ "C 42.6 48.7 44.6 46.7 44.6 44.3 ",
+ "C 44.6 42.0 42.9 40.2 40.7 39.9 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg +="%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 14.3 39.9 ",
+ "C 12.0 40.1 10.2 42.0 10.2 44.3 ",
+ "C 10.2 46.7 12.2 48.7 14.7 48.7 ",
+ "C 16.7 48.7 18.5 47.3 18.9 45.4 ",
+ "C 17.1 43.9 15.5 42.1 14.3 39.9 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg +="%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 19.0 15.4 ",
+ "C 18.7 13.3 16.9 11.7 14.7 11.7 ",
+ "C 12.2 11.7 10.2 13.7 10.2 16.2 ",
+ "C 10.2 18.5 12.1 20.5 14.5 20.6 ",
+ "C 15.7 18.5 17.2 16.8 19.0 15.4 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg +="%s%s%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 27.5 12.6 ",
+ "C 29.4 12.6 31.2 13.0 32.9 13.7 ",
+ "C 33.7 12.6 34.1 11.3 34.1 9.9 ",
+ "C 34.1 6.2 31.1 3.2 27.4 3.2 ",
+ "C 23.7 3.2 20.7 6.2 20.7 9.9 ",
+ "C 20.7 11.3 21.2 12.7 22.0 13.7 ",
+ "C 23.7 13.0 25.5 12.6 27.5 12.6 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg +="%s%s%s%s%s%s%s%s%s%s%s%s" % (" <path d=\"M 43.1 30.4 ",
+ "C 43.1 35.2 41.5 39.7 38.5 43.0 ",
+ "C 35.6 46.4 31.6 48.3 27.5 48.3 ",
+ "C 23.4 48.3 19.4 46.4 16.5 43.0 ",
+ "C 13.5 39.7 11.9 35.2 11.9 30.4 ",
+ "C 11.9 20.6 18.9 12.6 27.5 12.6 ",
+ "C 36.1 12.6 43.1 20.6 43.1 30.4 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._fill, ";\" stroke=\"", self._stroke, "\" />\n")
+ svg +="%s%s%s%s%s" % (" <path d=\"M 25.9 33.8 L 24.3 29.1 ",
+ "L 27.5 26.5 L 31.1 29.2 L 29.6 33.8 Z\" stroke_width=\"3.5\" ",
+ "fill=\"", self._stroke, ";\" stroke=\"none\" />\n")
+ svg +="%s%s%s%s%s%s" % (" <path d=\"M 27.5 41.6 ",
+ "C 23.5 41.4 22.0 39.5 22.0 39.5 L 25.5 35.4 L 30.0 35.5 ",
+ "L 33.1 39.7 C 33.1 39.7 30.2 41.7 27.5 41.6 Z\" ",
+ "stroke_width=\"3.5\" fill=\"", self._stroke,
+ ";\" stroke=\"none\" />\n")
+ svg +="%s%s%s%s%s%s" % (" <path d=\"M 18.5 33.8 ",
+ "C 17.6 30.9 18.6 27.0 18.6 27.0 L 22.6 29.1 L 24.1 33.8 ",
+ "L 20.5 38.0 C 20.5 38.0 19.1 36.0 18.4 33.8 Z\" ",
+ "stroke_width=\"3.5\" fill=\"", self._stroke,
+ ";\" stroke=\"none\" />\n")
+ svg +="%s%s%s%s%s%s" % (" <path d=\"M 19.5 25.1 ",
+ "C 19.5 25.1 20.0 23.2 22.5 21.3 ",
+ "C 24.7 19.7 27.0 19.6 27.0 19.6 L 26.9 24.6 L 23.4 27.3 ",
+ "L 19.5 25.1 Z\" stroke_width=\"3.5\" fill=\"", self._stroke,
+ ";\" stroke=\"none\" />\n")
+ svg +="%s%s%s%s%s%s" % (" <path d=\"M 32.1 27.8 L 28.6 25.0 ",
+ "L 29 19.8 C 29 19.8 30.8 19.7 33.0 21.4 ",
+ "C 35.2 23.2 36.3 26.4 36.3 26.4 L 32.1 27.8 Z\" ",
+ "stroke_width=\"3.5\" fill=\"", self._stroke,
+ ";\" stroke=\"none\" />\n")
+ svg +="%s%s%s%s%s%s" % (" <path d=\"M 31.3 34.0 L 32.6 29.6 ",
+ "L 36.8 28.0 C 36.8 28.0 37.5 30.7 36.8 33.7 ",
+ "C 36.2 36.0 34.7 38.1 34.7 38.1 L 31.3 34.0 Z\" ",
+ "stroke_width=\"3.5\" fill=\"", self._stroke,
+ ";\" stroke=\"none\" />\n")
+ self._width, self._height = 55, 55
+ svg += self.footer()
+ return self.header() + svg
+
+ def palette(self, width, height):
+ self.reset_min_max()
+ self._width, self._height = width, height
+ self._fill, self._stroke = "#FFD000", "none"
+ svg = self._rect(width, height, 0, 0)
+ self._hide_x = (width-self._radius*1.5)/2
+ self._hide_y = (height-self._radius*1.5)/2
+ svg += self._hide_dot(True)
+ svg += self.footer()
+ return self.header() + svg
+
+ def toolbar(self, width, height):
+ self.reset_min_max()
+ self._width, self._height = width, height
+ self._fill, self._stroke = "#282828", "none"
+ svg = self._rect(width, height, 0, 0)
+ svg += self.footer()
+ return self.header() + svg
+
+ def sandwich_top(self):
+ self.reset_min_max()
+ x = self._stroke_width/2.0
+ y = self._stroke_width/2.0+self._radius
+ self.margins[0] = int((x+self._stroke_width+0.5)*self._scale)
+ self.margins[1] = int((self._stroke_width+0.5)*self._scale)
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, y)
+ svg += self._corner(1, -1)
+ svg += self._rline_to(self._radius+self._stroke_width, 0)
+ svg += self._do_slot()
+ svg += self._rline_to(self._expand_x, 0)
+ xx = self._x
+ svg += self._corner(1, 1)
+ svg += self._do_innie()
+ svg += self._corner(-1, 1)
+ svg += self.line_to(xx, self._y)
+ svg += self._rline_to(-self._expand_x, 0)
+ svg += self._do_tab()
+ if self._no_arm:
+ svg += self._rline_to(-self._radius-self._stroke_width, 0)
+ svg += self._corner(-1, -1)
+ else:
+ svg += self._inverse_corner(-1, 1, 90, 0, 0)
+ svg += self._rline_to(0, self._expand_y)
+ svg += self._rline_to(-self._radius, 0)
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ svg += self.footer()
+ return self.header() + svg
+
+ def sandwich_bottom(self):
+ self.reset_min_max()
+ x = self._stroke_width/2.0
+ y = self._stroke_width/2.0
+ self.margins[0] = int((x+self._stroke_width+0.5)*self._scale)
+ self.margins[1] = int((self._stroke_width+0.5)*self._scale)
+ self.margins[2] = 0
+ self.margins[3] = 0
+ svg = self.new_path(x, y)
+ svg += self._rline_to(self._radius, 0)
+ svg += self._rline_to(0, self._expand_y)
+ svg += self._inverse_corner(1, 1, 90, 0, 0)
+ svg += self._do_slot()
+ svg += self._rline_to(self._radius, 0)
+ svg += self._corner(-1, 1)
+ svg += self._do_tab()
+ svg += self._rline_to(-self._radius-self._stroke_width,0)
+ svg += self._corner(-1, -1)
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ self._hide_x = x + self._radius/2
+ self._hide_y = y + self._radius/2
+ if self._hide is True:
+ svg += self._hide_dot()
+ if self._show is True:
+ svg += self._show_dot()
+ svg += self.footer()
+ return self.header() + svg
+
+ #
+ # Utility methods
+ #
+ def set_draw_innies(self, flag=True):
+ self._draw_innies = flag
+
+ def set_hide(self, flag=False):
+ self._hide = flag
+
+ def set_show(self, flag=False):
+ self._show = flag
+
+ def get_width(self):
+ return self._width
+
+ def get_height(self):
+ return self._height
+
+ def get_innie_width(self):
+ return (self._innie_x1+self._innie_x2)*self._scale
+
+ def get_slot_depth(self):
+ return self._slot_y*self._scale
+
+ def clear_docks(self):
+ self.docks = []
+
+ def set_scale(self, scale=1):
+ self._scale = scale
+
+ def set_orientation(self, orientation=0):
+ self._orientation = orientation
+
+ def expand(self, w=0, h=0):
+ self._expand_x = w
+ self._expand_y = h
+
+ def set_stroke_width(self, stroke_width=1.5):
+ self._stroke_width = stroke_width
+ self._calc_porch_params()
+
+ def set_colors(self, colors=["#00FF00","#00A000"]):
+ self._fill = colors[0]
+ self._stroke = colors[1]
+
+ def set_fill_color(self, color="#00FF00"):
+ self._fill = color
+
+ def set_stroke_color(self, color="#00A000"):
+ self._stroke = color
+
+ def set_gradiant(self, flag=False):
+ self._gradiant = flag
+
+ def set_innie(self, innie_array=[False]):
+ self._innie = innie_array
+
+ def set_outie(self, flag=False):
+ self._outie = flag
+
+ def set_slot(self, flag=True):
+ self._slot = flag
+ if self._slot is True:
+ self._cap = False
+
+ def set_cap(self, flag=False):
+ self._cap = flag
+ if self._cap is True:
+ self._slot = False
+
+ def set_tab(self, flag=True):
+ self._tab = flag
+
+ def set_porch(self, flag=False):
+ self._porch = flag
+
+ def set_boolean(self, flag=False):
+ self._bool = flag
+
+ def set_else(self, flag=False):
+ self._else = flag
+
+ def set_no_arm(self, flag=True):
+ self._no_arm = flag
+
+ def reset_min_max(self):
+ self._min_x = 10000
+ self._min_y = 10000
+ self._max_x = -10000
+ self._max_y = -10000
+
+ #
+ # Exotic methods
+ #
+
+ def set_radius(self, radius=8):
+ self._radius = radius
+
+ def set_innie_params(self, x1=4, y1=3, x2=4, y2=4):
+ self._innie_x1 = x1
+ self._innie_y1 = y1
+ self._innie_x2 = x2
+ self._innie_y2 = y2
+ self._calc_porch_params()
+
+ def set_innie_spacer(self, innie_spacer = 0):
+ self._innie_spacer = innie_spacer
+
+ def set_slot_params(self, x=12, y=4):
+ self._slot_x = x
+ self._slot_y = y
+
+ def _calc_porch_params(self):
+ self._porch_x = self._innie_x1+self._innie_x2+4*self._stroke_width
+ self._porch_y = self._innie_y1+self._innie_y2+4*self._stroke_width
+
+ #
+ # SVG helper methods
+ #
+ def header(self, center=False):
+ return "%s%s%s%s%s%s%.1f%s%s%.1f%s%s%s" % (
+ "<svg\n",
+ " xmlns:svg=\"http://www.w3.org/2000/svg\"\n",
+ " xmlns=\"http://www.w3.org/2000/svg\"\n",
+ " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n",
+ " version=\"1.1\"\n",
+ " width=\"", self._width, "\"\n",
+ " height=\"", self._height, "\">\n",
+ self._defs(),
+ self._transform(center))
+
+ def _defs(self):
+ if self._gradiant is True:
+ return "%s%s%s%s%s%s%s%s%s%s%s%s%.1f%s%s%.1f%s%s%.1f%s%s" % (
+ " <defs>\n <linearGradient\n id=\"linearGradient1234\">\n",
+ " <stop\n style=\"stop-color:#ffffff;stop-opacity:1;\"\n",
+ " offset=\"0\" />\n",
+ " <stop\n style=\"stop-color:", self._fill,
+ ";stop-opacity:1;\"\n",
+ " offset=\"1\" />\n",
+ " </linearGradient>\n",
+ " <linearGradient\n xlink:href=\"#linearGradient1234\"\n",
+ " id=\"linearGradient5678\"\n",
+ " x1=\"0\"\n",
+ " y1=\"", self._height/2.0, "\"\n",
+ " x2=\"", self._width/self._scale, "\"\n",
+ " y2=\"", self._height/2.0, "\"\n",
+ " gradientUnits=\"userSpaceOnUse\" />\n </defs>\n")
+ else:
+ return ""
+
+ def _transform(self, center):
+ if self._orientation != 0:
+ orientation = "<g\ntransform = \"rotate(%.1f %.1f %.1f)\">\n" % (
+ self._orientation, self._width/2.0, self._height/2.0)
+ else:
+ orientation = ""
+ if center:
+ return "<g\ntransform=\"translate(%.1f, %.1f)\">\n" % (
+ -self._min_x, -self._min_y)
+ else:
+ return "<g\ntransform=\"scale(%.1f, %.1f)\">\n%s" % (
+ self._scale, self._scale, orientation )
+
+ def footer(self):
+ if self._orientation != 0:
+ return " </g>\n</g>\n</svg>\n"
+ else:
+ return " </g>\n</svg>\n"
+
+ def style(self):
+ if self._gradiant is True:
+ fill = "url(#linearGradient5678)"
+ else:
+ fill = self._fill
+ return "%s%s;%s%s;%s%.1f;%s%s" % (
+ " style=\"fill:",fill,
+ "fill-opacity:1;stroke:",self._stroke,
+ "stroke-width:",self._stroke_width,
+ "stroke-linecap:round;",
+ "stroke-opacity:1;\" />\n")
+
+ def text(self, x, y, size, width, string):
+ self._x = x
+ self._y = y
+ self._check_min_max()
+ self._x = x+width
+ self._y = y-size
+ self._check_min_max()
+ return " %s%.1f%s%s%s%.1f%s%.1f%s%.1f%s%s%s%s%s" % (
+ "<text style=\"font-size:", size, "px;fill:", self._stroke,
+ ";font-family:Sans\">\n <tspan x=\"", x, "\" y=\"", y,
+ "\" style=\"font-size:", size, "px;fill:", self._stroke, "\">",
+ string, "</tspan>\n </text>\n")
+
+ def image(self, x, y, w, h, path, image_data=None):
+ self._x = x
+ self._y = y
+ self._check_min_max()
+ self._x = x+w
+ self._y = y+h
+ self._check_min_max()
+ if image_data == None:
+ return " %s%.1f%s%.1f%s%.1f%s%.1f%s%s%s" % (
+ "<image x=\"", x, "\" y=\"", y,
+ "\" width=\"", w, "\" height=\"", h,
+ "\" xlink:href=\"file://", path, "\"/>\n")
+ else:
+ return " %s%.1f%s%.1f%s%.1f%s%.1f%s%s%s" % (
+ "<image x=\"", x, "\" y=\"", y,
+ "\" width=\"", w, "\" height=\"", h,
+ "\" xlink:href=\"data:image/png;base64,", image_data, "\"/>\n")
+
+
+ def _circle(self, r, cx, cy):
+ return "%s%s%s%s%s%f%s%f%s%f%s" % ("<circle style=\"fill:",
+ self._fill, ";stroke:", self._stroke, ";\" r=\"", r, "\" cx=\"",
+ cx, "\" cy=\"", cy, "\" />\n")
+
+ def _rect(self, w, h, x, y):
+ return "%s%s%s%s%s%f%s%f%s%f%s%f%s" % ("<rect style=\"fill:",
+ self._fill, ";stroke:", self._stroke, ";\" width=\"", w,
+ "\" height=\"", h,"\" x=\"", x, "\" y=\"", y, "\" />\n")
+
+ def background(self, fill):
+ return "%s%s%s%s%s%f%s%f%s%f%s%f%s" % ("<rect style=\"fill:",
+ fill, ";stroke:", fill, ";\" width=\"", self._max_x-self._min_x,
+ "\" height=\"", self._max_y-self._min_y,"\" x=\"",
+ self._min_x, "\" y=\"", self._min_y, "\" />\n")
+
+ def _check_min_max(self):
+ if self._x < self._min_x:
+ self._min_x = self._x
+ if self._y < self._min_y:
+ self._min_y = self._y
+ if self._x > self._max_x:
+ self._max_x = self._x
+ if self._y > self._max_y:
+ self._max_y = self._y
+
+ def line_to(self, x, y):
+ self._check_min_max()
+ if self._x == x and self._y == y:
+ return ""
+ else:
+ self._x = x
+ self._y = y
+ self._check_min_max()
+ return "L %.1f %.1f " % (x, y)
+
+ def _rline_to(self, dx, dy):
+ if dx == 0 and dy == 0:
+ return ""
+ else:
+ return self.line_to(self._x+dx, self._y+dy)
+
+ def arc_to(self, x, y, r, a=90, l=0, s=1):
+ self._check_min_max()
+ if r == 0:
+ return self.line_to(x, y)
+ else:
+ self._x = x
+ self._y = y
+ self._check_min_max()
+ return "A %.1f %.1f %.1f %d %d %.1f %.1f " % (
+ r, r, a, l, s, x, y)
+
+ def _rarc_to(self, sign_x, sign_y, a=90, l=0, s=1):
+ if self._radius == 0:
+ return ""
+ else:
+ x = self._x + sign_x*self._radius
+ y = self._y + sign_y*self._radius
+ return self.arc_to(x, y, self._radius, a, l, s)
+
+ def _inverse_corner(self, sign_x, sign_y, a=90, l=0, s=1, start=True,
+ end=True):
+ r2 = self._stroke_width+self._radius/2.0
+ if start:
+ if sign_x*sign_y == -1:
+ svg_str =self._rline_to(sign_x*(r2-self._stroke_width), 0)
+ else:
+ svg_str =self._rline_to(0, sign_y*+(r2-self._stroke_width))
+ x = self._x + sign_x*r2
+ y = self._y + sign_y*r2
+ svg_str += self.arc_to(x, y, r2, a, l, s)
+ if end:
+ if sign_x*sign_y == -1:
+ svg_str +=self._rline_to(0, sign_y*(r2-self._stroke_width))
+ else:
+ svg_str +=self._rline_to(sign_x*(r2-self._stroke_width), 0)
+ return svg_str
+
+ def _corner(self, sign_x, sign_y, a=90, l=0, s=1, start=True, end=True):
+ svg_str = ""
+ if sign_x == 1 and sign_y == -1:
+ self._hide_x = self._x + self._radius/2
+ self._hide_y = self._y - self._radius/2
+ if sign_x == -1 and sign_y == 1:
+ self._show_x = self._x - self._radius/2
+ self._show_y = self._y + self._radius/2
+ if True in self._innie:
+ self._show_x -= (self._innie_x1+self._innie_x2)
+ self._show_y -= (self._innie_y1+self._innie_y2)
+ if self._radius > 0:
+ r2 = self._radius/2.0
+ if start:
+ if sign_x*sign_y == 1:
+ svg_str +=self._rline_to(sign_x*r2, 0)
+ else:
+ svg_str +=self._rline_to(0, sign_y*r2)
+ x = self._x + sign_x*r2
+ y = self._y + sign_y*r2
+ svg_str += self.arc_to(x, y, r2, a, l, s)
+ if end:
+ if sign_x*sign_y == 1:
+ svg_str +=self._rline_to(0, sign_y*r2)
+ else:
+ svg_str +=self._rline_to(sign_x*r2, 0)
+ return svg_str
+
+ def new_path(self, x, y):
+ """
+ self._min_x = x
+ self._min_y = y
+ self._max_x = x
+ self._max_y = y
+ """
+ self._x = x
+ self._y = y
+ return " <path d=\"m%.1f %.1f " % (x, y)
+
+ def _close_path(self):
+ return "z\"\n"
+
+ def _hide_dot(self, noscale=False):
+ _saved_fill, _saved_stroke = self._fill, self._stroke
+ self._fill, self._stroke = "#FF0000", "#FF0000"
+ svg = "</g>/n<g>/n"
+ if noscale:
+ scale = 2.0
+ else:
+ scale = self._scale
+ scale2 = scale/2
+ svg += self._circle(self._dot_radius*scale2, self._hide_x*scale,
+ self._hide_y*scale)
+ self._fill, self._stroke = "#FFFFFF", "#FFFFFF"
+ svg += self._rect(10*scale2, 2*scale2, self._hide_x*scale-5*scale2,
+ self._hide_y*scale-scale+scale2)
+ self._fill, self._stroke = _saved_fill, _saved_stroke
+ return svg
+
+ def _show_dot(self, noscale=False):
+ _saved_fill, _saved_stroke = self._fill, self._stroke
+ self._fill, self._stroke = "#00FE00", "#00FE00"
+ svg = "</g>/n<g>/n"
+ if noscale:
+ scale = 2.0
+ else:
+ scale = self._scale
+ scale2 = scale/2
+ svg += self._circle(self._dot_radius*scale2, self._show_x*scale,
+ self._show_y*scale)
+ self._fill, self._stroke = "#FEFEFE", "#FEFEFE"
+ svg += self._rect(10*scale2, 2*scale2, self._show_x*scale-5*scale2,
+ self._show_y*scale-scale+scale2)
+ svg += self._rect(2*scale2, 10*scale2, self._show_x*scale-scale+scale2,
+ self._show_y*scale-5*scale2)
+ self._fill, self._stroke = _saved_fill, _saved_stroke
+ return svg
+
+ def _do_slot(self):
+ if self._slot is True:
+ self.docks.append((int(self._x*self._scale),
+ int(self._y*self._scale)))
+ return "%s%s%s" % (
+ self._rline_to(0, self._slot_y),
+ self._rline_to(self._slot_x, 0),
+ self._rline_to(0, -self._slot_y))
+ elif self._cap is True:
+ return "%s%s" % (
+ self._rline_to(self._slot_x/2.0, -self._slot_y*2.0),
+ self._rline_to(self._slot_x/2.0, self._slot_y*2.0))
+ else:
+ return self._rline_to(self._slot_x, 0)
+
+ def _do_tail(self):
+ if self._outie is True:
+ return self._rline_to(-self._slot_x, 0)
+ else:
+ return "%s%s" % (
+ self._rline_to(-self._slot_x/2.0, self._slot_y*2.0),
+ self._rline_to(-self._slot_x/2.0, -self._slot_y*2.0))
+
+ def _do_tab(self):
+ s = "%s%s%s%s%s" % (
+ self._rline_to(-self._stroke_width, 0),
+ self._rline_to(0, self._slot_y),
+ self._rline_to(-self._slot_x+2*self._stroke_width, 0),
+ self._rline_to(0, -self._slot_y),
+ self._rline_to(-self._stroke_width, 0))
+ self.docks.append((int(self._x*self._scale),
+ int((self._y+self._stroke_width)*self._scale)))
+ return s
+
+ def _do_innie(self):
+ self.docks.append((int((self._x+self._stroke_width)*self._scale),
+ int((self._y+self._innie_y2)*self._scale)))
+ if self.margins[2] == 0:
+ self.margins[1] = int((self._y-self._innie_y1)*self._scale)
+ self.margins[2] = int((self._x-self._innie_x1-self._innie_x2-\
+ self._stroke_width*2)*self._scale)
+ self.margins[3] =\
+ int((self._y+self._innie_y2+self._innie_y1)*self._scale)
+ return "%s%s%s%s%s%s%s" % (
+ self._rline_to(-self._innie_x1, 0),
+ self._rline_to(0, -self._innie_y1),
+ self._rline_to(-self._innie_x2, 0),
+ self._rline_to(0, self._innie_y2+2*self._innie_y1),
+ self._rline_to(self._innie_x2, 0),
+ self._rline_to(0, -self._innie_y1),
+ self._rline_to(self._innie_x1, 0))
+
+ def _do_reverse_innie(self):
+ self.docks.append((int((self._x+self._stroke_width)*self._scale),
+ int((self._y)*self._scale)))
+ return "%s%s%s%s%s%s%s" % (
+ self._rline_to(-self._innie_x1, 0),
+ self._rline_to(0, self._innie_y1),
+ self._rline_to(-self._innie_x2, 0),
+ self._rline_to(0, -self._innie_y2-2*self._innie_y1),
+ self._rline_to(self._innie_x2, 0),
+ self._rline_to(0, self._innie_y1),
+ self._rline_to(self._innie_x1, 0))
+
+ def _do_outie(self):
+ if self._outie is not True:
+ return self._rline_to(0, -self._innie_y2)
+ self.docks.append((int(self._x*self._scale), int(self._y*self._scale)))
+ return "%s%s%s%s%s%s%s%s%s" % (
+ self._rline_to(0, -self._stroke_width),
+ self._rline_to(-self._innie_x1-2*self._stroke_width, 0),
+ self._rline_to(0, self._innie_y1),
+ self._rline_to(-self._innie_x2+2*self._stroke_width, 0),
+ self._rline_to(0,
+ -self._innie_y2-2*self._innie_y1+2*self._stroke_width),
+ self._rline_to(self._innie_x2-2*self._stroke_width, 0),
+ self._rline_to(0, self._innie_y1),
+ self._rline_to(self._innie_x1+2*self._stroke_width, 0),
+ self._rline_to(0, -self._stroke_width))
+
+ def _do_porch(self, flag=True):
+ if flag:
+ return "%s%s%s" % (
+ self._rline_to(0, self._porch_y+self._innie_y1),
+ self._rline_to(self._porch_x-self._radius, 0),
+ self._corner(1, 1))
+ else:
+ return "%s%s%s" % (
+ self._rline_to(0,
+ self._porch_y-self._innie_y1+self._stroke_width),
+ self._rline_to(self._porch_x-self._radius, 0),
+ self._corner(1, 1))
+
+ def _start_boolean(self, xoffset, yoffset):
+ svg = self.new_path(xoffset, yoffset)
+ self._radius -= self._stroke_width
+ self.docks.append((int(self._x*self._scale), int(self._y*self._scale)))
+ svg += self._rarc_to(1, -1)
+ self._radius += self._stroke_width
+ return svg + self._rline_to(self._stroke_width, 0)
+
+ def _do_boolean(self):
+ self.docks.append(
+ (int((self._x-self._radius+self._stroke_width)*self._scale),
+ int((self._y+self._radius)*self._scale)))
+ self.margins[2] =\
+ int((self._x-self._radius-self._stroke_width)*self._scale)
+ return self._rarc_to(-1, 1, 90, 0, 0) + self._rarc_to(1, 1, 90, 0, 0)
+
+ def _end_boolean(self):
+ svg = self._rline_to(-self._radius*1.5,0)
+ svg += self._rline_to(0, -self._stroke_width)
+ svg += self._rline_to(-self._stroke_width, 0)
+ self._radius -= self._stroke_width
+ svg += self._rarc_to(-1, -1)
+ self._radius += self._stroke_width
+ svg += self._close_path()
+ self.calc_w_h()
+ svg += self.style()
+ return svg + self.footer()
+
+ def calc_w_h(self, add_stroke_width=True):
+ if add_stroke_width:
+ self._width = (self._max_x-self._min_x+self._stroke_width)*\
+ self._scale
+ else:
+ self._width = (self._max_x-self._min_x)*self._scale
+ if self.margins[2] == 0:
+ self.margins[2] = int((self._stroke_width+0.5)*self._scale)
+ else:
+ self.margins[2] = int(self._width - self.margins[2])
+ if add_stroke_width:
+ self._height = (self._max_y-self._min_y+self._stroke_width)*\
+ self._scale
+ else:
+ self._height = (self._max_y-self._min_y)*self._scale
+ if self.margins[3] == 0:
+ if self._tab:
+ self.margins[3] =\
+ int((self._slot_y+self._stroke_width+0.5)*self._scale)
+ else:
+ self.margins[3] =\
+ int((self._slot_y*2+self._stroke_width+0.5)*self._scale)
+ else:
+ self.margins[3] = int(self._height - self.margins[3])
+
+ def _calculate_x_y(self):
+ x = self._stroke_width/2.0
+ y = self._stroke_width/2.0+self._radius
+ self.margins[0] = int(x+self._stroke_width+0.5)
+ self.margins[1] = int(self._stroke_width+0.5)
+ if self._outie is True:
+ x += self._innie_x1+self._innie_x2
+ self.margins[0] += self._innie_x1+self._innie_x2
+ if self._cap is True:
+ y += self._slot_y*2.0
+ self.margins[1] += self._slot_y*2.0
+ elif self._slot is True:
+ self.margins[1] += self._slot_y
+ self.margins[0] *= self._scale
+ self.margins[1] *= self._scale
+ return(x, y)
+
+#
+# Command-line tools for testing
+#
+
+def open_file(datapath, filename):
+ return file(os.path.join(datapath, filename), "w")
+
+def close_file(f):
+ f.close()
+
+def generator(datapath):
+
+ svg0 = SVG()
+ f = open_file(datapath, "basic.svg")
+ svg0.set_scale(2)
+ svg0.set_tab(True)
+ svg0.set_slot(True)
+ svg0.set_no_arm(True)
+ svg_str = svg0.sandwich_top()
+ f.write(svg_str)
+ close_file(f)
+
+ """
+ svgt = SVG()
+ svgt.set_orientation(180)
+ f = open_file(datapath, "turtle180.svg")
+ svg_str = svgt.turtle(["#FF0000","#00FF00"])
+ f.write(svg_str)
+ close_file(f)
+
+
+ svg2 = SVG()
+ f = open_file(datapath, "box-test.svg")
+ svg2.set_scale(1)
+ svg2.expand(40,0)
+ svg2.set_colors(["#FFA000","#A08000"])
+ svg2.set_gradiant(True)
+ svg_str = svg2.basic_box()
+ f.write(svg_str)
+ close_file(f)
+
+ svg2 = SVG()
+ f = open_file(datapath, "box-test2.svg")
+ svg2.set_scale(4)
+ svg2.expand(40,0)
+ svg2.set_colors(["#FFA000","#A08000"])
+ svg2.set_gradiant(True)
+ svg_str = svg2.basic_box()
+ f.write(svg_str)
+ close_file(f)
+
+ svg3 = SVG()
+ f = open_file(datapath, "compare-text.svg")
+ svg3.set_scale(1)
+ svg3.set_colors(["#0000FF","#0000A0"])
+ svg3.set_gradiant(True)
+ # svg3.set_porch(True)
+ svg_str = svg3.boolean_compare()
+ f.write(svg_str)
+ close_file(f)
+
+ svg4 = SVG()
+ f = open_file(datapath, "and-or-test.svg")
+ svg4.set_scale(1)
+ svg4.set_colors(["#00FFFF","#00A0A0"])
+ svg4.set_gradiant(True)
+ svg_str = svg4.boolean_and_or()
+ f.write(svg_str)
+ close_file(f)
+
+ svg5 = SVG()
+ f = open_file(datapath, "nor-test.svg")
+ svg5.set_scale(1)
+ svg5.set_colors(["#FF00FF","#A000A0"])
+ svg5.set_gradiant(True)
+ svg_str = svg5.boolean_not()
+ f.write(svg_str)
+ close_file(f)
+ """
+
+def main():
+ return 0
+
+if __name__ == "__main__":
+ generator(os.path.abspath('.'))
+ main()
+
+
+#
+# Load pixbuf from SVG string
+#
+def svg_str_to_pixbuf(svg_string):
+ pl = gtk.gdk.PixbufLoader('svg')
+ pl.write(svg_string)
+ pl.close()
+ pixbuf = pl.get_pixbuf()
+ return pixbuf
+
+
+#
+# Read SVG string from a file
+#
+def svg_from_file(pathname):
+ f = file(pathname, 'r')
+ svg = f.read()
+ f.close()
+ return(svg)
+
diff --git a/TurtleArt/taturtle.py b/TurtleArt/taturtle.py
new file mode 100644
index 0000000..23344e2
--- /dev/null
+++ b/TurtleArt/taturtle.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+#Copyright (c) 2010 Walter Bender
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+from taconstants import TURTLE_LAYER
+from tasprite_factory import SVG, svg_str_to_pixbuf
+from sprites import Sprite
+
+def generate_turtle_pixbufs(colors):
+ """ Generate pixbufs for generic turtles """
+ shapes = []
+ svg = SVG()
+ svg.set_scale(1.0)
+ for i in range(36):
+ svg.set_orientation(i*10)
+ shapes.append(svg_str_to_pixbuf(svg.turtle(colors)))
+ return shapes
+
+#
+# A class for the list of blocks and everything they share in common
+#
+class Turtles:
+ def __init__(self, sprite_list):
+ """ Class to hold turtles """
+ self.dict = dict()
+ self.sprite_list = sprite_list
+ self.default_pixbufs = []
+
+ def get_turtle(self, k, append=False, colors=None):
+ """ Find a turtle """
+ if self.dict.has_key(k):
+ return self.dict[k]
+ elif append is False:
+ return None
+ else:
+ if colors == None:
+ Turtle(self, k)
+ else:
+ Turtle(self, k, colors.split(','))
+ return self.dict[k]
+
+ def get_turtle_key(self, turtle):
+ """ Find a turtle's name """
+ for k in iter(self.dict):
+ if self.dict[k] == turtle:
+ return k
+ return None
+
+ def turtle_count(self):
+ """ How many turtles are there? """
+ return(len(self.dict))
+
+ def add_to_dict(self, k, turtle):
+ """ Add a new turtle """
+ self.dict[k] = turtle
+
+ def remove_from_dict(self, k):
+ """ Delete a turtle """
+ if self.dict.has_key(k):
+ del(self.dict[k])
+
+ def show_all(self):
+ """ Make all turtles visible """
+ for k in iter(self.dict):
+ self.dict[k].show()
+
+ #
+ # sprite utilities
+ #
+ def spr_to_turtle(self, spr):
+ """ Find the turtle that corresponds to sprite spr. """
+ for k in iter(self.dict):
+ if spr == self.dict[k].spr:
+ return self.dict[k]
+ return None
+
+ def get_pixbufs(self):
+ """ Get the pixbufs for the default turtle shapes. """
+ if self.default_pixbufs == []:
+ self.default_pixbufs = generate_turtle_pixbufs(
+ ["#008000", "#00A000"])
+ return(self.default_pixbufs)
+
+#
+# A class for the individual turtles
+#
+class Turtle:
+ def __init__(self, turtles, key, colors=None):
+ """ The turtle is not a block, just a sprite with an orientation """
+ self.x = 0
+ self.y = 0
+ self.hidden = False
+ self.shapes = []
+ self.type = 'turtle'
+ self.heading = 0
+ self.pen_shade = 50
+ self.pen_color = 0
+ self.pen_gray = 100
+ self.pen_size = 5
+ self.pen_state = True
+
+ if colors is None:
+ self.shapes = turtles.get_pixbufs()
+ else:
+ self.colors = colors[:]
+ self.shapes = generate_turtle_pixbufs(self.colors)
+
+ if turtles.sprite_list is not None:
+ self.spr = Sprite(turtles.sprite_list, 0, 0, self.shapes[0])
+ else:
+ self.spr = None
+ turtles.add_to_dict(key, self)
+
+ def set_heading(self, heading):
+ """ Set the turtle heading (and shape: one per 10 degrees) """
+ self.heading = heading
+ i = (int(self.heading+5)%360)/10
+ if self.hidden is False and self.spr is not None:
+ try:
+ self.spr.set_shape(self.shapes[i])
+ except IndexError:
+ self.spr.set_shape(self.shapes[0])
+ print "Turtle shape IndexError %f -> %d" % (heading, i)
+
+ def set_color(self, color):
+ """ Set the pen color for this turtle. """
+ self.pen_color = color
+
+ def set_gray(self, gray):
+ """ Set the pen gray level for this turtle. """
+ self.pen_gray = gray
+
+ def set_shade(self, shade):
+ """ Set the pen shade for this turtle. """
+ self.pen_shade = shade
+
+ def set_pen_size(self, pen_size):
+ """ Set the pen size for this turtle. """
+ self.pen_size = pen_size
+
+ def set_pen_state(self, pen_state):
+ """ Set the pen state (down==True) for this turtle. """
+ self.pen_state = pen_state
+
+ def hide(self):
+ """ Hide the turtle. """
+ if self.spr is not None:
+ self.spr.hide()
+ self.hidden = True
+
+ def show(self):
+ """ Show the turtle. """
+ if self.spr is not None:
+ self.spr.set_layer(TURTLE_LAYER)
+ self.hidden = False
+ self.move((self.x, self.y))
+ self.set_heading(self.heading)
+
+ def move(self, pos):
+ """ Move the turtle. """
+ self.x, self.y = pos[0], pos[1]
+ if self.hidden is False and self.spr is not None:
+ self.spr.move(pos)
+ return(self.x, self.y)
+
+ def get_xy(self):
+ """ Return the turtle's x, y coordinates. """
+ return(self.x, self.y)
+
+ def get_heading(self):
+ """ Return the turtle's heading. """
+ return(self.heading)
+
+ def get_color(self):
+ """ Return the turtle's color. """
+ return(self.pen_color)
+
+ def get_gray(self):
+ """ Return the turtle's gray level. """
+ return(self.pen_gray)
+
+ def get_shade(self):
+ """ Return the turtle's shade. """
+ return(self.pen_shade)
+
+ def get_pen_size(self):
+ """ Return the turtle's pen size. """
+ return(self.pen_size)
+
+ def get_pen_state(self):
+ """ Return the turtle's pen state. """
+ return(self.pen_state)
diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py
new file mode 100644
index 0000000..e7888ea
--- /dev/null
+++ b/TurtleArt/tautils.py
@@ -0,0 +1,705 @@
+#Copyright (c) 2007-8, Playful Invention Company.
+#Copyright (c) 2008-10, Walter Bender
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import gtk
+import pickle
+import subprocess
+try:
+ OLD_SUGAR_SYSTEM = False
+ import json
+ json.dumps
+ from json import load as jload
+ from json import dump as jdump
+except (ImportError, AttributeError):
+ try:
+ import simplejson as json
+ from simplejson import load as jload
+ from simplejson import dump as jdump
+ except:
+ OLD_SUGAR_SYSTEM = True
+from taconstants import STRING_OR_NUMBER_ARGS, HIDE_LAYER, CONTENT_ARGS, \
+ COLLAPSIBLE, BLOCK_LAYER, CONTENT_BLOCKS
+from StringIO import StringIO
+import os.path
+from gettext import gettext as _
+import logging
+_logger = logging.getLogger('turtleart-activity')
+
+class pythonerror(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+'''
+The strategy for mixing numbers and strings is to first try
+converting the string to a float; then if the string is a single
+character, try converting it to an ord; finally, just treat it as a
+string. Numbers appended to strings are first trreated as ints, then
+floats.
+'''
+def convert(x, fn, try_ord=True):
+ try:
+ return fn(x)
+ except ValueError:
+ if try_ord:
+ xx, flag = chr_to_ord(x)
+ if flag:
+ return fn(xx)
+ return x
+
+def chr_to_ord(x):
+ """ Try to comvert a string to an ord """
+ if strtype(x) and len(x) == 1:
+ try:
+ return ord(x[0]), True
+ except ValueError:
+ return x, False
+ return x, False
+
+def strtype(x):
+ """ Is x a string type? """
+ if type(x) == str:
+ return True
+ if type(x) == unicode:
+ return True
+ return False
+
+def magnitude(pos):
+ """ Calculate the magnitude of the distance between to blocks. """
+ x, y = pos
+ return x*x+y*y
+
+def json_load(text):
+ """ Load JSON data using what ever resources are available. """
+ if OLD_SUGAR_SYSTEM is True:
+ _listdata = json.read(text)
+ else:
+ # strip out leading and trailing whitespace, nulls, and newlines
+ text = text.lstrip()
+ text = text.replace('\12','')
+ text = text.replace('\00','')
+ _io = StringIO(text.rstrip())
+ _listdata = jload(_io)
+ # json converts tuples to lists, so we need to convert back,
+ return _tuplify(_listdata)
+
+def _tuplify(tup):
+ """ Convert to tuples """
+ if type(tup) is not list:
+ return tup
+ return tuple(map(_tuplify, tup))
+
+def get_id(connection):
+ """ Get a connection block ID. """
+ if connection is None:
+ return None
+ return connection.id
+
+def json_dump(data):
+ """ Save data using available JSON tools. """
+ if OLD_SUGAR_SYSTEM is True:
+ return json.write(data)
+ else:
+ _io = StringIO()
+ jdump(data, _io)
+ return _io.getvalue()
+
+def get_load_name(suffix, load_save_folder):
+ """ Open a load file dialog. """
+ _dialog = gtk.FileChooserDialog("Load...", None,
+ gtk.FILE_CHOOSER_ACTION_OPEN,
+ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN, gtk.RESPONSE_OK))
+ _dialog.set_default_response(gtk.RESPONSE_OK)
+ return do_dialog(_dialog, suffix, load_save_folder)
+
+def get_save_name(suffix, load_save_folder, save_file_name):
+ """ Open a save file dialog. """
+ _dialog = gtk.FileChooserDialog("Save...", None,
+ gtk.FILE_CHOOSER_ACTION_SAVE,
+ (gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE,
+ gtk.RESPONSE_OK))
+ _dialog.set_default_response(gtk.RESPONSE_OK)
+ if save_file_name is not None:
+ _dialog.set_current_name(save_file_name+suffix)
+ return do_dialog(_dialog, suffix, load_save_folder)
+
+#
+# We try to maintain read-compatibility with all versions of Turtle Art.
+# Try pickle first; then different versions of json.
+#
+def data_from_file(ta_file):
+ """ Open the .ta file, ignoring any .png file that might be present. """
+ file_handle = open(ta_file, "r")
+ try:
+ _data = pickle.load(file_handle)
+ except:
+ # Rewind necessary because of failed pickle.load attempt
+ file_handle.seek(0)
+ _text = file_handle.read()
+ _data = data_from_string(_text)
+ file_handle.close()
+ return _data
+
+def data_from_string(text):
+ """ JSON load data from a string. """
+ return json_load(text)
+
+def data_to_file(data, ta_file):
+ """ Write data to a file. """
+ file_handle = file(ta_file, "w")
+ file_handle.write(data_to_string(data))
+ file_handle.close()
+
+def data_to_string(data):
+ """ JSON dump a string. """
+ return json_dump(data)
+
+def do_dialog(dialog, suffix, load_save_folder):
+ """ Open a file dialog. """
+ _result = None
+ file_filter = gtk.FileFilter()
+ file_filter.add_pattern('*'+suffix)
+ file_filter.set_name("Turtle Art")
+ dialog.add_filter(file_filter)
+ dialog.set_current_folder(load_save_folder)
+ _response = dialog.run()
+ if _response == gtk.RESPONSE_OK:
+ _result = dialog.get_filename()
+ load_save_folder = dialog.get_current_folder()
+ dialog.destroy()
+ return _result, load_save_folder
+
+def save_picture(canvas, file_name=''):
+ """ Save the canvas to a file. """
+ _pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, canvas.width,
+ canvas.height)
+ _pixbuf.get_from_drawable(canvas.canvas.images[0],
+ canvas.canvas.images[0].get_colormap(),
+ 0, 0, 0, 0, canvas.width, canvas.height)
+ if file_name != '':
+ _pixbuf.save(file_name, 'png')
+ return _pixbuf
+
+def save_svg(string, file_name):
+ """ Write a string to a file. """
+ file_handle = file(file_name, "w")
+ file_handle.write(string)
+ file_handle.close()
+
+def get_pixbuf_from_journal(dsobject, w, h):
+ """ Load a pixbuf from a Journal object. """
+ try:
+ _pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(dsobject.file_path,
+ int(w), int(h))
+ except:
+ try:
+ _pixbufloader = \
+ gtk.gdk.pixbuf_loader_new_with_mime_type('image/png')
+ _pixbufloader.set_size(min(300, int(w)), min(225, int(h)))
+ _pixbufloader.write(dsobject.metadata['preview'])
+ _pixbufloader.close()
+ _pixbuf = _pixbufloader.get_pixbuf()
+ except:
+ _pixbuf = None
+ return _pixbuf
+
+def get_path(activity, subpath ):
+ """ Find a Rainbow-approved place for temporary files. """
+ try:
+ return(os.path.join(activity.get_activity_root(), subpath))
+ except:
+ # Early versions of Sugar didn't support get_activity_root()
+ return(os.path.join(os.environ['HOME'], ".sugar/default",
+ "org.laptop.TurtleArtActivity", subpath))
+
+def image_to_base64(pixbuf, activity):
+ """ Convert an image to base64 """
+ _file_name = os.path.join(get_path(activity, 'instance'), '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
+
+def movie_media_type(name):
+ """ Is it movie media? """
+ return name.endswith(('.ogv', '.vob', '.mp4', '.wmv', '.mov', '.mpeg'))
+
+def audio_media_type(name):
+ """ Is it audio media? """
+ return name.endswith(('.ogg', '.oga', '.m4a'))
+
+def image_media_type(name):
+ """ Is it image media? """
+ return name.endswith(('.png', '.jpg', '.jpeg', '.gif', '.tiff', '.tif',
+ '.svg'))
+def text_media_type(name):
+ """ Is it text media? """
+ return name.endswith(('.txt', '.py', '.lg', '.doc', '.rtf'))
+
+def round_int(num):
+ """ Remove trailing decimal places if number is an int """
+ try:
+ float(num)
+ except TypeError:
+ _logger.debug("error trying to convert %s to number" % (str(num)))
+ raise pythonerror("#syntaxerror")
+
+ if int(float(num)) == num:
+ return int(num)
+ else:
+ if float(num)<0:
+ _nn = int((float(num)-0.005)*100)/100.
+ else:
+ _nn = int((float(num)+0.005)*100)/100.
+ if int(float(_nn)) == _nn:
+ return int(_nn)
+ return _nn
+
+def calc_image_size(spr):
+ """ Calculate the maximum size for placing an image onto a sprite. """
+ return spr.label_safe_width(), spr.label_safe_height()
+
+
+
+# Collapsible stacks live between 'sandwichtop' and 'sandwichbottom' blocks
+
+def reset_stack_arm(top):
+ """ When we undock, retract the 'arm' that extends from 'sandwichtop'. """
+ if top is not None and top.name == 'sandwichtop':
+ if top.ey > 0:
+ top.reset_y()
+
+def grow_stack_arm(top):
+ """ When we dock, grow an 'arm' from 'sandwichtop'. """
+ if top is not None and top.name == 'sandwichtop':
+ _bot = find_sandwich_bottom(top)
+ if _bot is None:
+ return
+ if top.ey > 0:
+ top.reset_y()
+ _ty = top.spr.get_xy()[1]
+ _th = top.spr.get_dimensions()[1]
+ _by = _bot.spr.get_xy()[1]
+ _dy = _by-(_ty + _th)
+ if _dy > 0:
+ top.expand_in_y(_dy/top.scale)
+ top.refresh()
+
+def find_sandwich_top(blk):
+ """ Find the sandwich top above this block. """
+ # Always follow the main branch of a flow: the first connection.
+ _blk = blk.connections[0]
+ while _blk is not None:
+ if _blk.name in COLLAPSIBLE:
+ return None
+ if _blk.name in ['repeat', 'if', 'ifelse', 'forever', 'while']:
+ if blk != _blk.connections[len(_blk.connections) - 1]:
+ return None
+ if _blk.name == 'sandwichtop' or _blk.name == 'sandwichtop2':
+ return _blk
+ blk = _blk
+ _blk = _blk.connections[0]
+ return None
+
+def find_sandwich_bottom(blk):
+ """ Find the sandwich bottom below this block. """
+ # Always follow the main branch of a flow: the last connection.
+ _blk = blk.connections[len(blk.connections) - 1]
+ while _blk is not None:
+ if _blk.name == 'sandwichtop' or _blk.name == 'sandwichtop2':
+ return None
+ if _blk.name in COLLAPSIBLE:
+ return _blk
+ _blk = _blk.connections[len(_blk.connections) - 1]
+ return None
+
+def find_sandwich_top_below(blk):
+ """ Find the sandwich top below this block. """
+ if blk.name == 'sandwichtop' or blk.name == 'sandwichtop2':
+ return blk
+ # Always follow the main branch of a flow: the last connection.
+ _blk = blk.connections[len(blk.connections) - 1]
+ while _blk is not None:
+ if _blk.name == 'sandwichtop' or _blk.name == 'sandwichtop2':
+ return _blk
+ _blk = _blk.connections[len(_blk.connections) - 1]
+ return None
+
+def restore_stack(top):
+ """ Restore the blocks between the sandwich top and sandwich bottom. """
+ _group = find_group(top.connections[len(top.connections) - 1])
+ _hit_bottom = False
+ _bot = find_sandwich_bottom(top)
+ for _blk in _group:
+ if not _hit_bottom and _blk == _bot:
+ _hit_bottom = True
+ if len(_blk.values) == 0:
+ _blk.values.append(0)
+ else:
+ _blk.values[0] = 0
+ _olddx = _blk.docks[1][2]
+ _olddy = _blk.docks[1][3]
+ # Replace 'sandwichcollapsed' shape with 'sandwichbottom' shape
+ _blk.name = 'sandwichbottom'
+ _blk.spr.set_label(' ')
+ _blk.svg.set_show(False)
+ _blk.svg.set_hide(True)
+ _blk.refresh()
+ # Redock to previous block in group
+ _you = _blk.connections[0]
+ (_yx, _yy) = _you.spr.get_xy()
+ _yd = _you.docks[len(_you.docks) - 1]
+ (_bx, _by) = _blk.spr.get_xy()
+ _dx = _yx + _yd[2] - _blk.docks[0][2] - _bx
+ _dy = _yy + _yd[3] - _blk.docks[0][3] - _by
+ _blk.spr.move_relative((_dx, _dy))
+ # Since the shapes have changed, the dock positions have too.
+ _newdx = _blk.docks[1][2]
+ _newdy = _blk.docks[1][3]
+ _dx += _newdx - _olddx
+ _dy += _newdy - _olddy
+ else:
+ if not _hit_bottom:
+ _blk.spr.set_layer(BLOCK_LAYER)
+ _blk.status = None
+ else:
+ _blk.spr.move_relative((_dx, _dy))
+ # Add 'sandwichtop' arm
+ top.name = 'sandwichtop'
+ top.refresh()
+ grow_stack_arm(top)
+
+def uncollapse_forks(top, looping=False):
+ """ From the top, find and restore any collapsible stacks on forks. """
+ if top == None:
+ return
+ if looping and top.name == 'sandwichtop' or top.name == 'sandwichtop2':
+ restore_stack(top)
+ return
+ if len(top.connections) == 0:
+ return
+ _blk = top.connections[len(top.connections) - 1]
+ while _blk is not None:
+ if _blk.name in COLLAPSIBLE:
+ return
+ if _blk.name == 'sandwichtop' or _blk.name == 'sandwichtop2':
+ restore_stack(_blk)
+ return
+ # Follow a fork
+ if _blk.name in ['repeat', 'if', 'ifelse', 'forever', 'while', 'until']:
+ top = find_sandwich_top_below(
+ _blk.connections[len(_blk.connections) - 2])
+ if top is not None:
+ uncollapse_forks(top, True)
+ if _blk.name == 'ifelse':
+ top = find_sandwich_top_below(
+ _blk.connections[len(_blk.connections) - 3])
+ if top is not None:
+ uncollapse_forks(top, True)
+ _blk = _blk.connections[len(_blk.connections) - 1]
+ return
+
+def collapse_stack(top):
+ """ Hide all the blocks between the sandwich top and sandwich bottom. """
+ # First uncollapse any nested stacks
+ if top == None or top.spr == None:
+ return
+ uncollapse_forks(top)
+ _hit_bottom = False
+ _bot = find_sandwich_bottom(top)
+ _group = find_group(top.connections[len(top.connections) - 1])
+ for _blk in _group:
+ if not _hit_bottom and _blk == _bot:
+ _hit_bottom = True
+ # Replace 'sandwichbottom' shape with 'sandwichcollapsed' shape
+ if len(_blk.values) == 0:
+ _blk.values.append(1)
+ else:
+ _blk.values[0] = 1
+ _olddx = _blk.docks[1][2]
+ _olddy = _blk.docks[1][3]
+ _blk.name = 'sandwichcollapsed'
+ _blk.svg.set_show(True)
+ _blk.svg.set_hide(False)
+ _blk._dx = 0
+ _blk._ey = 0
+ _blk.spr.set_label(' ')
+ _blk.resize()
+ _blk.spr.set_label(_('click to open'))
+ _blk.resize()
+ # Redock to sandwich top in group
+ _you = find_sandwich_top(_blk)
+ (_yx, _yy) = _you.spr.get_xy()
+ _yd = _you.docks[len(_you.docks) - 1]
+ (_bx, _by) = _blk.spr.get_xy()
+ _dx = _yx + _yd[2] - _blk.docks[0][2] - _bx
+ _dy = _yy + _yd[3] - _blk.docks[0][3] - _by
+ _blk.spr.move_relative((_dx, _dy))
+ # Since the shapes have changed, the dock positions have too.
+ _newdx = _blk.docks[1][2]
+ _newdy = _blk.docks[1][3]
+ _dx += _newdx - _olddx
+ _dy += _newdy - _olddy
+ else:
+ if not _hit_bottom:
+ _blk.spr.set_layer(HIDE_LAYER)
+ _blk.status = 'collapsed'
+ else:
+ _blk.spr.move_relative((_dx, _dy))
+ # Remove 'sandwichtop' arm
+ top.name = 'sandwichtop2'
+ top.refresh()
+
+def collapsed(blk):
+ """ Is this stack collapsed? """
+ if blk is not None and blk.name in COLLAPSIBLE and\
+ len(blk.values) == 1 and blk.values[0] != 0:
+ return True
+ return False
+
+def collapsible(blk):
+ """ Can this stack be collapsed? """
+ if blk is None or blk.name not in COLLAPSIBLE:
+ return False
+ if find_sandwich_top(blk) is None:
+ return False
+ return True
+
+def hide_button_hit(spr, x, y):
+ """ Did the sprite's hide (contract) button get hit? """
+ _red, _green, _blue, _alpha = spr.get_pixel((x, y))
+ if (_red == 255 and _green == 0) or _green == 255:
+ return True
+ else:
+ return False
+
+def show_button_hit(spr, x, y):
+ """ Did the sprite's show (expand) button get hit? """
+ _red, _green, _blue, _alpha = spr.get_pixel((x, y))
+ if _green == 254:
+ return True
+ else:
+ return False
+
+def numeric_arg(value):
+ """ Dock test: looking for a numeric value """
+ if type(convert(value, float)) is float:
+ return True
+ return False
+
+def zero_arg(value):
+ """ Dock test: looking for a zero argument """
+ if numeric_arg(value):
+ if convert(value, float) == 0:
+ return True
+ return False
+
+def neg_arg(value):
+ """ Dock test: looking for a negative argument """
+ if numeric_arg(value):
+ if convert(value, float) < 0:
+ return True
+ return False
+
+def 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 arithmetic_check(blk1, blk2, dock1, dock2):
+ """ Dock strings only if they convert to numbers. Avoid /0 and root(-1)"""
+ if blk1 == None or blk2 == None:
+ return True
+ if blk1.name in ['sqrt', 'number', 'string'] and\
+ blk2.name in ['sqrt', 'number', 'string']:
+ if blk1.name == 'number' or blk1.name == 'string':
+ if not numeric_arg(blk1.values[0]) or neg_arg(blk1.values[0]):
+ return False
+ elif blk2.name == 'number' or blk2.name == 'string':
+ if not numeric_arg(blk2.values[0]) or neg_arg(blk2.values[0]):
+ return False
+ elif blk1.name in ['division2', 'number', 'string'] and\
+ blk2.name in ['division2', 'number', 'string']:
+ if blk1.name == 'number' or blk1.name == 'string':
+ if not numeric_arg(blk1.values[0]):
+ return False
+ if dock2 == 2 and zero_arg(blk1.values[0]):
+ return False
+ elif blk2.name == 'number' or blk2.name == 'string':
+ if not numeric_arg(blk2.values[0]):
+ return False
+ if dock1 == 2 and zero_arg(blk2.values[0]):
+ return False
+ elif blk1.name in ['product2', 'minus2', 'random', 'remainder2',
+ 'string'] and\
+ blk2.name in ['product2', 'minus2', 'random', 'remainder2',
+ 'string']:
+ if blk1.name == 'string':
+ if not numeric_arg(blk1.values[0]):
+ return False
+ elif blk1.name == 'string':
+ if not numeric_arg(blk2.values[0]):
+ return False
+ elif blk1.name in ['greater2', 'less2'] and blk2.name == 'string':
+ # Non-numeric stings are OK if only both args are strings;
+ # Lots of test conditions...
+ if dock1 == 1 and blk1.connections[2] is not None:
+ if blk1.connections[2].name == 'number':
+ if not numeric_arg(blk2.values[0]):
+ return False
+ elif dock1 == 2 and blk1.connections[1] is not None:
+ if blk1.connections[1].name == 'number':
+ if not numeric_arg(blk2.values[0]):
+ return False
+ elif blk2.name in ['greater2', 'less2'] and blk1.name == 'string':
+ if dock2 == 1 and blk2.connections[2] is not None:
+ if blk2.connections[2].name == 'number':
+ if not numeric_arg(blk1.values[0]):
+ return False
+ elif dock2 == 2 and blk2.connections[1] is not None:
+ if blk2.connections[1].name == 'number':
+ if not numeric_arg(blk1.values[0]):
+ return False
+ elif blk1.name in ['greater2', 'less2'] and blk2.name == 'number':
+ if dock1 == 1 and blk1.connections[2] is not None:
+ if blk1.connections[2].name == 'string':
+ if not numeric_arg(blk1.connections[2].values[0]):
+ return False
+ elif dock1 == 2 and blk1.connections[1] is not None:
+ if blk1.connections[1].name == 'string':
+ if not numeric_arg(blk1.connections[1].values[0]):
+ return False
+ elif blk2.name in ['greater2', 'less2'] and blk1.name == 'number':
+ if dock2 == 1 and blk2.connections[2] is not None:
+ if blk2.connections[2].name == 'string':
+ if not numeric_arg(blk2.connections[2].values[0]):
+ return False
+ elif dock2 == 2 and blk2.connections[1] is not None:
+ if blk2.connections[1].name == 'string':
+ if not numeric_arg(blk2.connections[1].values[0]):
+ return False
+ return True
+
+def xy(event):
+ """ Where is the mouse event? """
+ return map(int, event.get_coords())
+
+"""
+Utilities related to finding blocks in stacks.
+"""
+
+def find_block_to_run(blk):
+ """ Find a stack to run (any stack without a 'def action'on the top). """
+ _top = find_top_block(blk)
+ if blk == _top and blk.name[0:3] is not 'def':
+ return True
+ else:
+ return False
+
+def find_top_block(blk):
+ """ Find the top block in a stack. """
+ if blk is None:
+ return None
+ if len(blk.connections) == 0:
+ return blk
+ while blk.connections[0] is not None:
+ blk = blk.connections[0]
+ return blk
+
+def find_start_stack(blk):
+ """ Find a stack with a 'start' block on top. """
+ if blk is None:
+ return False
+ if find_top_block(blk).name == 'start':
+ return True
+ else:
+ return False
+
+def find_group(blk):
+ """ Find the connected group of block in a stack. """
+ if blk is None:
+ return []
+ _group = [blk]
+ if blk.connections is not None:
+ for _blk2 in blk.connections[1:]:
+ if _blk2 is not None:
+ _group.extend(find_group(_blk2))
+ return _group
+
+def find_blk_below(blk, name):
+ """ Find a specific block below this block. """
+ if blk == None or len(blk.connections) == 0:
+ return
+ _group = find_group(blk)
+ for _gblk in _group:
+ if _gblk.name == name:
+ return _gblk
+ return None
+
+def olpc_xo_1():
+ """ Is the an OLPC XO-1 or XO-1.5? """
+ return os.path.exists('/etc/olpc-release') or \
+ os.path.exists('/sys/power/olpc-pm')
+
+def walk_stack(tw, blk):
+ """ Convert blocks to logo psuedocode. """
+ top = find_top_block(blk)
+ if blk == top:
+ code = tw.lc.run_blocks(top, tw.block_list.list, False)
+ return code
+ else:
+ return []
diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py
new file mode 100644
index 0000000..3d62a3f
--- /dev/null
+++ b/TurtleArt/tawindow.py
@@ -0,0 +1,2336 @@
+# -*- 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
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+import os
+import os.path
+from math import atan2, pi
+DEGTOR = 2*pi/360
+from gettext import gettext as _
+
+try:
+ from sugar.graphics.objectchooser import ObjectChooser
+ from sugar.datastore import datastore
+ from sugar import profile
+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, \
+ 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, RETURN, \
+ DEAD_DICTS, DEAD_KEYS, TEMPLATES, PYTHON_SKIN, \
+ PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK, OLD_NAMES, \
+ BOOLEAN_STYLE, BLOCK_NAMES
+from talogo import LogoCode, stop_logo
+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, olpc_xo_1, \
+ dock_dx_dy, data_to_string
+from tasprite_factory import SVG, svg_str_to_pixbuf, svg_from_file
+from sprites import Sprites, Sprite
+
+import logging
+_logger = logging.getLogger('turtleart-activity')
+
+class TurtleArtWindow():
+ """ TurtleArt Window class abstraction """
+ timeout_tag = [0]
+
+ def __init__(self, win, path, parent=None, mycolors=None):
+ self._loaded_project = ""
+ self.win = None
+ self.parent = parent
+ if type(win) == gtk.DrawingArea:
+ self.interactive_mode = True
+ self.window = win
+ self.window.set_flags(gtk.CAN_FOCUS)
+ 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()
+ 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()
+ else:
+ _logger.debug("bad win type %s" % (type(win)))
+
+ if self.running_sugar:
+ self.activity = parent
+ self.nick = profile.get_nick_name()
+ else:
+ self.activity = None
+ self.nick = None
+
+ self.path = path
+ self.load_save_folder = os.path.join(path, 'samples')
+ self.save_folder = None
+ self.save_file_name = None
+ self.width = gtk.gdk.screen_width()
+ self.height = gtk.gdk.screen_height()
+
+ self.keypress = ""
+ self.keyvalue = 0
+ self.dead_key = ""
+ self.mouse_flag = 0
+ self.mouse_x = 0
+ self.mouse_y = 0
+
+ self.orientation = HORIZONTAL_PALETTE
+ if olpc_xo_1():
+ self.lead = 1.0
+ self.scale = 0.67
+ if self.running_sugar and not self.activity.new_sugar_system:
+ self.orientation = VERTICAL_PALETTE
+ else:
+ self.lead = 1.0
+ self.scale = 1.0
+ self.block_scale = BLOCK_SCALE
+ self.trash_scale = 0.5
+ self.myblock = None
+ self.nop = 'nop'
+ self.loaded = 0
+ self.step_time = 0
+ self.hide = False
+ self.palette = True
+ self.coord_scale = 1
+ self.buddies = []
+ self.saved_string = ''
+ self.dx = 0
+ self.dy = 0
+ self.media_shapes = {}
+ self.cartesian = False
+ self.polar = False
+ self.overlay_shapes = {}
+ self.toolbar_shapes = {}
+ self.toolbar_offset = 0
+ self.status_spr = None
+ self.status_shapes = {}
+ self.toolbar_spr = None
+ self.palette_sprs = []
+ self.palettes = []
+ self.palette_button = []
+ self.trash_index = PALETTE_NAMES.index('trash')
+ self.trash_stack = []
+ self.selected_palette = None
+ self.previous_palette = None
+ self.selectors = []
+ self.selected_selector = None
+ self.previous_selector = None
+ self.selector_shapes = []
+ self.selected_blk = None
+ self.selected_spr = None
+ self.drag_group = None
+ self.drag_turtle = 'move', 0, 0
+ self.drag_pos = 0, 0
+ self.paste_offset = 20
+ self.block_list = Blocks(self.scale)
+ if self.interactive_mode:
+ self.sprite_list = Sprites(self.window, self.area, self.gc)
+ else:
+ self.sprite_list = None # Sprites(self.window, None, self.gc)
+ self.turtles = Turtles(self.sprite_list)
+ if mycolors == None:
+ Turtle(self.turtles, 1)
+ else:
+ Turtle(self.turtles, 1, mycolors.split(','))
+ self.active_turtle = self.turtles.get_turtle(1)
+ self.saving_svg = False
+ self.svg_string = ''
+ self.selected_turtle = None
+ self.canvas = TurtleGraphics(self, self.width, self.height)
+ self.titlex = -(self.canvas.width*TITLEXY[0])/(self.coord_scale*2)
+ self.leftx = -(self.canvas.width*TITLEXY[0])/(self.coord_scale*2)
+ self.rightx = 0
+ self.titley = (self.canvas.height*TITLEXY[1])/(self.coord_scale*2)
+ self.topy = (self.canvas.height*(TITLEXY[1]-0.125))/(self.coord_scale*2)
+ self.bottomy = 0
+ self.lc = LogoCode(self)
+ self.saved_pictures = []
+
+ if self.interactive_mode:
+ self._setup_misc()
+ self._show_toolbar_palette(0, False)
+ self.block_operation = ''
+
+ def _setup_events(self):
+ """ Register the events we listen to. """
+ self.window.add_events(gtk.gdk.BUTTON_PRESS_MASK)
+ self.window.add_events(gtk.gdk.BUTTON_RELEASE_MASK)
+ self.window.add_events(gtk.gdk.POINTER_MOTION_MASK)
+ self.window.add_events(gtk.gdk.KEY_PRESS_MASK)
+ self.window.connect("expose-event", self._expose_cb)
+ self.window.connect("button-press-event", self._buttonpress_cb)
+ self.window.connect("button-release-event", self._buttonrelease_cb)
+ self.window.connect("motion-notify-event", self._move_cb)
+ self.window.connect("key_press_event", self._keypress_cb)
+
+ def _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:]
+ else:
+ file_name = _name
+ self.media_shapes[_name] = svg_str_to_pixbuf(svg_from_file(
+ "%s/images/%s.svg" % (self.path, file_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,
+ int(self.width/2-600), int(self.height/2-450),
+ svg_str_to_pixbuf(svg_from_file(
+ "%s/images/%s.svg" % (self.path, _name))))
+ self.overlay_shapes[_name].hide()
+ self.overlay_shapes[_name].type = 'overlay'
+
+ if not self.running_sugar:
+ offset = 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'
+ self.toolbar_shapes['stopiton'].hide()
+
+ def sharing(self):
+ """ Is a chattube available for share? """
+ if self.running_sugar and hasattr(self.activity, 'chattube') and\
+ self.activity.chattube is not None:
+ return True
+ return False
+
+ def is_project_empty(self):
+ """ Check to see if project has any blocks in use """
+ return len(self.just_blocks()) == 1
+
+ def _expose_cb(self, win, event):
+ """ Repaint """
+ self.sprite_list.refresh(event)
+ # self.canvas.cr_expose(event)
+ return True
+
+ def eraser_button(self):
+ """ Eraser_button (hide status block when clearing the screen.) """
+ if self.status_spr is not None:
+ self.status_spr.hide()
+ self.lc.prim_clear()
+ self.display_coordinates()
+
+ def run_button(self, time):
+ """ Run turtle! """
+ if self.running_sugar:
+ self.activity.recenter()
+ # Look for a 'start' block
+ for blk in self.just_blocks():
+ if find_start_stack(blk):
+ self.step_time = time
+ print "running stack starting from %s" % (blk.name)
+ self._run_stack(blk)
+ return
+ # If there is no 'start' block, run stacks that aren't 'def action'
+ for blk in self.just_blocks():
+ if find_block_to_run(blk):
+ self.step_time = time
+ print "running stack starting from %s" % (blk.name)
+ self._run_stack(blk)
+ return
+
+ def stop_button(self):
+ """ Stop button """
+ stop_logo(self)
+
+ def set_userdefined(self):
+ """ Change icon for user-defined blocks after Python code is loaded. """
+ for blk in self.just_blocks():
+ if blk.name in PYTHON_SKIN:
+ x, y = self._calc_image_offset('pythonon', blk.spr)
+ blk.set_image(self.media_shapes['pythonon'], x, y)
+ self._resize_skin(blk)
+ self.nop = 'pythonloaded'
+
+ def set_fullscreen(self):
+ """ Enter fullscreen mode """
+ if self.running_sugar:
+ self.activity.fullscreen()
+ self.activity.recenter()
+
+ def set_cartesian(self, flag):
+ """ Turn on/off Cartesian coordinates """
+ if flag:
+ if self.coord_scale == 1:
+ self.overlay_shapes['Cartesian_labeled'].set_layer(
+ OVERLAY_LAYER)
+ else:
+ self.overlay_shapes['Cartesian'].set_layer(OVERLAY_LAYER)
+ self.cartesian = True
+ else:
+ if self.coord_scale == 1:
+ self.overlay_shapes['Cartesian_labeled'].hide()
+ else:
+ self.overlay_shapes['Cartesian'].hide()
+ self.cartesian = False
+
+ def set_polar(self, flag):
+ """ Turn on/off polar coordinates """
+ if flag:
+ self.overlay_shapes['polar'].set_layer(OVERLAY_LAYER)
+ self.polar = True
+ else:
+ self.overlay_shapes['polar'].hide()
+ self.polar = 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]
+ showing = False
+ if shape in shape._sprites.list:
+ shape.hide()
+ showing = True
+ self.overlay_shapes[_name] = Sprite(self.sprite_list,
+ int(self.width / 2 - 600),
+ int(self.height / 2 - 450),
+ svg_str_to_pixbuf(
+ svg_from_file("%s/images/%s.svg" % (self.path, _name))))
+ if showing:
+ self.overlay_shapes[_name].set_layer(OVERLAY_LAYER)
+ else:
+ self.overlay_shapes[_name].hide()
+ self.overlay_shapes[_name].type = 'overlay'
+ self.cartesian = False
+ self.polar = False
+ self.canvas.width = self.width
+ self.canvas.height = self.height
+ self.canvas.move_turtle()
+
+ def hideshow_button(self):
+ """ Hide/show button """
+ if not self.hide:
+ for blk in self.just_blocks():
+ blk.spr.hide()
+ self.hide_palette()
+ self.hide = True
+ else:
+ for blk in self.just_blocks():
+ if blk.status != 'collapsed':
+ blk.spr.set_layer(BLOCK_LAYER)
+ self.show_palette()
+ if self.activity is not None and self.activity.new_sugar_system:
+ self.activity.palette_buttons[0].set_icon(
+ PALETTE_NAMES[0] + 'on')
+ self.hide = False
+ self.canvas.canvas.inval()
+
+ def hideshow_palette(self, state):
+ """ Hide or show palette """
+ if not state:
+ self.palette = False
+ if self.running_sugar:
+ self.activity.do_hidepalette()
+ self.hide_palette()
+ else:
+ self.palette = True
+ if self.running_sugar:
+ self.activity.do_showpalette()
+ self.show_palette()
+
+ def show_palette(self, n=0):
+ """ Show palette """
+ 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:
+ self.toolbar_spr.set_layer(CATEGORY_LAYER)
+ self.palette = True
+
+ def hide_palette(self):
+ """ Hide the palette. """
+ self._hide_toolbar_palette()
+ self.palette_button[self.orientation].hide()
+ self.palette_button[2].hide()
+ if self.activity is None or not self.activity.new_sugar_system:
+ self.toolbar_spr.hide()
+ self.palette = False
+
+ def hideblocks(self):
+ """ Callback from 'hide blocks' block """
+ if not self.interactive_mode:
+ return
+ self.hide = False
+ self.hideshow_button()
+ if self.running_sugar:
+ self.activity.do_hide()
+
+ def showblocks(self):
+ """ Callback from 'show blocks' block """
+ if not self.interactive_mode:
+ return
+ self.hide = True
+ self.hideshow_button()
+ if self.running_sugar:
+ self.activity.do_show()
+
+ def resize_blocks(self, blocks=None):
+ """ Resize blocks or if blocks is None, all of the blocks """
+ if blocks is None:
+ blocks = self.just_blocks()
+
+ # We need to restore collapsed stacks before resizing.
+ for blk in blocks:
+ if blk.status == 'collapsed':
+ bot = find_sandwich_bottom(blk)
+ if collapsed(bot):
+ dy = bot.values[0]
+ restore_stack(find_sandwich_top(blk))
+ bot.values[0] = dy
+
+ # Do the resizing.
+ for blk in blocks:
+ blk.rescale(self.block_scale)
+ for blk in blocks:
+ self._adjust_dock_positions(blk)
+
+ # Re-collapsed stacks after resizing.
+ for blk in blocks:
+ if collapsed(blk):
+ collapse_stack(find_sandwich_top(blk))
+ for blk in blocks:
+ if blk.name == 'sandwichtop':
+ grow_stack_arm(blk)
+
+ # Resize the skins on some blocks: media content and Python
+ for blk in blocks:
+ if blk.name in BLOCKS_WITH_SKIN:
+ self._resize_skin(blk)
+
+ def _show_toolbar_palette(self, n, init_only=False):
+ """ Show the toolbar palettes, creating them on init_only """
+ if (self.activity is None or not self.activity.new_sugar_system) 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)
+
+
+ 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)
+
+ if init_only:
+ return
+
+ # Hide the previously displayed palette
+ self._hide_previous_palette()
+
+ self.selected_palette = n
+ self.previous_palette = self.selected_palette
+
+ if self.activity is None or not self.activity.new_sugar_system:
+ 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)):
+ 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)
+
+ if self.palettes[n] == []:
+ # Create 'proto' blocks for each palette entry
+ for i, name in enumerate(PALETTES[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:
+ self._proto_skin(name+'small', n, i)
+ elif name[:8] == 'template':
+ self._proto_skin(name[8:], n, i)
+ elif name[:7] == 'picture':
+ self._proto_skin(name[7:], n, i)
+ 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:
+ # Hide the selectors
+ for i in range(len(PALETTES)):
+ 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')
+ self.selected_palette = None
+ self.previous_palette = None
+
+ def _hide_previous_palette(self):
+ """ 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:
+ 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:
+ for blk in self.trash_stack:
+ for gblk in find_group(blk):
+ gblk.spr.hide()
+
+ def _horizontal_layout(self, x, y, blocks):
+ """ Position prototypes in a horizontal palette. """
+ _max_w = 0
+ for blk in blocks:
+ _w, _h = self._width_and_height(blk)
+ if y + _h > PALETTE_HEIGHT + self.toolbar_offset:
+ x += int(_max_w+3)
+ y = self.toolbar_offset + 3
+ _max_w = 0
+ (_bx, _by) = blk.spr.get_xy()
+ _dx = x-_bx
+ _dy = y-_by
+ for g in find_group(blk):
+ g.spr.move_relative((int(_dx), int(_dy)))
+ 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:
+ # 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
+ 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
+ # 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
+
+ def _layout_palette(self, n):
+ """ 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))
+ 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)))
+ self.palette_button[2].move((PALETTE_WIDTH-20,
+ self.toolbar_offset))
+ self.palette_sprs[n][self.orientation].set_layer(CATEGORY_LAYER)
+
+ def _buttonpress_cb(self, win, event):
+ """ Button press """
+ self.window.grab_focus()
+ x, y = xy(event)
+ self.mouse_flag = 1
+ self.mouse_x = x
+ self.mouse_y = y
+ self.button_press(event.get_state()&gtk.gdk.CONTROL_MASK, x, y)
+ return True
+
+ def button_press(self, mask, x, y, verbose=False):
+ if verbose:
+ print "processing remote button press: %d, %d" % (x, y)
+ self.block_operation = 'click'
+
+ # Unselect things that may have been selected earlier
+ if self.selected_blk is not None:
+ self._unselect_block()
+ self.selected_turtle = None
+ # Always hide the status layer on a click
+ if self.status_spr is not None:
+ self.status_spr.hide()
+
+ # Find out what was clicked
+ spr = self.sprite_list.find_sprite((x, y))
+ self.dx = 0
+ self.dy = 0
+ if spr is None:
+ return True
+ self.selected_spr = spr
+
+ # From the sprite at x, y, look for a corresponding block
+ blk = self.block_list.spr_to_block(spr)
+ if blk is not None:
+ if blk.type == 'block':
+ self.selected_blk = blk
+ self._block_pressed(x, y, blk)
+ elif blk.type == 'trash':
+ self._restore_from_trash(find_top_block(blk))
+ elif blk.type == 'proto':
+ if blk.name == 'restoreall':
+ self._restore_all_from_trash()
+ elif blk.name == 'restore':
+ self._restore_latest_from_trash()
+ elif blk.name == 'empty':
+ self._empty_trash()
+ elif MACROS.has_key(blk.name):
+ self._new_macro(blk.name, x + 20, y + 20)
+ else:
+ blk.highlight()
+ self._new_block(blk.name, x, y)
+ blk.unhighlight()
+ return True
+
+ # Next, look for a turtle
+ t = self.turtles.spr_to_turtle(spr)
+ if t is not None:
+ self.selected_turtle = t
+ self.canvas.set_turtle(self.turtles.get_turtle_key(t))
+ self._turtle_pressed(x, y)
+ return True
+
+ # Finally, check for anything else
+ if hasattr(spr, 'type'):
+ if spr.type == "canvas":
+ pass
+ # spr.set_layer(CANVAS_LAYER)
+ elif spr.type == 'selector':
+ self._select_category(spr)
+ elif spr.type == 'category':
+ if hide_button_hit(spr, x, y):
+ self.hideshow_palette(False)
+ elif spr.type == 'palette':
+ if spr.name == _('next'):
+ i = self.selected_palette + 1
+ if i == len(PALETTE_NAMES):
+ i = 0
+ if self.activity is None or \
+ not self.activity.new_sugar_system:
+ 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')
+ self.activity.palette_buttons[i].set_icon(
+ PALETTE_NAMES[i] + 'on')
+ self.show_palette(i)
+ else:
+ self.orientation = 1 - self.orientation
+ self.palette_button[self.orientation].set_layer(TAB_LAYER)
+ self.palette_button[1 - self.orientation].hide()
+ self.palette_sprs[self.selected_palette][
+ 1 - self.orientation].hide()
+ self._layout_palette(self.selected_palette)
+ self.show_palette(self.selected_palette)
+ elif spr.type == 'toolbar':
+ self._select_toolbar_button(spr)
+ return True
+
+ def _select_category(self, spr):
+ """ Select a category from the toolbar (old Sugar systems only). """
+ i = self.selectors.index(spr)
+ spr.set_shape(self.selector_shapes[i][1])
+ if self.selected_selector is not None:
+ j = self.selectors.index(self.selected_selector)
+ if i == j:
+ return
+ self.selected_selector.set_shape(self.selector_shapes[j][0])
+ self.previous_selector = self.selected_selector
+ self.selected_selector = spr
+ self.show_palette(i)
+
+ def _select_toolbar_button(self, spr):
+ """ Select a toolbar button (Used when not running Sugar). """
+ if not hasattr(spr, 'name'):
+ return
+ if spr.name == 'run-fastoff':
+ self.lc.trace = 0
+ self.run_button(0)
+ elif spr.name == 'run-slowoff':
+ self.lc.trace = 0
+ self.run_button(3)
+ elif spr.name == 'debugoff':
+ self.lc.trace = 1
+ self.run_button(6)
+ elif spr.name == 'stopiton':
+ self.stop_button()
+ self.toolbar_shapes['stopiton'].hide()
+ elif spr.name == 'eraseron':
+ self.eraser_button()
+ elif spr.name == 'hideshowoff':
+ self.hideshow_button()
+
+ def _put_in_trash(self, blk, x=0, y=0):
+ """ Put a group of blocks into the trash. """
+ self.trash_stack.append(blk)
+ group = find_group(blk)
+ for gblk in group:
+ if gblk.status == 'collapsed':
+ # Collapsed stacks are restored for rescaling
+ # and then recollapsed after they are moved to the trash.
+ bot = find_sandwich_bottom(gblk)
+ if collapsed(bot):
+ dy = bot.values[0]
+ restore_stack(find_sandwich_top(gblk))
+ bot.values[0] = dy
+ gblk.type = 'trash'
+ gblk.rescale(self.trash_scale)
+ blk.spr.move((x, y))
+ for gblk in group:
+ self._adjust_dock_positions(gblk)
+
+ # Re-collapsing any stacks we had restored for scaling
+ for gblk in group:
+ if collapsed(gblk):
+ collapse_stack(find_sandwich_top(gblk))
+
+ # And resize any skins.
+ for gblk in group:
+ if gblk.name in BLOCKS_WITH_SKIN:
+ self._resize_skin(gblk)
+
+ # self.show_palette(self.trash_index)
+ if self.selected_palette != self.trash_index:
+ for gblk in group:
+ gblk.spr.hide()
+
+ def _restore_all_from_trash(self):
+ """ Restore all the blocks in the trash can. """
+ for blk in self.block_list.list:
+ if blk.type == 'trash':
+ self._restore_from_trash(blk)
+
+ def _restore_latest_from_trash(self):
+ """ Restore most recent blocks from the trash can. """
+ if len(self.trash_stack) == 0:
+ return
+ self._restore_from_trash(self.trash_stack[len(self.trash_stack) - 1])
+
+ def _restore_from_trash(self, blk):
+ group = find_group(blk)
+ for gblk in group:
+ gblk.rescale(self.block_scale)
+ gblk.spr.set_layer(BLOCK_LAYER)
+ x, y = gblk.spr.get_xy()
+ if self.orientation == 0:
+ gblk.spr.move((x, y + PALETTE_HEIGHT + self.toolbar_offset))
+ else:
+ gblk.spr.move((x + PALETTE_WIDTH, y))
+ gblk.type = 'block'
+ for gblk in group:
+ self._adjust_dock_positions(gblk)
+ # If the stack had been collapsed before going into the trash,
+ # collapse it again now.
+ for gblk in group:
+ if collapsed(gblk):
+ collapse_stack(find_sandwich_top(gblk))
+ # And resize any skins.
+ for gblk in group:
+ if gblk.name in BLOCKS_WITH_SKIN:
+ self._resize_skin(gblk)
+
+ self.trash_stack.remove(blk)
+
+ def _empty_trash(self):
+ """ Permanently remove all blocks presently in the trash can. """
+ for blk in self.block_list.list:
+ if blk.type == 'trash':
+ blk.type = 'deleted'
+ blk.spr.hide()
+ self.trash_stack = []
+
+ def _in_the_trash(self, x, y):
+ """ Is x, y over the trash can? """
+ """
+ if self.selected_palette == self.trash_index and \
+ self.palette_sprs[self.trash_index][self.orientation].hit((x, y)):
+ return True
+ """
+ if self.selected_palette is not None and\
+ self.palette_sprs[self.selected_palette][self.orientation].hit(
+ (x, y)):
+ return True
+ return False
+
+ def _block_pressed(self, x, y, blk):
+ """ Block pressed """
+ if blk is not None:
+ blk.highlight()
+ self._disconnect(blk)
+ self.drag_group = find_group(blk)
+ (sx, sy) = blk.spr.get_xy()
+ self.drag_pos = x-sx, y-sy
+ for blk in self.drag_group:
+ if blk.status != 'collapsed':
+ blk.spr.set_layer(TOP_LAYER)
+ self.saved_string = blk.spr.labels[0]
+
+ def _unselect_block(self):
+ """ Unselect block """
+ # After unselecting a 'number' block, we need to check its value
+ if self.selected_blk.name == 'number':
+ self._number_check()
+ elif self.selected_blk.name == 'string':
+ self._string_check()
+ self.selected_blk.unhighlight()
+ self.selected_blk = None
+
+ def _new_block(self, name, x, y):
+ """ 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)
+ else:
+ newblk = Block(self.block_list, self.sprite_list, name, x - 20,
+ y - 20, 'block', [], self.block_scale)
+
+ # Add a 'skin' to some blocks
+ if name in PYTHON_SKIN:
+ if self.nop == 'pythonloaded':
+ self._block_skin('pythonon', newblk)
+ else:
+ self._block_skin('pythonoff', newblk)
+ elif name in 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 DEFAULTS.has_key(newblk.name):
+ for i, argvalue in enumerate(DEFAULTS[newblk.name]):
+ # skip the first dock position since it is always a connector
+ dock = newblk.docks[i + 1]
+ argname = dock[0]
+ if argname == 'unavailable':
+ continue
+ if argname == 'media':
+ argname = 'journal'
+ elif argname == 'number' and \
+ (type(argvalue) is str or type(argvalue) is unicode):
+ argname = 'string'
+ elif argname == 'bool':
+ argname = argvalue
+ elif argname == 'flow':
+ argname = argvalue
+ (sx, sy) = newspr.get_xy()
+ if argname is not None:
+ if argname in CONTENT_BLOCKS:
+ argblk = Block(self.block_list, self.sprite_list,
+ argname, 0, 0, 'block', [argvalue],
+ self.block_scale)
+ else:
+ argblk = Block(self.block_list, self.sprite_list,
+ argname, 0, 0, 'block', [],
+ self.block_scale)
+ argdock = argblk.docks[0]
+ nx = sx + dock[2] - argdock[2]
+ ny = sy + dock[3] - argdock[3]
+ if argname == 'journal':
+ self._block_skin('journaloff', argblk)
+ argblk.spr.move((nx, ny))
+ argblk.spr.set_layer(TOP_LAYER)
+ argblk.connections = [newblk, None]
+ newblk.connections[i + 1] = argblk
+ self.drag_group = find_group(newblk)
+ self.block_operation = 'new'
+
+ def _new_macro(self, name, x, y):
+ """ Create a "macro" (predefined stack of blocks). """
+ macro = MACROS[name]
+ macro[0][2] = x
+ macro[0][3] = y
+ top = self.process_data(macro)
+ self.block_operation = 'new'
+ self._check_collapsibles(top)
+ self.drag_group = find_group(top)
+
+ def process_data(self, block_data, offset=0):
+ """ Process block_data (from a macro, a file, or the clipboard). """
+ if offset != 0:
+ _logger.debug("offset is %d" % (offset))
+ # Create the blocks (or turtle).
+ blocks = []
+ for blk in block_data:
+ if not self._found_a_turtle(blk):
+ blocks.append(self.load_block(blk, offset))
+
+ # Make the connections.
+ for i in range(len(blocks)):
+ cons = []
+ # Normally, it is simply a matter of copying the connections.
+ if blocks[i].connections == None:
+ for c in block_data[i][4]:
+ if c is None:
+ cons.append(None)
+ else:
+ cons.append(blocks[c])
+ elif blocks[i].connections == 'check':
+ # Corner case to convert old-style boolean and arithmetic blocks
+ cons.append(None) # Add an extra connection.
+ for c in block_data[i][4]:
+ if c is None:
+ cons.append(None)
+ else:
+ cons.append(blocks[c])
+ # If the boolean op was connected, readjust the plumbing.
+ if blocks[i].name in BOOLEAN_STYLE:
+ if block_data[i][4][0] is not None:
+ c = block_data[i][4][0]
+ cons[0] = blocks[block_data[c][4][0]]
+ c0 = block_data[c][4][0]
+ for j, cj in enumerate(block_data[c0][4]):
+ if cj == c:
+ blocks[c0].connections[j] = blocks[i]
+ if c < i:
+ blocks[c].connections[0] = blocks[i]
+ blocks[c].connections[3] = None
+ else:
+ # Connection was to a block we haven't seen yet.
+ print "WARNING: dock check couldn't see the future"
+ else:
+ if block_data[i][4][0] is not None:
+ c = block_data[i][4][0]
+ cons[0] = blocks[block_data[c][4][0]]
+ c0 = block_data[c][4][0]
+ for j, cj in enumerate(block_data[c0][4]):
+ if cj == c:
+ blocks[c0].connections[j] = blocks[i]
+ if c < i:
+ blocks[c].connections[0] = blocks[i]
+ blocks[c].connections[1] = None
+ else:
+ # Connection was to a block we haven't seen yet.
+ print "WARNING: dock check couldn't see the future"
+ else:
+ print "WARNING: unknown connection state %s" % \
+ (str(blocks[i].connections))
+ blocks[i].connections = cons[:]
+
+ # Block sizes and shapes may have changed.
+ for blk in blocks:
+ self._adjust_dock_positions(blk)
+
+ # Look for any stacks that need to be collapsed or sandwiched
+ for blk in blocks:
+ if collapsed(blk):
+ collapse_stack(find_sandwich_top(blk))
+ elif blk.name == 'sandwichbottom' and collapsible(blk):
+ blk.svg.set_hide(True)
+ blk.svg.set_show(False)
+ blk.refresh()
+ grow_stack_arm(find_sandwich_top(blk))
+
+ # Resize blocks to current scale
+ self.resize_blocks(blocks)
+
+ if len(blocks) > 0:
+ return blocks[0]
+ else:
+ return None
+
+ def _adjust_dock_positions(self, blk):
+ """ Adjust the dock x, y positions """
+ if not self.interactive_mode:
+ return
+ (sx, sy) = blk.spr.get_xy()
+ for i, c in enumerate(blk.connections):
+ if i > 0 and c is not None:
+ bdock = blk.docks[i]
+ for j in range(len(c.docks)):
+ if c.connections[j] == blk:
+ cdock = c.docks[j]
+ nx = sx + bdock[2] - cdock[2]
+ ny = sy + bdock[3] - cdock[3]
+ c.spr.move((nx, ny))
+ self._adjust_dock_positions(c)
+
+ def _turtle_pressed(self, x, y):
+ """ Turtle pressed """
+ (tx, ty) = self.selected_turtle.get_xy()
+ dx = x - tx - 30
+ dy = y - ty - 30
+ if (dx * dx) + (dy * dy) > 200:
+ self.drag_turtle = ('turn',
+ self.canvas.heading - atan2(dy, dx)/DEGTOR, 0)
+ else:
+ self.drag_turtle = ('move', x - tx, y - ty)
+
+ def _move_cb(self, win, event):
+ """ Mouse move """
+ x, y = xy(event)
+ self._mouse_move(x, y)
+ return True
+
+ def _mouse_move(self, x, y, verbose=False, mdx=0, mdy=0):
+ if verbose:
+ print "processing remote mouse move: %d, %d" % (x, y)
+
+ self.block_operation = 'move'
+ # First, check to see if we are dragging or rotating a turtle.
+ if self.selected_turtle is not None:
+ dtype, dragx, dragy = self.drag_turtle
+ (sx, sy) = self.selected_turtle.get_xy()
+ if dtype == 'move':
+ if mdx != 0 or mdy != 0:
+ dx, dy = mdx, mdy
+ else:
+ dx = x - dragx - sx
+ dy = y - dragy - sy
+ self.selected_turtle.move((sx + dx, sy + dy))
+ else:
+ if mdx != 0 or mdy != 0:
+ dx = mdx
+ dy = mdy
+ else:
+ dx = x - sx - 30
+ dy = y - sy - 30
+ self.canvas.seth(int(dragx + atan2(dy, dx)/DEGTOR + 5)/10 * 10)
+ # If we are hoving, show popup help.
+ elif self.drag_group is None:
+ self._show_popup(x, y)
+ return
+ # If we have a stack of blocks selected, move them.
+ elif self.drag_group[0] is not None:
+ blk = self.drag_group[0]
+ # Don't move a bottom blk is the stack is collapsed
+ if collapsed(blk):
+ return
+
+ self.selected_spr = blk.spr
+ dragx, dragy = self.drag_pos
+ if mdx != 0 or mdy != 0:
+ dx = mdx
+ dy = mdy
+ else:
+ (sx, sy) = blk.spr.get_xy()
+ dx = x - dragx - sx
+ dy = y - dragy - sy
+
+ # Take no action if there was a move of 0,0.
+ if dx == 0 and dy == 0:
+ return
+ self.drag_group = find_group(blk)
+
+ # Prevent blocks from ending up with a negative x...
+ for gblk in self.drag_group:
+ (bx, by) = gblk.spr.get_xy()
+ if bx + dx < 0:
+ dx += -(bx + dx)
+ """
+ # ...or under the palette.
+ if self.selected_palette is not None and\
+ self.selected_palette != self.trash_index:
+ w, h = self.palette_sprs[self.selected_palette][
+ self.orientation].get_dimensions()
+ if self.orientation == HORIZONTAL_PALETTE:
+ if bx < w and\
+ by+dy < self.toolbar_offset+PALETTE_HEIGHT:
+ dy += -(by+dy)+self.toolbar_offset+PALETTE_HEIGHT
+ else:
+ if by < h+self.toolbar_offset and bx+dx < PALETTE_WIDTH:
+ dx += -(bx+dx)+PALETTE_WIDTH
+ """
+
+ # Move the stack.
+ for gblk in self.drag_group:
+ (bx, by) = gblk.spr.get_xy()
+ gblk.spr.move((bx + dx, by + dy))
+ if mdx != 0 or mdy != 0:
+ dx = 0
+ dy = 0
+ else:
+ self.dx += dx
+ self.dy += dy
+
+ def _show_popup(self, x, y):
+ """ Let's help our users by displaying a little help. """
+ spr = self.sprite_list.find_sprite((x, y))
+ blk = self.block_list.spr_to_block(spr)
+ if spr and blk is not None:
+ if self.timeout_tag[0] == 0:
+ self.timeout_tag[0] = self._do_show_popup(blk.name)
+ self.selected_spr = spr
+ else:
+ if self.timeout_tag[0] > 0:
+ try:
+ gobject.source_remove(self.timeout_tag[0])
+ self.timeout_tag[0] = 0
+ except:
+ self.timeout_tag[0] = 0
+ elif spr and hasattr(spr,'type') and (spr.type == 'selector' or\
+ spr.type == 'palette' or\
+ spr.type == 'toolbar'):
+ if self.timeout_tag[0] == 0 and hasattr(spr, 'name'):
+ self.timeout_tag[0] = self._do_show_popup(spr.name)
+ self.selected_spr = spr
+ else:
+ if self.timeout_tag[0] > 0:
+ try:
+ gobject.source_remove(self.timeout_tag[0])
+ self.timeout_tag[0] = 0
+ except:
+ self.timeout_tag[0] = 0
+ else:
+ if self.timeout_tag[0] > 0:
+ try:
+ gobject.source_remove(self.timeout_tag[0])
+ self.timeout_tag[0] = 0
+ except:
+ self.timeout_tag[0] = 0
+
+ def _do_show_popup(self, block_name):
+ """ Fetch the help text and display it. """
+ if SPECIAL_NAMES.has_key(block_name):
+ block_name_s = SPECIAL_NAMES[block_name]
+ elif BLOCK_NAMES.has_key(block_name):
+ block_name_s = BLOCK_NAMES[block_name][0]
+ elif block_name in TOOLBAR_SHAPES:
+ block_name_s = ''
+ else:
+ block_name_s = _(block_name)
+ if HELP_STRINGS.has_key(block_name):
+ if block_name_s == '':
+ label = HELP_STRINGS[block_name]
+ else:
+ label = block_name_s + ": " + HELP_STRINGS[block_name]
+ else:
+ label = block_name_s
+ 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)
+ return 0
+
+ def _buttonrelease_cb(self, win, event):
+ """ Button release """
+ x, y = xy(event)
+ self.button_release(x, y)
+ return True
+
+ def button_release(self, x, y, verbose=False):
+ # We may have been moving the turtle
+ if self.selected_turtle is not None:
+ (tx, ty) = self.selected_turtle.get_xy()
+ (cx, cy) = self.canvas.canvas.get_xy()
+ # self.canvas.xcor = tx - self.canvas.canvas._width/2 + 30 - cx
+ # self.canvas.ycor = self.canvas.canvas._height/2 - ty - 30 + cy
+ self.canvas.xcor = tx - self.canvas.width/2 + 30 - cx
+ self.canvas.ycor = self.canvas.height/2 - ty - 30 + cy
+ self.canvas.move_turtle()
+ if self.running_sugar:
+ self.display_coordinates()
+ self.selected_turtle = None
+ return
+
+ # If we don't have a group of blocks, then there is nothing to do.
+ if self.drag_group == None:
+ return
+
+ blk = self.drag_group[0]
+ # Remove blocks by dragging them onto the trash palette.
+ if self.block_operation == 'move' and self._in_the_trash(x, y):
+ self._put_in_trash(blk, x, y)
+ self.drag_group = None
+ return
+
+ # Pull a stack of new blocks off of the category palette.
+ if self.block_operation == 'new':
+ for gblk in self.drag_group:
+ (bx, by) = gblk.spr.get_xy()
+ if self.orientation == 0:
+ gblk.spr.move((bx+20,
+ by+PALETTE_HEIGHT+self.toolbar_offset))
+ else:
+ gblk.spr.move((bx+PALETTE_WIDTH, by+20))
+
+ # Look to see if we can dock the current stack.
+ self._snap_to_dock()
+ self._check_collapsibles(blk)
+ for gblk in self.drag_group:
+ if gblk.status != 'collapsed':
+ gblk.spr.set_layer(BLOCK_LAYER)
+ self.drag_group = None
+
+ # Find the block we clicked on and process it.
+ if self.block_operation == 'click':
+ self._click_block(x, y)
+
+ def _click_block(self, x, y):
+ """ Click block """
+ blk = self.block_list.spr_to_block(self.selected_spr)
+ if blk is None:
+ return
+ self.selected_blk = blk
+ if blk.name == 'number' or blk.name == 'string':
+ self.saved_string = blk.spr.labels[0]
+ blk.spr.labels[0] += CURSOR
+ elif blk.name in BOX_STYLE_MEDIA:
+ self._import_from_journal(self.selected_blk)
+ if blk.name == 'journal' and self.running_sugar:
+ self._load_description_block(blk)
+ elif blk.name == 'identity2' or blk.name == 'hspace':
+ group = find_group(blk)
+ if hide_button_hit(blk.spr, x, y):
+ dx = blk.reset_x()
+ elif show_button_hit(blk.spr, x, y):
+ dx = 20
+ blk.expand_in_x(dx)
+ else:
+ dx = 0
+ for gblk in group:
+ if gblk != blk:
+ gblk.spr.move_relative((dx * blk.scale, 0))
+ elif blk.name == 'vspace':
+ group = find_group(blk)
+ if hide_button_hit(blk.spr, x, y):
+ dy = blk.reset_y()
+ elif show_button_hit(blk.spr, x, y):
+ dy = 20
+ blk.expand_in_y(dy)
+ else:
+ dy = 0
+ 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 or blk.name == 'nop':
+ if show_button_hit(blk.spr, x, y):
+ n = len(blk.connections)
+ group = find_group(blk.connections[n-1])
+ if blk.name == 'myfunc1arg':
+ blk.spr.labels[1] = 'f(x, y)'
+ blk.spr.labels[2] = ' '
+ dy = blk.add_arg()
+ blk.primitive = 'myfunction2'
+ blk.name = 'myfunc2arg'
+ elif blk.name == 'myfunc2arg':
+ blk.spr.labels[1] = 'f(x, y, z)'
+ dy = blk.add_arg(False)
+ blk.primitive = 'myfunction3'
+ blk.name = 'myfunc3arg'
+ elif blk.name == 'userdefined':
+ dy = blk.add_arg()
+ blk.primitive = 'userdefined2'
+ blk.name = 'userdefined2args'
+ elif blk.name == 'userdefined2args':
+ dy = blk.add_arg(False)
+ blk.primitive = 'userdefined3'
+ blk.name = 'userdefined3args'
+ 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]
+ argblk = Block(self.block_list, self.sprite_list, argname,
+ 0, 0, 'block', [argvalue], self.block_scale)
+ argdock = argblk.docks[0]
+ (bx, by) = blk.spr.get_xy()
+ nx = bx + blk.docks[n - 1][2] - argdock[2]
+ ny = by + blk.docks[n - 1][3] - argdock[3]
+ argblk.spr.move((nx, ny))
+ argblk.spr.set_layer(TOP_LAYER)
+ argblk.connections = [blk, None]
+ blk.connections[n - 1] = argblk
+ grow_stack_arm(find_sandwich_top(blk))
+ elif blk.name in PYTHON_SKIN and self.myblock == None:
+ self._import_py()
+ else:
+ self._run_stack(blk)
+ elif blk.name in COLLAPSIBLE:
+ top = find_sandwich_top(blk)
+ if collapsed(blk):
+ restore_stack(top)
+ elif top is not None:
+ collapse_stack(top)
+ else:
+ self._run_stack(blk)
+
+ def _check_collapsibles(self, blk):
+ """ Check the state of collapsible blocks upon change in dock state. """
+ group = find_group(blk)
+ for gblk in group:
+ if gblk.name in COLLAPSIBLE:
+ if collapsed(gblk):
+ gblk.svg.set_show(True)
+ gblk.svg.set_hide(False)
+ reset_stack_arm(find_sandwich_top(gblk))
+ elif collapsible(gblk):
+ gblk.svg.set_hide(True)
+ gblk.svg.set_show(False)
+ grow_stack_arm(find_sandwich_top(gblk))
+ else:
+ gblk.svg.set_hide(False)
+ gblk.svg.set_show(False)
+ # Ouch: When you tear off the sandwich bottom, you
+ # no longer have access to the group with the sandwich top
+ # so check them all.
+ for b in self.just_blocks():
+ if b.name == 'sandwichtop':
+ if find_sandwich_bottom(b) is None:
+ reset_stack_arm(b)
+ gblk.refresh()
+
+ def _run_stack(self, blk):
+ """ Run a stack of blocks. """
+ if blk is None:
+ return
+ self.lc.ag = None
+ top = find_top_block(blk)
+ self.lc.run_blocks(top, self.just_blocks(), True)
+ if self.interactive_mode:
+ gobject.idle_add(self.lc.doevalstep)
+ else:
+ while self.lc.doevalstep():
+ pass
+
+ def _snap_to_dock(self):
+ """ Snap a block to the dock of another block. """
+ my_block = self.drag_group[0]
+ d = 200
+ for my_dockn in range(len(my_block.docks)):
+ for your_block in self.just_blocks():
+ # don't link to a block to which you're already connected
+ if your_block in self.drag_group:
+ continue
+ # check each dock of your_block for a possible connection
+ for your_dockn in range(len(your_block.docks)):
+ this_xy = dock_dx_dy(your_block, your_dockn,
+ my_block, my_dockn)
+ if magnitude(this_xy) > d:
+ continue
+ d = magnitude(this_xy)
+ best_xy = this_xy
+ best_you = your_block
+ best_your_dockn = your_dockn
+ best_my_dockn = my_dockn
+ if d < 200:
+ if not arithmetic_check(my_block, best_you, best_my_dockn,
+ best_your_dockn):
+ return
+ for blk in self.drag_group:
+ (sx, sy) = blk.spr.get_xy()
+ blk.spr.move((sx + best_xy[0], sy + best_xy[1]))
+
+ # If there was already a block docked there, move it to the trash.
+ blk_in_dock = best_you.connections[best_your_dockn]
+ if blk_in_dock is not None:
+ blk_in_dock.connections[0] = None
+ self._put_in_trash(blk_in_dock)
+
+ best_you.connections[best_your_dockn] = my_block
+ if my_block.connections is not None:
+ my_block.connections[best_my_dockn] = best_you
+
+ def _import_from_journal(self, blk):
+ """ Import a file from the Sugar Journal """
+ if self.running_sugar:
+ chooser = ObjectChooser('Choose image', self.parent,
+ gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
+ try:
+ result = chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ dsobject = chooser.get_selected_object()
+ self._update_media_icon(blk, dsobject, dsobject.object_id)
+ dsobject.destroy()
+ finally:
+ chooser.destroy()
+ del chooser
+ else:
+ fname, self.load_save_folder = \
+ get_load_name('.*', self.load_save_folder)
+ if fname is None:
+ return
+ self._update_media_icon(blk, fname)
+
+ def _load_description_block(self, blk):
+ """ Look for a corresponding description block """
+ if blk == None or blk.name != 'journal' or len(blk.values) == 0 or\
+ blk.connections[0] is None:
+ return
+ _blk = blk.connections[0]
+ dblk = find_blk_below(_blk, 'description')
+ # Autoupdate the block if it is empty
+ if dblk != None and (len(dblk.values) == 0 or dblk.values[0] == None):
+ self._update_media_icon(dblk, None, blk.values[0])
+
+ def _update_media_icon(self, blk, name, value=''):
+ """ Update the icon on a 'loaded' media block. """
+ if blk.name == 'journal':
+ self._load_image_thumb(name, blk)
+ elif blk.name == 'audio':
+ self._block_skin('audioon', blk)
+ else:
+ self._block_skin('descriptionon', blk)
+ if value == '':
+ value = name
+ if len(blk.values) > 0:
+ blk.values[0] = value
+ else:
+ blk.values.append(value)
+ blk.spr.set_label(' ')
+
+ def _load_image_thumb(self, picture, blk):
+ """ Replace icon with a preview image. """
+ pixbuf = None
+ self._block_skin('descriptionon', blk)
+
+ if self.running_sugar:
+ w, h = calc_image_size(blk.spr)
+ pixbuf = get_pixbuf_from_journal(picture, w, h)
+ else:
+ if movie_media_type(picture):
+ self._block_skin('journalon', blk)
+ elif audio_media_type(picture):
+ self._block_skin('audioon', blk)
+ blk.name = 'audio'
+ elif image_media_type(picture):
+ w, h = calc_image_size(blk.spr)
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(picture, w, h)
+ else:
+ blk.name = 'description'
+ if pixbuf is not None:
+ x, y = self._calc_image_offset('', blk.spr)
+ blk.set_image(pixbuf, x, y)
+ self._resize_skin(blk)
+
+ def _disconnect(self, blk):
+ """ Disconnect block from stack above it. """
+ if blk.connections[0] == None:
+ return
+ if collapsed(blk):
+ return
+ blk2 = blk.connections[0]
+ blk2.connections[blk2.connections.index(blk)] = None
+ blk.connections[0] = None
+
+ def _keypress_cb(self, area, event):
+ """ Keyboard """
+ keyname = gtk.gdk.keyval_name(event.keyval)
+ keyunicode = gtk.gdk.keyval_to_unicode(event.keyval)
+
+ if event.get_state()&gtk.gdk.MOD1_MASK:
+ alt_mask = True
+ alt_flag = 'T'
+ else:
+ alt_mask = False
+ alt_flag = 'F'
+ self._key_press(alt_mask, keyname, keyunicode)
+ return keyname
+
+ def _key_press(self, alt_mask, keyname, keyunicode, verbose=False):
+ if keyname is None:
+ return False
+ if verbose:
+ print "processing remote key press: %s" % (keyname)
+
+ self.keypress = keyname
+
+ # First, process Alt keys.
+ if alt_mask and self.selected_blk is not None:
+ if keyname == "p":
+ self.hideshow_button()
+ elif keyname == 'q':
+ exit()
+ return True
+ # Process keyboard input for 'number' blocks
+ if self.selected_blk is not None and \
+ self.selected_blk.name == 'number':
+ self._process_numeric_input(keyname)
+ return True
+ # Process keyboard input for 'string' blocks
+ elif self.selected_blk is not None and \
+ self.selected_blk.name == 'string':
+ self.process_alphanumeric_input(keyname, keyunicode)
+ if self.selected_blk is not None:
+ self.selected_blk.resize()
+ return True
+ # Otherwise, use keyboard input to move blocks or turtles
+ else:
+ self._process_keyboard_commands(keyname)
+ if self.selected_blk is None:
+ return False
+
+ def _process_numeric_input(self, keyname):
+ ''' Make sure numeric input is valid. '''
+ oldnum = self.selected_blk.spr.labels[0].replace(CURSOR, '')
+ if len(oldnum) == 0:
+ oldnum = '0'
+ if keyname == 'minus':
+ if oldnum == '0':
+ newnum = '-'
+ elif oldnum[0] != '-':
+ newnum = '-' + oldnum
+ else:
+ newnum = oldnum
+ elif keyname == 'period' and '.' not in oldnum:
+ newnum = oldnum + '.'
+ elif keyname == 'BackSpace':
+ if len(oldnum) > 0:
+ newnum = oldnum[:len(oldnum)-1]
+ else:
+ newnum = ''
+ elif keyname in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
+ if oldnum == '0':
+ newnum = keyname
+ else:
+ newnum = oldnum + keyname
+ elif keyname == 'Return':
+ self._unselect_block()
+ return
+ else:
+ newnum = oldnum
+ if newnum == '.':
+ newnum = '0.'
+ if len(newnum) > 0 and newnum != '-':
+ try:
+ float(newnum)
+ except ValueError, e:
+ newnum = oldnum
+ self.selected_blk.spr.set_label(newnum + CURSOR)
+
+ def process_alphanumeric_input(self, keyname, keyunicode):
+ """ Make sure alphanumeric input is properly parsed. """
+ if len(self.selected_blk.spr.labels[0]) > 0:
+ c = self.selected_blk.spr.labels[0].count(CURSOR)
+ if c == 0:
+ oldleft = self.selected_blk.spr.labels[0]
+ oldright = ''
+ elif len(self.selected_blk.spr.labels[0]) == 1:
+ oldleft = ''
+ oldright = ''
+ else:
+ try: # Why are getting a ValueError on occasion?
+ oldleft, oldright = \
+ self.selected_blk.spr.labels[0].split(CURSOR)
+ except ValueError:
+ print "[%s]" % self.selected_blk.spr.labels[0]
+ oldleft = self.selected_blk.spr.labels[0]
+ oldright = ''
+ else:
+ oldleft = ''
+ oldright = ''
+ newleft = oldleft
+ if keyname in ['Shift_L', 'Shift_R', 'Control_L', 'Caps_Lock', \
+ 'Alt_L', 'Alt_R', 'KP_Enter', 'ISO_Level3_Shift']:
+ keyname = ''
+ keyunicode = 0
+ # Hack until I sort out input and unicode and dead keys,
+ if keyname[0:5] == 'dead_':
+ self.dead_key = keyname
+ keyname = ''
+ keyunicode = 0
+ if keyname == 'space':
+ keyunicode = 32
+ elif keyname == 'Tab':
+ keyunicode = 9
+ if keyname == 'BackSpace':
+ if len(oldleft) > 1:
+ newleft = oldleft[:len(oldleft)-1]
+ else:
+ newleft = ''
+ elif keyname == 'Home':
+ oldright = oldleft+oldright
+ newleft = ''
+ elif keyname == 'Left':
+ if len(oldleft) > 0:
+ oldright = oldleft[len(oldleft)-1:]+oldright
+ newleft = oldleft[:len(oldleft)-1]
+ elif keyname == 'Right':
+ if len(oldright) > 0:
+ newleft = oldleft + oldright[0]
+ oldright = oldright[1:]
+ elif keyname == 'End':
+ newleft = oldleft+oldright
+ oldright = ''
+ elif keyname == 'Return':
+ newleft = oldleft+RETURN
+ elif keyname == 'Down':
+ self._unselect_block()
+ return
+ elif keyname == 'Up' or keyname == 'Escape': # Restore previous state
+ self.selected_blk.spr.set_label(self.saved_string)
+ self._unselect_block()
+ return
+ else:
+ if self.dead_key is not '':
+ keyunicode = \
+ DEAD_DICTS[DEAD_KEYS.index(self.dead_key[5:])][keyname]
+ self.dead_key = ''
+ if keyunicode > 0:
+ if unichr(keyunicode) != '\x00':
+ newleft = oldleft+unichr(keyunicode)
+ else:
+ newleft = oldleft
+ elif keyunicode == -1: # clipboard text
+ if keyname == '\n':
+ newleft = oldleft+RETURN
+ else:
+ newleft = oldleft+keyname
+ self.selected_blk.spr.set_label("%s%s%s" % (newleft, CURSOR, oldright))
+
+ def _process_keyboard_commands(self, keyname):
+ """ Use the keyboard to move blocks and turtle """
+ mov_dict = {'KP_Up':[0, 10], 'j':[0, 10], 'Up':[0, 10],
+ 'KP_Down':[0, -10], 'k':[0, -10], 'Down':[0, -10],
+ 'KP_Left':[-10, 0], 'h':[-10, 0], 'Left':[-10, 0],
+ 'KP_Right':[10, 0], 'l':[10, 0], 'Right':[10, 0],
+ 'KP_Page_Down':[0, 0], 'KP_Page_Up':[0, 0], 'KP_End':[0, 0],
+ 'KP_Home':[-1, -1], 'Return':[-1, -1], 'Esc':[0, 0]}
+ if not mov_dict.has_key(keyname):
+ return
+ if keyname == 'KP_End':
+ self.run_button(0)
+ elif self.selected_spr is not None:
+ blk = self.block_list.spr_to_block(self.selected_spr)
+ tur = self.turtles.spr_to_turtle(self.selected_spr)
+ if not self.lc.running and blk is not None:
+ if keyname == 'Return' or keyname == 'KP_Page_Up':
+ (x, y) = blk.spr.get_xy()
+ self._click_block(x, y)
+ elif keyname == 'KP_Page_Down':
+ if self.drag_group == None:
+ self.drag_group = find_group(blk)
+ for gblk in self.drag_group:
+ gblk.spr.hide()
+ self.drag_group = None
+ else:
+ self._jog_block(blk, mov_dict[keyname][0],
+ mov_dict[keyname][1])
+ elif tur is not None:
+ self._jog_turtle(mov_dict[keyname][0], mov_dict[keyname][1])
+ return True
+
+ def _jog_turtle(self, dx, dy):
+ """ Jog turtle """
+ if dx == -1 and dy == -1:
+ self.canvas.xcor = 0
+ self.canvas.ycor = 0
+ else:
+ self.canvas.xcor += dx
+ self.canvas.ycor += dy
+ self.canvas.move_turtle()
+ self.display_coordinates()
+ self.selected_turtle = None
+
+ def _jog_block(self, blk, dx, dy):
+ """ Jog block """
+ if collapsed(blk):
+ return
+ self.drag_group = find_group(blk)
+ # check to see if any block ends up with a negative x
+ for blk in self.drag_group:
+ (sx, sy) = blk.spr.get_xy()
+ if sx+dx < 0:
+ dx += -(sx + dx)
+ # move the stack
+ for blk in self.drag_group:
+ (sx, sy) = blk.spr.get_xy()
+ blk.spr.move((sx + dx, sy - dy))
+ self._snap_to_dock()
+ self.drag_group = None
+
+ def _number_check(self):
+ """ Make sure a 'number' block contains a number. """
+ n = self.selected_blk.spr.labels[0].replace(CURSOR, '')
+ if n in ['-', '.', '-.']:
+ n = 0
+ if n is not None:
+ try:
+ f = float(n)
+ if f > 1000000:
+ n = 1
+ self.showlabel("#overflowerror")
+ elif f < -1000000:
+ n = -1
+ self.showlabel("#overflowerror")
+ except ValueError:
+ n = 0
+ self.showlabel("#notanumber")
+ else:
+ n = 0
+ self.selected_blk.spr.set_label(n)
+ self.selected_blk.values[0] = n
+
+ def _string_check(self):
+ s = self.selected_blk.spr.labels[0].replace(CURSOR, '')
+ self.selected_blk.spr.set_label(s)
+ self.selected_blk.values[0] = s.replace(RETURN, "\12")
+
+ def load_python_code(self):
+ """ Load Python code from a file """
+ fname, self.load_save_folder = get_load_name('.py',
+ self.load_save_folder)
+ if fname == None:
+ return
+ f = open(fname, 'r')
+ self.myblock = f.read()
+ f.close()
+
+ def _import_py(self):
+ """ Import Python code into a block """
+ if self.running_sugar:
+ self.activity.import_py()
+ else:
+ self.load_python_code()
+ self.set_userdefined()
+
+ def new_project(self):
+ """ Start a new project """
+ stop_logo(self)
+ self._loaded_project = ""
+ # Put current project in the trash.
+ while len(self.just_blocks()) > 0:
+ blk = self.just_blocks()[0]
+ top = find_top_block(blk)
+ self._put_in_trash(top)
+ self.canvas.clearscreen()
+ self.save_file_name = None
+
+ def is_new_project(self):
+ """ Is this a new project or was a old project loaded from a file? """
+ return self._loaded_project == ""
+
+ def project_has_changed(self):
+ """ WARNING: order of JSON serialized data may have changed. """
+ try:
+ f = open(self._loaded_project, 'r')
+ saved_project_data = f.read()
+ f.close()
+ except:
+ print "problem loading saved project data from %s" %\
+ (self._loaded_project)
+ saved_project_data = ""
+ current_project_data = data_to_string(self.assemble_data_to_save())
+
+ return saved_project_data != current_project_data
+
+ def load_files(self, ta_file, create_new_project=True):
+ """ Load a project from a file """
+ if create_new_project:
+ self.new_project()
+ self._check_collapsibles(self.process_data(data_from_file(ta_file)))
+ self._loaded_prokect = ta_file
+
+ def load_file(self, create_new_project=True):
+ _file_name, self.load_save_folder = get_load_name('.ta',
+ self.load_save_folder)
+ if _file_name == None:
+ return
+ if _file_name[-3:] == '.ta':
+ _file_name = _file_name[0:-3]
+ self.load_files(_file_name+'.ta', create_new_project)
+ if create_new_project:
+ self.save_file_name = os.path.basename(_file_name)
+ if self.running_sugar:
+ self.activity.metadata['title'] = os.path.split(_file_name)[1]
+
+ def _found_a_turtle(self, blk):
+ """ Either [-1, 'turtle', ...] or [-1, ['turtle', key], ...] """
+ if blk[1] == 'turtle':
+ self.load_turtle(blk)
+ return True
+ elif type(blk[1]) == list and blk[1][0] == 'turtle':
+ 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):
+ """ Restore a turtle from its saved state """
+ tid, name, xcor, ycor, heading, color, shade, pensize = blk
+ self.canvas.set_turtle(key)
+ self.canvas.setxy(xcor, ycor)
+ self.canvas.seth(heading)
+ self.canvas.setcolor(color)
+ self.canvas.setshade(shade)
+ self.canvas.setpensize(pensize)
+
+ def load_block(self, b, offset=0):
+ """ Restore individual blocks from saved state """
+ # A block is saved as: (i, (btype, value), x, y, (c0,... cn))
+ # The x, y position is saved/loaded for backward compatibility
+ btype, value = b[1], None
+ if type(btype) == tuple:
+ btype, value = btype
+ elif type(btype) == list:
+ btype, value = btype[0], btype[1]
+ if btype in CONTENT_BLOCKS or btype in COLLAPSIBLE:
+ if btype == 'number':
+ try:
+ values = [round_int(value)]
+ except ValueError:
+ values = [0]
+ elif btype in COLLAPSIBLE:
+ if value is not None:
+ values = [int(value)]
+ else:
+ values = []
+ else:
+ values = [value]
+ else:
+ values = []
+
+ if btype in OLD_DOCK:
+ check_dock = True
+ else:
+ check_dock = False
+ if OLD_NAMES.has_key(btype):
+ btype = OLD_NAMES[btype]
+ blk = Block(self.block_list, self.sprite_list, btype,
+ b[2] + self.canvas.cx + offset,
+ b[3] + self.canvas.cy + offset,
+ 'block', values, self.block_scale)
+ # Some blocks get transformed.
+ if btype == 'string' and blk.spr is not None:
+ blk.spr.set_label(blk.values[0].replace('\n', RETURN))
+ elif btype == 'start': # block size is saved in start block
+ if value is not None:
+ self.block_scale = value
+ elif btype in EXPANDABLE or btype == 'nop':
+ if btype == 'vspace':
+ if value is not None:
+ blk.expand_in_y(value)
+ elif btype == 'hspace' or btype == 'identity2':
+ if value is not None:
+ blk.expand_in_x(value)
+ elif btype == 'templatelist' or btype == 'list':
+ for i in range(len(b[4])-4):
+ blk.add_arg()
+ elif btype == 'myfunc2arg' or btype == 'myfunc3arg' or\
+ btype == 'userdefined2args' or btype == 'userdefined3args':
+ blk.add_arg()
+ if btype == 'myfunc3arg' or btype == 'userdefined3args':
+ blk.add_arg(False)
+ if btype in PYTHON_SKIN:
+ if self.nop == 'pythonloaded':
+ self._block_skin('pythonon', blk)
+ else:
+ self._block_skin('pythonoff', blk)
+ elif btype in BOX_STYLE_MEDIA and blk.spr is not None:
+ if len(blk.values) == 0 or blk.values[0] == 'None' or\
+ blk.values[0] == None:
+ self._block_skin(btype+'off', blk)
+ elif btype == 'audio' or btype == 'description':
+ self._block_skin(btype+'on', blk)
+ elif self.running_sugar:
+ try:
+ dsobject = datastore.get(blk.values[0])
+ if not movie_media_type(dsobject.file_path[-4:]):
+ w, h, = calc_image_size(blk.spr)
+ pixbuf = get_pixbuf_from_journal(dsobject, w, h)
+ if pixbuf is not None:
+ x, y = self._calc_image_offset('', blk.spr)
+ blk.set_image(pixbuf, x, y)
+ else:
+ self._block_skin('journalon', blk)
+ dsobject.destroy()
+ except:
+ try:
+ w, h, = calc_image_size(blk.spr)
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
+ blk.values[0], w, h)
+ x, y = self._calc_image_offset('', blk.spr)
+ blk.set_image(pixbuf, x, y)
+ except:
+ print "Warning: Couldn't open dsobject (%s)" % \
+ (blk.values[0])
+ self._block_skin('journaloff', blk)
+ else:
+ if not movie_media_type(blk.values[0][-4:]):
+ try:
+ w, h, = calc_image_size(blk.spr)
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
+ blk.values[0], w, h)
+ x, y = self._calc_image_offset('', blk.spr)
+ blk.set_image(pixbuf, x, y)
+ except:
+ self._block_skin('journaloff', blk)
+ else:
+ self._block_skin('journalon', blk)
+ blk.spr.set_label(' ')
+ blk.resize()
+
+ if self.interactive_mode:
+ blk.spr.set_layer(BLOCK_LAYER)
+ if check_dock:
+ blk.connections = 'check'
+ return blk
+
+ def load_start(self, ta_file=None):
+ """ Start a new project with a 'start' brick """
+ if ta_file is None:
+ self.process_data([[0, "start", PALETTE_WIDTH + 20,
+ self.toolbar_offset+PALETTE_HEIGHT + 20,
+ [None, None]]])
+ else:
+ self.process_data(data_from_file(ta_file))
+
+ def save_file(self, _file_name=None):
+ """ Start a project to a file """
+ if self.save_folder is not None:
+ self.load_save_folder = self.save_folder
+ if _file_name is None:
+ _file_name, self.load_save_folder = get_save_name('.ta',
+ self.load_save_folder, self.save_file_name)
+ if _file_name is None:
+ return
+ if _file_name[-3:] == '.ta':
+ _file_name = _file_name[0:-3]
+ data_to_file(self.assemble_data_to_save(), _file_name + '.ta')
+ self.save_file_name = os.path.basename(_file_name)
+ if not self.running_sugar:
+ self.save_folder = self.load_save_folder
+
+ def assemble_data_to_save(self, save_turtle=True, save_project=True):
+ """ Pack the project (or stack) into a data stream to be serialized """
+ _data = []
+ _blks = []
+
+ if save_project:
+ _blks = self.just_blocks()
+ else:
+ if self.selected_blk is None:
+ return []
+ _blks = find_group(find_top_block(self.selected_blk))
+
+ for _i, _blk in enumerate(_blks):
+ _blk.id = _i
+ for _blk in _blks:
+ if _blk.name in CONTENT_BLOCKS or _blk.name in COLLAPSIBLE:
+ if len(_blk.values) > 0:
+ _name = (_blk.name, _blk.values[0])
+ else:
+ _name = (_blk.name)
+ elif _blk.name in EXPANDABLE:
+ _ex, _ey = _blk.get_expand_x_y()
+ if _ex > 0:
+ _name = (_blk.name, _ex)
+ elif _ey > 0:
+ _name = (_blk.name, _ey)
+ else:
+ _name = (_blk.name, 0)
+ elif _blk.name == 'start': # save block_size in start block
+ _name = (_blk.name, self.block_scale)
+ else:
+ _name = (_blk.name)
+ if hasattr(_blk, 'connections'):
+ connections = [get_id(_cblk) for _cblk in _blk.connections]
+ else:
+ connections = None
+ (_sx, _sy) = _blk.spr.get_xy()
+ # Add a slight offset for copy/paste
+ if not save_project:
+ _sx += 20
+ _sy += 20
+ _data.append((_blk.id, _name, _sx-self.canvas.cx,
+ _sy-self.canvas.cy, connections))
+ if save_turtle:
+ for _turtle in iter(self.turtles.dict):
+ 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))
+ return _data
+
+ def display_coordinates(self):
+ """ Display the coordinates of the current turtle on the toolbar """
+ x = round_int(self.canvas.xcor/self.coord_scale)
+ y = round_int(self.canvas.ycor/self.coord_scale)
+ h = round_int(self.canvas.heading)
+ if self.running_sugar:
+ self.activity.coordinates_label.set_text("%s: %d %s: %d %s: %d" % (
+ _("xcor"), x, _("ycor"), y, _("heading"), h))
+ self.activity.coordinates_label.show()
+ elif self.interactive_mode:
+ self.win.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:
+ print label
+ return
+ if shp == 'syntaxerror' and str(label) != '':
+ if self.status_shapes.has_key(str(label)[1:]):
+ shp = str(label)[1:]
+ label = ''
+ else:
+ shp = 'status'
+ elif shp[0] == '#':
+ shp = shp[1:]
+ label = ''
+ if shp == 'notanumber':
+ shp = 'overflowerror'
+ self.status_spr.set_shape(self.status_shapes[shp])
+ self.status_spr.set_label(str(label))
+ self.status_spr.set_layer(STATUS_LAYER)
+ if shp == 'info':
+ self.status_spr.move((PALETTE_WIDTH, self.height-400))
+ else:
+ self.status_spr.move((PALETTE_WIDTH, self.height-200))
+
+ def calc_position(self, template):
+ """ Relative placement of portfolio objects (depreciated) """
+ w, h, x, y, dx, dy = TEMPLATES[template]
+ x *= self.canvas.width
+ y *= self.canvas.height
+ w *= (self.canvas.width-x)
+ h *= (self.canvas.height-y)
+ dx *= w
+ dy *= h
+ return(w, h, x, y, dx, dy)
+
+ def save_for_upload(self, _file_name):
+ """ Grab the current canvas and save it for upload """
+ if _file_name[-3:] == '.ta':
+ _file_name = _file_name[0:-3]
+ data_to_file(self.assemble_data_to_save(), _file_name + '.ta')
+ save_picture(self.canvas, _file_name + '.png')
+ ta_file = _file_name + '.ta'
+ image_file = _file_name + '.png'
+ return ta_file, image_file
+
+ def save_as_image(self, name="", svg=False, pixbuf=None):
+ """ Grab the current canvas and save it. """
+
+ if not self.interactive_mode:
+ # print name
+ save_picture(self.canvas, name[:-3] + ".png")
+ 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"
+ else:
+ if len(name) == 0:
+ filename = "ta.png"
+ else:
+ filename = name+".png"
+ datapath = get_path(self.activity, 'instance')
+ elif len(name) == 0:
+ name = "ta"
+ if self.save_folder is not None:
+ self.load_save_folder = self.save_folder
+ 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)
+ datapath = self.load_save_folder
+ else:
+ datapath = os.getcwd()
+ if svg:
+ filename = name+".svg"
+ else:
+ filename = name+".png"
+ if filename is None:
+ return
+
+ file_path = os.path.join(datapath, filename)
+ if svg:
+ if self.svg_string == '':
+ return
+ save_svg(self.svg_string, file_path)
+ self.svg_string = ''
+ else:
+ save_picture(self.canvas, file_path)
+
+ # keep a log of the saved pictures for export to HTML
+ self.saved_pictures.append(file_path)
+
+ if self.running_sugar:
+ dsobject = datastore.create()
+ if len(name) == 0:
+ dsobject.metadata['title'] = "%s %s" % (
+ self.activity.metadata['title'], _("image"))
+ else:
+ dsobject.metadata['title'] = name
+ dsobject.metadata['icon-color'] = profile.get_color().to_string()
+ if svg:
+ dsobject.metadata['mime_type'] = 'image/svg+xml'
+ else:
+ dsobject.metadata['mime_type'] = 'image/png'
+ dsobject.set_file_path(file_path)
+ datastore.write(dsobject)
+ dsobject.destroy()
+
+ def just_blocks(self):
+ """ Filter out 'proto', 'trash', and 'deleted' blocks """
+ just_blocks_list = []
+ for _blk in self.block_list.list:
+ if _blk.type == 'block':
+ just_blocks_list.append(_blk)
+ return just_blocks_list
+
+ def _width_and_height(self, blk):
+ """ What are the width and height of a stack? """
+ minx = 10000
+ miny = 10000
+ maxx = -10000
+ maxy = -10000
+ for gblk in find_group(blk):
+ (x, y) = gblk.spr.get_xy()
+ w, h = gblk.spr.get_dimensions()
+ if x < minx:
+ minx = x
+ if y < miny:
+ miny = y
+ if x + w > maxx:
+ maxx = x + w
+ if y + h > maxy:
+ maxy = y + h
+ return(maxx - minx, maxy - miny)
+
+ # Utilities related to putting a image 'skin' on a block
+
+ def _calc_image_offset(self, name, spr, iw=0, ih=0):
+ """ Calculate the postion for placing an image onto a sprite. """
+ _l, _t = spr.label_left_top()
+ if name == '':
+ return _l, _t
+ _w = spr.label_safe_width()
+ _h = spr.label_safe_height()
+ if iw == 0:
+ iw = self.media_shapes[name].get_width()
+ ih = self.media_shapes[name].get_height()
+ return int(_l + (_w - iw)/2), int(_t + (_h - ih)/2)
+
+ def _calc_w_h(self, name, spr):
+ """ Calculate new image size """
+ target_w = spr.label_safe_width()
+ target_h = spr.label_safe_height()
+ if name == '':
+ return target_w, target_h
+ image_w = self.media_shapes[name].get_width()
+ image_h = self.media_shapes[name].get_height()
+ scale_factor = float(target_w)/image_w
+ new_w = target_w
+ new_h = image_h*scale_factor
+ if new_h > target_h:
+ scale_factor = float(target_h)/new_h
+ new_h = target_h
+ new_w = target_w*scale_factor
+ return int(new_w), int(new_h)
+
+ def _proto_skin(self, name, n, i):
+ """ Utility for calculating proto skin images """
+ x, y = self._calc_image_offset(name, self.palettes[n][i].spr)
+ self.palettes[n][i].spr.set_image(self.media_shapes[name], 1, x, y)
+
+ def _block_skin(self, name, blk):
+ """ Some blocks get a skin """
+ x, y = self._calc_image_offset(name, blk.spr)
+ blk.set_image(self.media_shapes[name], x, y)
+ self._resize_skin(blk)
+
+ def _resize_skin(self, blk):
+ """ Resize the 'skin' when block scale changes. """
+ if blk.name in PYTHON_SKIN:
+ w, h = self._calc_w_h('pythonoff', blk.spr)
+ x, y = self._calc_image_offset('pythonoff', blk.spr, w, h)
+ elif blk.name == 'journal':
+ if len(blk.values) == 1 and blk.values[0] is not None:
+ w, h = self._calc_w_h('', blk.spr)
+ x, y = self._calc_image_offset('journaloff', blk.spr, w, h)
+ else:
+ w, h = self._calc_w_h('journaloff', blk.spr)
+ x, y = self._calc_image_offset('journaloff', blk.spr, w, h)
+ else:
+ w, h = self._calc_w_h('descriptionoff', blk.spr)
+ x, y = self._calc_image_offset('descriptionoff', blk.spr, w, h)
+ blk.scale_image(x, y, w, h)