diff options
-rw-r--r-- | TurtleArt/__init__.py | 0 | ||||
-rw-r--r-- | TurtleArt/sprites.py | 420 | ||||
-rw-r--r-- | TurtleArt/tablock.py | 865 | ||||
-rw-r--r-- | TurtleArt/tacanvas.py | 596 | ||||
-rw-r--r-- | TurtleArt/taconstants.py | 1065 | ||||
-rw-r--r-- | TurtleArt/taexporthtml.py | 147 | ||||
-rw-r--r-- | TurtleArt/taexportlogo.py | 353 | ||||
-rw-r--r-- | TurtleArt/tagplay.py | 176 | ||||
-rw-r--r-- | TurtleArt/tajail.py | 69 | ||||
-rw-r--r-- | TurtleArt/talogo.py | 1366 | ||||
-rw-r--r-- | TurtleArt/tamyblock.py | 245 | ||||
-rwxr-xr-x | TurtleArt/tasprite_factory.py | 1118 | ||||
-rw-r--r-- | TurtleArt/taturtle.py | 209 | ||||
-rw-r--r-- | TurtleArt/tautils.py | 705 | ||||
-rw-r--r-- | TurtleArt/tawindow.py | 2336 | ||||
-rw-r--r-- | TurtleArtActivity.py | 13 | ||||
-rwxr-xr-x | turtleart.py | 30 |
17 files changed, 9698 insertions, 15 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()>k.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()>k.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) diff --git a/TurtleArtActivity.py b/TurtleArtActivity.py index 7f9156f..fbd2fb0 100644 --- a/TurtleArtActivity.py +++ b/TurtleArtActivity.py @@ -50,12 +50,13 @@ from gettext import gettext as _ import os.path import tarfile -from taconstants import PALETTE_NAMES, OVERLAY_LAYER, HELP_STRINGS -from taexporthtml import save_html -from taexportlogo import save_logo -from tautils import data_to_file, data_to_string, data_from_string, get_path -from tawindow import TurtleArtWindow -from taturtle import Turtle +from TurtleArt.taconstants import PALETTE_NAMES, OVERLAY_LAYER, HELP_STRINGS +from TurtleArt.taexporthtml import save_html +from TurtleArt.taexportlogo import save_logo +from TurtleArt.tautils import data_to_file, data_to_string, data_from_string, \ + get_path +from TurtleArt.tawindow import TurtleArtWindow +from TurtleArt.taturtle import Turtle SERVICE = 'org.laptop.TurtleArtActivity' IFACE = SERVICE diff --git a/turtleart.py b/turtleart.py index addfc83..d1c34ee 100755 --- a/turtleart.py +++ b/turtleart.py @@ -37,18 +37,20 @@ sys.argv[1:] = [] # Execution of import gst cannot see '--help' or '-h' from gettext import gettext as _ -from taconstants import OVERLAY_LAYER -from tautils import data_to_string, data_from_string, get_save_name -from tawindow import TurtleArtWindow -from taexporthtml import save_html -from taexportlogo import save_logo +from TurtleArt.taconstants import OVERLAY_LAYER +from TurtleArt.tautils import data_to_string, data_from_string, get_save_name +from TurtleArt.tawindow import TurtleArtWindow +from TurtleArt.taexporthtml import save_html +from TurtleArt.taexportlogo import save_logo -HELP_MSG = 'turtleart.py: ' + _('usage is') + """ +_HELP_MSG = 'turtleart.py: ' + _('usage is') + """ \tturtleart.py \tturtleart.py project.ta \tturtleart.py --output_png project.ta \tturtleart.py -o project""" +_MAX_FILE_SIZE = 950000 + def _make_sub_menu(menu, name): """ add a new submenu to the toolbar """ @@ -95,11 +97,11 @@ class TurtleMain(): ['help', 'output_png']) except getopt.GetoptError, err: print str(err) - print HELP_MSG + print _HELP_MSG sys.exit(2) for o, a in opts: if o in ('-h', '--help'): - print HELP_MSG + print _HELP_MSG sys.exit() if o in ('-o', '--output_png'): self.output_png = True @@ -109,7 +111,7 @@ class TurtleMain(): self.ta_file = args[0] if len(args) > 1 or self.output_png and self.ta_file is None: - print HELP_MSG + print _HELP_MSG sys.exit() if self.ta_file is not None: @@ -572,6 +574,16 @@ http://turtleartsite.sugarlabs.org to upload your project.')) *self.description_entry.get_buffer().get_bounds()) tafile, imagefile = self.tw.save_for_upload(title) + # Set a maximum file size for image to be uploaded. + if int(os.path.getsize(imagefile)) > _MAX_FILE_SIZE: + import Image + while int(os.path.getsize(imagefile)) > _MAX_FILE_SIZE: + big_file = Image.open(imagefile) + smaller_file = big_file.resize(int(0.9 * big_file.size[0]), + int(0.9 * big_file.size[1]), + Image.ANTIALIAS) + smaller_file.save(imagefile, quality = 100) + c = pycurl.Curl() c.setopt(c.POST, 1) c.setopt(c.FOLLOWLOCATION, 1) |