diff options
author | Walter Bender <walter.bender@gmail.com> | 2012-02-18 22:41:14 (GMT) |
---|---|---|
committer | Walter Bender <walter.bender@gmail.com> | 2012-02-18 22:41:14 (GMT) |
commit | d67a8ae233995c513b1971dbe16ab249f3a2d7b1 (patch) | |
tree | 8378c8a9d580c759645cebe648a5e6ecb30b68a9 | |
parent | 7a9d0865db3aebb04211c0f269c45ffd971d9fd4 (diff) |
sync up with Turtle Blocks v136
-rw-r--r-- | TurtleArt/sprites.py | 29 | ||||
-rw-r--r-- | TurtleArt/tabasics.py | 64 | ||||
-rw-r--r-- | TurtleArt/tablock.py | 47 | ||||
-rw-r--r-- | TurtleArt/tacanvas.py | 13 | ||||
-rw-r--r-- | TurtleArt/taconstants.py | 40 | ||||
-rw-r--r-- | TurtleArt/taexporthtml.py | 24 | ||||
-rw-r--r-- | TurtleArt/taexportlogo.py | 2 | ||||
-rw-r--r-- | TurtleArt/talogo.py | 259 | ||||
-rw-r--r-- | TurtleArt/tapalette.py | 23 | ||||
-rwxr-xr-x | TurtleArt/tasprite_factory.py | 58 | ||||
-rw-r--r-- | TurtleArt/taturtle.py | 39 | ||||
-rw-r--r-- | TurtleArt/tautils.py | 468 | ||||
-rw-r--r-- | TurtleArt/tawindow.py | 342 |
13 files changed, 948 insertions, 460 deletions
diff --git a/TurtleArt/sprites.py b/TurtleArt/sprites.py index baae94f..fdf0105 100644 --- a/TurtleArt/sprites.py +++ b/TurtleArt/sprites.py @@ -120,6 +120,11 @@ class Sprites: else: self.list.insert(i, spr) + def find_in_list(self, spr): + if spr in self.list: + return True + return False + def remove_from_list(self, spr): ''' Remove a sprite from the list. ''' if spr in self.list: @@ -187,7 +192,8 @@ class Sprite: self._dy.append(0) self._dx[i] = dx self._dy[i] = dy - if isinstance(image, gtk.gdk.Pixbuf): + if isinstance(image, gtk.gdk.Pixbuf) or \ + isinstance(image, cairo.ImageSurface): w = image.get_width() h = image.get_height() else: @@ -200,14 +206,17 @@ class Sprite: self.rect.width = w + dx if h + dy > self.rect.height: self.rect.height = h + dy - surface = cairo.ImageSurface( - cairo.FORMAT_ARGB32, self.rect.width, self.rect.height) - context = cairo.Context(surface) - context = gtk.gdk.CairoContext(context) - context.set_source_pixbuf(image, 0, 0) - context.rectangle(0, 0, self.rect.width, self.rect.height) - context.fill() - self.cached_surfaces[i] = surface + if isinstance(image, cairo.ImageSurface): + self.cached_surfaces[i] = image + else: # Convert to Cairo surface + surface = cairo.ImageSurface( + cairo.FORMAT_ARGB32, self.rect.width, self.rect.height) + context = cairo.Context(surface) + context = gtk.gdk.CairoContext(context) + context.set_source_pixbuf(image, 0, 0) + context.rectangle(0, 0, self.rect.width, self.rect.height) + context.fill() + self.cached_surfaces[i] = surface def move(self, pos): ''' Move to new (x, y) position ''' @@ -352,7 +361,7 @@ class Sprite: return False if y > self.rect.y + self.rect.height: return False - return True + return self._sprites.find_in_list(self) def draw_label(self, cr): ''' Draw the label based on its attributes ''' diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py index d99c51a..60b8d91 100644 --- a/TurtleArt/tabasics.py +++ b/TurtleArt/tabasics.py @@ -67,7 +67,7 @@ from gettext import gettext as _ from tapalette import make_palette, define_logo_function from talogo import primitive_dictionary, logoerror from tautils import convert, chr_to_ord, round_int, strtype -from taconstants import BLACK, WHITE, CONSTANTS +from taconstants import BLACK, WHITE, CONSTANTS, XO30 def _num_type(x): """ Is x a number type? """ @@ -356,9 +356,14 @@ start fill block)')) def _color_palette(self): """ The basic Turtle Art color palette """ - palette = make_palette('colors', - colors=["#00FFFF", "#00A0A0"], - help_string=_('Palette of pen colors')) + if self.tw.hw == XO30: + palette = make_palette('pen', + colors=["#00FFFF", "#00A0A0"], + help_string=_('Palette of pen colors')) + else: + palette = make_palette('colors', + colors=["#00FFFF", "#00A0A0"], + help_string=_('Palette of pen colors')) palette.add_block('setcolor', style='basic-style-1arg', @@ -661,7 +666,11 @@ operators')) logo_command='greater?', help_string=_('logical greater-than operator')) self.tw.lc.def_prim( - 'greater?', 2, lambda self, x, y: primitive_dictionary['more'](x, y)) + 'greater?', 2, + lambda self, x, y: primitive_dictionary['more'](x, y)) + + if self.tw.canvas.width > 1024: + self._make_constant(palette, 'true', _('True'), 1) primitive_dictionary['less'] = self._prim_less palette.add_block('less2', @@ -675,6 +684,9 @@ operators')) self.tw.lc.def_prim( 'less?', 2, lambda self, x, y: primitive_dictionary['less'](x, y)) + if self.tw.canvas.width > 1024: + self._make_constant(palette, 'false', _('False'), 0) + primitive_dictionary['equal'] = self._prim_equal palette.add_block('equal2', hidden=True, @@ -765,7 +777,7 @@ number of seconds')) palette.add_block('if', hidden=True, style='flow-style-boolean', - label=[' ', _('if'), _('then')], + label=[_('if'), ' ', _('then')], prim_name='if', default=[None, None, 'vspace'], special_name=_('if then'), @@ -778,7 +790,7 @@ operators from Numbers palette')) palette.add_block('ifelse', hidden=True, style='flow-style-else', - label=[' ', _('if'), _('then else')], + label=[_('if'), ' ', _('then else')], prim_name='ifelse', default=[None, 'vspace', None, 'vspace'], logo_command='ifelse', @@ -788,7 +800,6 @@ boolean operators from Numbers palette')) self.tw.lc.def_prim('ifelse', 3, primitive_dictionary['ifelse'], True) palette.add_block('hspace', - hidden=True, style='flow-style-tail', label=' ', prim_name='nop', @@ -857,6 +868,7 @@ buttons')) 'box2', None, x)) palette.add_block('string', + hidden=True, style='box-style', label=_('text'), default=_('text'), @@ -883,6 +895,7 @@ buttons')) primitive_dictionary['box'] = self._prim_box palette.add_block('box', + hidden=True, style='number-style-1strarg', label=_('box'), prim_name='box', @@ -1142,6 +1155,14 @@ variable')) def _prim_careful_divide(self, x, y): """ Raise error on divide by zero """ + if type(x) == list and _num_type(y): + z = [] + for i in range(len(x)): + try: + z.append(x[i] / y) + except ZeroDivisionError: + raise logoerror("#zerodivide") + return z try: return x / y except ZeroDivisionError: @@ -1158,6 +1179,11 @@ variable')) def _prim_equal(self, x, y): """ Numeric and logical equal """ + if type(x) == list and type(y) == list: + for i in range(len(x)): + if x[i] != y[i]: + return False + return True try: return float(x) == float(y) except ValueError: @@ -1198,6 +1224,11 @@ variable')) """ Add numbers, concat strings """ if _num_type(x) and _num_type(y): return(x + y) + elif type(x) == list and type(y) == list: + z = [] + for i in range(len(x)): + z.append(x[i] + y[i]) + return(z) else: if _num_type(x): xx = str(round_int(x)) @@ -1213,6 +1244,11 @@ variable')) """ Numerical subtraction """ if _num_type(x) and _num_type(y): return(x - y) + elif type(x) == list and type(y) == list: + z = [] + for i in range(len(x)): + z.append(x[i] - y[i]) + return(z) try: return self._string_to_num(x) - self._string_to_num(y) except TypeError: @@ -1222,6 +1258,16 @@ variable')) """ Numerical multiplication """ if _num_type(x) and _num_type(y): return(x * y) + elif type(x) == list and _num_type(y): + z = [] + for i in range(len(x)): + z.append(x[i] * y) + return(z) + elif type(y) == list and _num_type(x): + z = [] + for i in range(len(y)): + z.append(y[i] * x) + return(z) try: return self._string_to_num(x) * self._string_to_num(y) except TypeError: @@ -1282,6 +1328,8 @@ variable')) return(x) if type(x) is ord: return(int(x)) + if type(x) is list: + raise logoerror("#syntaxerror") xx = convert(x.replace(self.tw.decimal_point, '.'), float) if type(xx) is float: return xx diff --git a/TurtleArt/tablock.py b/TurtleArt/tablock.py index 03ddc05..b809930 100644 --- a/TurtleArt/tablock.py +++ b/TurtleArt/tablock.py @@ -128,6 +128,7 @@ class Block: self.shapes = [None, None] self.name = name self.colors = colors + self._custom_colors = False self.scale = scale self.docks = None self.connections = None @@ -141,6 +142,7 @@ class Block: self._ei = 0 self._font_size = [6.0, 4.5] self._image = None + self._visible = True self.block_methods = { 'basic-style': self._make_basic_style, @@ -172,7 +174,6 @@ class Block: 'flow-style-tail': self._make_flow_style_tail, 'flow-style-1arg': self._make_flow_style_1arg, 'flow-style-boolean': self._make_flow_style_boolean, - 'flow-style-while': self._make_flow_style_while, 'flow-style-else': self._make_flow_style_else, 'collapsible-top': [self._make_collapsible_style_top, True, True], 'collapsible-top-no-arm': [self._make_collapsible_style_top, @@ -211,6 +212,18 @@ class Block: self.block_list.append_to_list(self) + def get_visibility(self): + ''' Should block be visible on the palette? ''' + return self._visible + + def set_visibility(self, state): + ''' Should block be visible? ''' + self._visible = state + if self._visible: + self.spr.restore() + else: + self.spr.hide() + def expandable(self): """ Can this block be expanded? """ if self.name in EXPANDABLE: @@ -282,6 +295,11 @@ class Block: self.refresh() self.spr.inval() + def set_colors(self, colors): + self.colors = colors[:] + self._custom_colors = True + self.refresh() + def refresh(self): if self.spr is None: return @@ -445,6 +463,15 @@ class Block: elif self.name in block_styles['number-style-porch']: self.spr.set_label_attributes(int(self._font_size[0] + 0.5), True, 'right', 'bottom', i) + + elif self.name in block_styles['flow-style-boolean'] or \ + self.name in block_styles['flow-style-else']: + self.spr.set_label_attributes(int(self._font_size[0] + 0.5), + True, 'left', 'middle', 0) + self.spr.set_label_attributes(int(self._font_size[1] + 0.5), + True, 'right', 'top', 1) + self.spr.set_label_attributes(int(self._font_size[1] + 0.5), + True, 'right', 'bottom', 2) elif i == 1: # top self.spr.set_label_attributes(int(self._font_size[1] + 0.5), True, 'right', 'top', i) @@ -479,6 +506,9 @@ class Block: self.block_methods['basic-style'](svg) def _set_colors(self, svg): + if self._custom_colors: + self.svg.set_colors(self.colors) + return if self.name in BOX_COLORS: self.colors = BOX_COLORS[self.name] elif self.name in special_block_colors: @@ -800,21 +830,6 @@ class Block: ['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_block_graphics(svg, self.svg.basic_flow) - 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) diff --git a/TurtleArt/tacanvas.py b/TurtleArt/tacanvas.py index 43e5ad2..bf22248 100644 --- a/TurtleArt/tacanvas.py +++ b/TurtleArt/tacanvas.py @@ -22,6 +22,7 @@ import gtk from math import sin, cos, atan, pi, sqrt +import os import pango import cairo import pangocairo @@ -552,23 +553,24 @@ class TurtleGraphics: if self.tw.saving_svg: if self.tw.running_sugar: # In Sugar, we embed the images inside the SVG - tmp_file = os.path.join(get_path(tw.activity, 'instance'), + tmp_file = os.path.join(get_path(self.tw.activity, 'instance'), 'tmpfile.png') pixbuf.save(tmp_file, 'png', {'quality': '100'}) self.tw.svg_string += self.svg.image( - x - self.width / 2, y, w, h, path, + x, y, w, h, path, image_to_base64(tmp_file, get_path(self.tw.activity, 'instance'))) + os.remove(tmp_file) else: # In GNOME, we embed a path - self.tw.svg_string += self.svg.image(x - self.width / 2, - y, w, h, path) + self.tw.svg_string += self.svg.image( + x - self.width / 2, y, w, h, path) if self.tw.sharing() and share: if self.tw.running_sugar: tmp_path = get_path(self.tw.activity, 'instance') else: tmp_path = '/tmp' - tmp_file = os.path.join(get_path(tw.activity, 'instance'), + tmp_file = os.path.join(get_path(self.tw.activity, 'instance'), 'tmpfile.png') pixbuf.save(tmp_file, 'png', {'quality': '100'}) data = image_to_base64(tmp_file, tmp_path) @@ -583,6 +585,7 @@ class TurtleGraphics: round_int(height), data]])) self.tw.send_event(event) + os.remove(tmp_file) def draw_text(self, label, x, y, size, w, share=True): ''' Draw text ''' diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py index 4dd5f50..74291fc 100644 --- a/TurtleArt/taconstants.py +++ b/TurtleArt/taconstants.py @@ -25,11 +25,12 @@ from gettext import gettext as _ # Sprite layers # -OVERLAY_LAYER = 525 -TURTLE_LAYER = 550 +OVERLAY_LAYER = 400 +TURTLE_LAYER = 500 BLOCK_LAYER = 600 CATEGORY_LAYER = 700 -TAB_LAYER = 710 +TAB_LAYER = 800 +PROTO_LAYER = 801 STATUS_LAYER = 900 TOP_LAYER = 1000 @@ -57,6 +58,8 @@ BLOCK_SCALE = [0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0] PALETTE_SCALE = 1.5 DEFAULT_TURTLE = 'Yertle' DEFAULT_TURTLE_COLORS = ['#008000', '#00A000'] +PALETTE_COLOR = "#FFD000" +TOOLBAR_COLOR = "#282828" HORIZONTAL_PALETTE = 0 VERTICAL_PALETTE = 1 BLACK = -9999 @@ -71,6 +74,7 @@ DEFAULT_SCALE = 33 XO1 = 'xo1' XO15 = 'xo1.5' XO175 = 'xo1.75' +XO30 = 'xo3.0' UNKNOWN = 'unknown' CONSTANTS = {'leftpos': None, 'toppos': None, 'rightpos': None, @@ -225,30 +229,14 @@ VOICES = {'af': 'afrikaans', 'cy': 'welsh-test', 'el': 'greek', # 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]]], + [[0, 'until', 0, 0, [None, 1, 4, None]], + [1, 'greater2', 0, 0, [0, 2, 3, None]], + [2, 'keyboard', 0, 0, [1, None]], + [3, ['number', '0'], 0, 0, [1, None]], + [4, 'wait', 0, 0, [0, 5, 6]], + [5, ['number', '0.1'], 0, 0, [4, None]], + [6, 'kbinput', 0, 0, [4, None]]], 'picturelist': [[0, 'sandwichtop_no_label', 0, 0, [None, 1]], [1, 'penup', 0, 0, [0, 2]], diff --git a/TurtleArt/taexporthtml.py b/TurtleArt/taexporthtml.py index 90dcc4d..0e6a2f1 100644 --- a/TurtleArt/taexporthtml.py +++ b/TurtleArt/taexporthtml.py @@ -69,8 +69,8 @@ def save_html(self, tw, embed_flag=True): HTML_GLUE['img'] = ('<img width="400" height="300" alt=' + \ '"Image" src="data:image/png;base64,\n', '"/>\n') - HTML_GLUE['img2'] = ('<img alt="Image" src="data:image/png;"' + \ - '"base64,\n"', '"/>\n') + HTML_GLUE['img2'] = ('<img alt="Image" src="data:image/png;' + \ + 'base64,\n', '"/>\n') """ If there are saved_pictures, put them into a .html; otherwise, @@ -78,25 +78,33 @@ def save_html(self, tw, embed_flag=True): """ htmlcode = '' if len(tw.saved_pictures) > 0: - for i, image_file in enumerate(tw.saved_pictures): + # saved_picture list is a collection of tuples of either the + # image_file or the containing dsobject and an SVG flag + for i, (image, svg_flag) in enumerate(tw.saved_pictures): htmlcode += HTML_GLUE['slide'][0] + str(i) htmlcode += HTML_GLUE['slide'][1] + \ HTML_GLUE['div'][0] + \ HTML_GLUE['h1'][0] + if tw.running_sugar: + from sugar.datastore import datastore + dobject = datastore.get(image) # dsobject.object_id + image_file = dobject.file_path + else: + image_file = image if embed_flag: f = open(image_file, 'r') imgdata = f.read() f.close() - if image_file.endswith(('.svg')): + if svg_flag: tmp = imgdata else: - imgdata = image_to_base64(image_file, - get_path(tw.activity, 'instance')) + imgdata = image_to_base64( + image_file, get_path(tw.activity, 'instance')) tmp = HTML_GLUE['img2'][0] tmp += imgdata tmp += HTML_GLUE['img2'][1] else: - if image_file.endswith(('.svg')): + if svg_flag: f = open(image_file, 'r') imgdata = f.read() f.close() @@ -135,7 +143,7 @@ def save_html(self, tw, embed_flag=True): style = HTML_GLUE['style'][0] + \ HTML_GLUE['style'][1] if len(tw.saved_pictures) > 0: - if tw.saved_pictures[0].endswith(('.svg')): + if tw.saved_pictures[0][1]: header = HTML_GLUE['html_svg'][0] style = COMMENT diff --git a/TurtleArt/taexportlogo.py b/TurtleArt/taexportlogo.py index 127689b..0d0660e 100644 --- a/TurtleArt/taexportlogo.py +++ b/TurtleArt/taexportlogo.py @@ -266,7 +266,7 @@ def _walk_stack(tw, blk_in_stack): top = find_top_block(blk_in_stack) if blk_in_stack == top: - psuedocode = tw.lc.run_blocks(top, tw.block_list.list, False) + psuedocode = tw.lc.run_blocks(top, tw.just_blocks(), False) return psuedocode else: return [] diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py index 0e28cf0..a2901eb 100644 --- a/TurtleArt/talogo.py +++ b/TurtleArt/talogo.py @@ -67,6 +67,19 @@ class logoerror(Exception): def __str__(self): return repr(self.value) +class HiddenBlock: + + def __init__(self, name, value=None): + self.name = name + self.values = [] + if value is not None: + self.values.append(value) + self.primitive = None + else: + self.primitive = name + self.connections = [] + self.docks = [] + # Utility functions def _just_stop(): @@ -122,6 +135,7 @@ class LogoCode: self.update_values = False self.gplay = None self.filepath = None + self.pixbuf = None self.dsobject = None self.start_time = None @@ -162,6 +176,11 @@ class LogoCode: self.stacks['stack2'] = None self.tw.saving_svg = False + # Save state in case there is a hidden macro expansion + self.save_blocks = None + self.save_blk = blk + self.save_while_blks = [] + if self.trace > 0: self.update_values = True else: @@ -169,13 +188,23 @@ class LogoCode: for b in blocks: b.unhighlight() + + for b in blocks: + # Hidden macro expansions + if b.name in ['while', 'until', 'forever']: + action_blk, new_blocks = self._expand_forever(b, blk, blocks) + blocks = new_blocks[:] + if b == blk: + blk = action_blk + + for b in blocks: if b.name == 'hat1': code = self._blocks_to_code(b) self.stacks['stack1'] = self._readline(code) - if b.name == 'hat2': + elif b.name == 'hat2': code = self._blocks_to_code(b) self.stacks['stack2'] = self._readline(code) - if b.name == 'hat': + elif b.name == 'hat': if b.connections is not None and len(b.connections) > 1 and \ b.connections[1] is not None: code = self._blocks_to_code(b) @@ -190,6 +219,19 @@ class LogoCode: self.stacks['stack3' + str(x)] = self._readline(code) code = self._blocks_to_code(blk) + + if self.save_blocks is not None: + # Undo any hidden macro expansion + blocks = self.save_blocks[:] + blk = self.save_blk + for b in self.save_while_blks: + if b[1] is not None: + b[0].connections[0].connections[b[1]] = b[0] + if b[2] is not None: + b[0].connections[-1].connections[b[2]] = b[0] + if b[3] is not None: + b[0].connections[-2].connections[b[3]] = b[0] + if run_flag: # debug_output("running code: %s" % (code), self.tw.running_sugar) self.start_time = time() @@ -210,7 +252,12 @@ class LogoCode: 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))) + # special case: expand 'while' and 'until' primitives + try: + code.append((blk.primitive, + self.tw.block_list.list.index(blk))) + except ValueError: + code.append(blk.primitive) # Hidden block elif len(blk.values) > 0: # Extract the value from content blocks. if blk.name == 'number': try: @@ -264,6 +311,7 @@ class LogoCode: The block associated with the command is stored as the second element in a tuple, e.g., (#forward, 16) """ + # debug_output(line, self.tw.running_sugar) res = [] while line: token = line.pop(0) @@ -430,7 +478,12 @@ class LogoCode: while (_millisecond() - starttime) < 120: try: if self.step is not None: - self.step.next() + try: + self.step.next() + except ValueError: + debug_output('generator already executing', + self.tw.running_sugar) + return False else: return False except StopIteration: @@ -494,11 +547,11 @@ class LogoCode: from tagplay import stop_media # stop_media(self) # TODO: gplay variable self.tw.canvas.clearscreen() - self.tw.lc.scale = DEFAULT_SCALE - self.tw.lc.hidden_turtle = None - self.tw.lc.start_time = time() + self.scale = DEFAULT_SCALE + self.hidden_turtle = None + self.start_time = time() for name in value_blocks: - self.tw.lc.update_label_value(name) + self.update_label_value(name) def int(self, n): """ Raise an error if n doesn't convert to int. """ @@ -562,50 +615,55 @@ class LogoCode: return int((self.tw.canvas.height * self.scale) / 100.) def insert_image(self, center=False, filepath=None, resize=True, - offset=False): + offset=False, pixbuf=False): """ Image only (at current x, y) """ if filepath is not None: self.filepath = filepath - pixbuf = None + if not pixbuf: + self.pixbuf = None w, h = self.wpercent(), self.hpercent() if w < 1 or h < 1: return - if self.dsobject is not None: + if pixbuf: # We may have to rescale the picture + if w != self.pixbuf.get_width() or h != self.pixbuf.get_height(): + self.pixbuf = self.pixbuf.scale_simple( + w, h, gtk.gdk.INTERP_BILINEAR) + elif self.dsobject is not None: try: - pixbuf = get_pixbuf_from_journal(self.dsobject, w, h) + self.pixbuf = get_pixbuf_from_journal(self.dsobject, w, h) except: debug_output("Couldn't open dsobject %s" % (self.dsobject), self.tw.running_sugar) - if pixbuf is None and \ + if self.pixbuf is None and \ self.filepath is not None and \ self.filepath != '': try: if not resize: - pixbuf = gtk.gdk.pixbuf_new_from_file(self.filepath) - w = pixbuf.get_width() - h = pixbuf.get_height() + self.pixbuf = gtk.gdk.pixbuf_new_from_file(self.filepath) + w = self.pixbuf.get_width() + h = self.pixbuf.get_height() else: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( + self.pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( self.filepath, w, h) except: self.tw.showlabel('nojournal', self.filepath) debug_output("Couldn't open filepath %s" % (self.filepath), self.tw.running_sugar) - if pixbuf is not None: + if self.pixbuf is not None: x = self.tw.canvas.xcor y = self.tw.canvas.ycor w *= self.tw.coord_scale h *= self.tw.coord_scale if center: - self.tw.canvas.draw_pixbuf(pixbuf, 0, 0, + self.tw.canvas.draw_pixbuf(self.pixbuf, 0, 0, self.x2tx() - int(w / 2), self.y2ty() - int(h / 2), w, h, self.filepath) elif offset: - self.tw.canvas.draw_pixbuf(pixbuf, 0, 0, self.x2tx(), + self.tw.canvas.draw_pixbuf(self.pixbuf, 0, 0, self.x2tx(), self.y2ty() - h, w, h, self.filepath) else: - self.tw.canvas.draw_pixbuf(pixbuf, 0, 0, + self.tw.canvas.draw_pixbuf(self.pixbuf, 0, 0, self.x2tx(), self.y2ty(), w, h, self.filepath) @@ -665,3 +723,162 @@ class LogoCode: from tagplay import play_movie_from_file play_movie_from_file(self, self.filepath, self.x2tx(), self.y2ty(), w, h) + + def _expand_forever(self, b, blk, blocks): + """ Expand a while or until block into: forever, ifelse, stopstack + Expand a forever block to run in a separate stack """ + + # TODO: create a less brittle way of doing this; having to + # manage the connections and flows locally means we may run + # into trouble if any of these block types (forever, while, + # until. ifelse, stopstack, or stack) is changed in tablock.py + if b.name == 'while': + while_blk = True + else: + while_blk = False + if b.name == 'until': + until_blk = True + else: + until_blk = False + + # We'll restore the original blocks when we are finished + if self.save_blocks is None: + self.save_blocks = blocks[:] + + # Create an action block that will jump to the new stack + action_name = '#s_forever %d' % (len(self.save_while_blks) + 1) + action_blk = HiddenBlock('stack') + action_label_blk = HiddenBlock('string', value=action_name) + + # Create the blocks we'll put in the new stack + forever_blk = HiddenBlock('forever') + if while_blk or until_blk: + ifelse_blk = HiddenBlock('ifelse') + stopstack_blk = HiddenBlock('stopstack') + inflow = None + whileflow = None + outflow = None + boolflow = None + if b.connections is not None: + inflow = b.connections[0] + if while_blk or until_blk: + boolflow = b.connections[1] + whileflow = b.connections[-2] + outflow = b.connections[-1] + + # Create action block(s) to run the code inside the forever loop + if until_blk and whileflow is not None: # run until flow at least once + action_flow_name = '#s_flow %d' % (len(self.save_while_blks) + 1) + action_first = HiddenBlock('stack') + first_label_blk = HiddenBlock('string', value=action_flow_name) + + # Assign new connections and build the docks + if inflow is not None: + i = inflow.connections.index(b) + if until_blk and whileflow is not None: + inflow.connections[i] = action_first + else: + inflow.connections[i] = action_blk + else: + i = None + if outflow is not None: + j = outflow.connections.index(b) + outflow.connections[j] = action_blk + else: + j = None + + if until_blk and whileflow is not None: + action_first.connections.append(inflow) + action_first.docks.append(['flow', True, 0, 0]) + action_first.connections.append(first_label_blk) + action_first.docks.append(['number', False, 0, 0]) + action_first.connections.append(action_blk) + action_first.docks.append(['flow', False, 0, 0]) + first_label_blk.connections.append(action_first) + first_label_blk.docks.append(['number', True, 0, 0]) + action_blk.connections.append(action_first) + else: + action_blk.connections.append(inflow) + action_blk.docks.append(['flow', True, 0, 0]) + action_blk.connections.append(action_label_blk) + action_blk.docks.append(['number', False, 0, 0]) + action_blk.connections.append(outflow) + action_blk.docks.append(['flow', False, 0, 0]) + action_label_blk.connections.append(action_blk) + action_label_blk.docks.append(['number', True, 0, 0]) + + forever_blk.connections.append(None) + forever_blk.docks.append(['flow', True, 0, 0]) + if while_blk or until_blk: + forever_blk.connections.append(ifelse_blk) + else: + forever_blk.connections.append(whileflow) + forever_blk.docks.append(['flow', False, 0, 0, '[']) + forever_blk.connections.append(outflow) + forever_blk.docks.append(['flow', False, 0, 0, ']']) + if while_blk or until_blk: + ifelse_blk.connections.append(forever_blk) + ifelse_blk.docks.append(['flow', True, 0, 0]) + ifelse_blk.connections.append(boolflow) + ifelse_blk.docks.append(['bool', False, 0, 0]) + if while_blk: + ifelse_blk.connections.append(whileflow) + ifelse_blk.connections.append(stopstack_blk) + else: # until + ifelse_blk.connections.append(stopstack_blk) + ifelse_blk.connections.append(whileflow) + ifelse_blk.docks.append(['flow', False, 0, 0, '[']) + ifelse_blk.docks.append(['flow', False, 0, 0, '][']) + ifelse_blk.connections.append(None) + ifelse_blk.docks.append(['flow', False, 0, 0, ']']) + stopstack_blk.connections.append(ifelse_blk) + stopstack_blk.docks.append(['flow', False, 0, 0]) + + if whileflow is not None: + if while_blk or until_blk: + whileflow.connections[0] = ifelse_blk + else: + whileflow.connections[0] = forever_blk + + # Create a separate stacks for the forever loop and the whileflow + code = self._blocks_to_code(forever_blk) + self.stacks['stack3' + str(action_name)] = self._readline(code) + if until_blk and whileflow is not None: + # Create a stack from the whileflow to be called from + # action_first, but then reconnect it to the ifelse block + c = whileflow.connections[0] + whileflow.connections[0] = None + code = self._blocks_to_code(whileflow) + self.stacks['stack3' + str(action_flow_name)] = self._readline(code) + whileflow.connections[0] = c + + # Save the connections so we can restore them later + if whileflow is not None: + self.save_while_blks.append([b, i, j, 0]) + else: + self.save_while_blks.append([b, i, j, None]) + + # Insert the new blocks into the stack + i = blocks.index(b) + if i == 0: + blocks_left = [] + else: + blocks_left = blocks[0:i] + if i == len(blocks) - 1: + blocks_right = [] + else: + blocks_right = blocks[i + 1:] + blocks = blocks_left[:] + if until_blk and whileflow is not None: + blocks.append(action_first) + blocks.append(action_blk) + blocks.append(forever_blk) + if while_blk or until_blk: + blocks.append(ifelse_blk) + blocks.append(stopstack_blk) + blocks.extend(blocks_right) + + if until_blk and whileflow is not None: + return action_first, blocks + else: + return action_blk, blocks diff --git a/TurtleArt/tapalette.py b/TurtleArt/tapalette.py index 61fcb14..0bdf3d6 100644 --- a/TurtleArt/tapalette.py +++ b/TurtleArt/tapalette.py @@ -31,6 +31,7 @@ logo_functions = {} special_names = {} # Names for blocks without names for popup help content_blocks = ['number', 'string', 'description', 'audio', 'video', 'journal'] +hidden_proto_blocks = [] # proto blocks that are (at least initially) hidden value_blocks = [] # blocks whose labels are updated get added here special_block_colors = {} block_styles = {'basic-style': [], @@ -155,8 +156,9 @@ class Palette(): block.set_colors(colors) block.set_value_block(value_block) block.set_content_block(content_block) - if not hidden: - block.set_palette(self._name) + block.set_palette(self._name) + if hidden: + block.set_hidden() block.add_block() @@ -179,10 +181,13 @@ def palette_name_to_index(palette_name): else: return None + def define_logo_function(key, value): - ''' Add a logo function to the table. ''' + ''' Add a logo function to the table (not necessarily associated + with a block, e.g., color lookup tables) ''' logo_functions[key] = value + class Block(): """ a class for defining new block primitives """ @@ -199,12 +204,18 @@ class Block(): self._value_block = False self._content_block = False self._colors = None + self._hidden = False def add_block(self, position=None): if self._name is None: debug_output('You must specify a name for your block') return + # FIXME: Does the block already exist? A block can live on + # multiple palettes, but it can only have one set of + # atttributes. So if this is a redefinition, remove it from + # all lists except palettes before regeneration. + if self._style is None: debug_output('You must specify a style for your block') return @@ -253,6 +264,12 @@ class Block(): if self._colors is not None: special_block_colors[self._name] = self._colors + if self._hidden: + hidden_proto_blocks.append(self._name) + + def set_hidden(self): + self._hidden = True + def set_colors(self, colors=None): self._colors = colors diff --git a/TurtleArt/tasprite_factory.py b/TurtleArt/tasprite_factory.py index d05902d..963c37c 100755 --- a/TurtleArt/tasprite_factory.py +++ b/TurtleArt/tasprite_factory.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -#Copyright (c) 2009,10 Walter Bender +#Copyright (c) 2009-12 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 @@ -25,7 +25,8 @@ pygtk.require('2.0') import gtk import os -from taconstants import HIT_RED, HIT_GREEN, HIDE_WHITE, SHOW_WHITE +from taconstants import HIT_RED, HIT_GREEN, HIDE_WHITE, SHOW_WHITE, \ + PALETTE_COLOR, TOOLBAR_COLOR class SVG: @@ -90,6 +91,8 @@ class SVG: """ def basic_block(self): + ''' The most common block type: used for 0, 1, 2, or 3 + argument commands, stacks, et al. ''' self.reset_min_max() (x, y) = self._calculate_x_y() self.margins[2] = 0 @@ -136,6 +139,8 @@ class SVG: return self.header() + svg def invisible(self): + ''' A block that is invisible but still has connectors: used + when collapsing stacks. ''' self.reset_min_max() (x, y) = self._calculate_x_y() self.margins[2] = 0 @@ -155,6 +160,7 @@ class SVG: return self.header() + self.footer() def basic_flow(self): + ''' A flow block includes an arm that holds a branch in the flow ''' self.reset_min_max() (x, y) = self._calculate_x_y() self.margins[2] = 0 @@ -183,6 +189,7 @@ class SVG: svg += self._rline_to(self._radius * 3 + self._slot_x * 2, 0) else: svg += self._rline_to(self._radius + self._slot_x, 0) + save_y = self._y hh = self._x svg += self._corner(1, 1) svg += self._rline_to(-self._radius, 0) @@ -211,9 +218,14 @@ class SVG: if self._show is True: svg += self._show_dot() svg += self.footer() + if self._bool is True: # move secondary labels to arm + self.margins[2] = self._radius * 1.5 * self._scale + self.margins[3] = (self._max_y - save_y - self._radius + \ + self._stroke_width) * self._scale return self.header() + svg def portfolio(self): + ''' Deprecated block ''' self.reset_min_max() (x, y) = self._calculate_x_y() self.margins[0] = int(x + 2 * self._stroke_width + 0.5) @@ -255,6 +267,7 @@ class SVG: return self.header() + svg def basic_box(self): + ''' Basic argument style used for numbers, text, media ''' self.reset_min_max() self.set_outie(True) x = self._stroke_width / 2.0 + self._innie_x1 + self._innie_x2 @@ -277,6 +290,7 @@ class SVG: return self.header() + svg def boolean_and_or(self): + ''' Booleans are in a class of their own ''' self.reset_min_max() svg = self._start_boolean(self._stroke_width / 2.0, self._radius * 5.5 + self._stroke_width / \ @@ -317,6 +331,7 @@ class SVG: return self.header() + svg def boolean_not(self): + ''' Booleans are in a class of their own ''' self.reset_min_max() svg = self._start_boolean(self._stroke_width / 2.0, self._radius * \ 2.0 + self._stroke_width / 2.0) @@ -339,6 +354,7 @@ class SVG: return self.header() + svg def boolean_compare(self): + ''' Booleans are in a class of their own ''' self.reset_min_max() yoffset = self._radius * 2 + 2 * self._innie_y2 + \ self._innie_spacer + self._stroke_width / 2.0 + \ @@ -377,7 +393,34 @@ class SVG: self.margins[2] = int(self._stroke_width * self._scale) return self.header() + svg + def triangle_up(self, colors): + ''' A triangle that points up ''' + self.reset_min_max() + self._fill, self._stroke = colors[0], colors[1] + self._width, self._height = 55 * self._scale, 55 * self._scale + svg = self.new_path(5, 50) + svg += self._rline_to(22.5, -45) + svg += self._rline_to(22.5, 45) + svg += self._close_path() + svg += self.style() + svg += self.footer() + return self.header() + svg + + def triangle_down(self, colors): + ''' A triangle that points down ''' + self.reset_min_max() + self._fill, self._stroke = colors[0], colors[1] + self._width, self._height = 55 * self._scale, 55 * self._scale + svg = self.new_path(5, 5) + svg += self._rline_to(22.5, 45) + svg += self._rline_to(22.5, -45) + svg += self._close_path() + svg += self.style() + svg += self.footer() + return self.header() + svg + def turtle(self, colors): + ''' Turtles are just another block ''' self.reset_min_max() self._fill, self._stroke = colors[0], colors[1] @@ -459,9 +502,10 @@ class SVG: return self.header() + svg def palette(self, width, height): + ''' Just a rectangle with a hide button. ''' self.reset_min_max() self._width, self._height = width, height - self._fill, self._stroke = "#FFD000", "none" + self._fill, self._stroke = PALETTE_COLOR, "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 @@ -470,14 +514,17 @@ class SVG: return self.header() + svg def toolbar(self, width, height): + ''' Just a rectangle ''' self.reset_min_max() self._width, self._height = width, height - self._fill, self._stroke = "#282828", "none" + self._fill, self._stroke = TOOLBAR_COLOR, "none" svg = self._rect(width, height, 0, 0) svg += self.footer() return self.header() + svg def sandwich_top(self, innie_flag=True): + ''' Special block for the top of a collapsible stack; includes + an 'arm" that extends down the left side of a stack ''' self.reset_min_max() x = self._stroke_width / 2.0 y = self._stroke_width / 2.0 + self._radius @@ -516,6 +563,9 @@ class SVG: return self.header() + svg def sandwich_bottom(self): + ''' Special block for the bottom of a collapsible stack; + includes a connection to the 'arm" that extends down the left + side of a stack ''' self.reset_min_max() x = self._stroke_width / 2.0 y = self._stroke_width / 2.0 diff --git a/TurtleArt/taturtle.py b/TurtleArt/taturtle.py index c7bb768..c13c494 100644 --- a/TurtleArt/taturtle.py +++ b/TurtleArt/taturtle.py @@ -20,8 +20,10 @@ #THE SOFTWARE. from random import uniform -from math import sin, cos, pi +from math import sin, cos, pi, sqrt from gettext import gettext as _ +import gtk +import cairo from taconstants import TURTLE_LAYER, DEFAULT_TURTLE_COLORS from tasprite_factory import SVG, svg_str_to_pixbuf @@ -176,25 +178,38 @@ class Turtle: self.shapes = generate_turtle_pixbufs(self.colors) self.set_heading(self.heading) - def set_shapes(self, shapes): + def set_shapes(self, shapes, i=0): """ Reskin the turtle """ n = len(shapes) - if n == SHAPES: + if n == 1 and i > 0: # set shape[i] + if i < len(self.shapes): + self.shapes[i] = shapes[0] + elif n == SHAPES: # all shapes have been precomputed self.shapes = shapes[:] - else: + else: # rotate shapes if n != 1: debug_output("%d images passed to set_shapes: ignoring" % (n), self.tw.running_sugar) - images = [shapes[0]] - if self.heading == 0: - for i in range(3): - images.append(images[i].rotate_simple(270)) + if self.heading == 0: # rotate the shapes + images = [] + w, h = shapes[0].get_width(), shapes[0].get_height() + nw = nh = int(sqrt(w * w + h * h)) for i in range(SHAPES): - j = (i + 4) % SHAPES - self.shapes[j] = images[int(j / 9)] - else: + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, nw, nh) + context = cairo.Context(surface) + context = gtk.gdk.CairoContext(context) + context.translate(nw / 2., nh / 2.) + context.rotate(i * 10 * pi / 180.) + context.translate(-nw / 2., -nh / 2.) + context.set_source_pixbuf(shapes[0], (nw - w) / 2., + (nh - h) / 2.) + context.rectangle(0, 0, nw, nh) + context.fill() + images.append(surface) + self.shapes = images[:] + else: # associate shape with image at current heading j = int(self.heading + 5) % 360 / (360 / SHAPES) - self.shapes[j] = images[0] + self.shapes[j] = shapes[0] self.custom_shapes = True self.show() diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py index a402563..97b396d 100644 --- a/TurtleArt/tautils.py +++ b/TurtleArt/tautils.py @@ -23,7 +23,7 @@ import gtk import cairo import pickle import subprocess -import os.path +import os from gettext import gettext as _ try: @@ -42,14 +42,17 @@ except (ImportError, AttributeError): from StringIO import StringIO from taconstants import COLLAPSIBLE, HIT_HIDE, HIT_SHOW, XO1, XO15, XO175, \ - UNKNOWN + XO30, UNKNOWN + +SANDWICHES = ['sandwichtop', 'sandwichtop_no_label', 'sandwichtop_no_arm', + 'sandwichtop_no_arm_no_label'] import logging _logger = logging.getLogger('turtleart-activity') def debug_output(message_string, running_sugar=False): - """ unified debugging output """ + ''' unified debugging output ''' if running_sugar: _logger.debug(message_string) else: @@ -57,7 +60,7 @@ def debug_output(message_string, running_sugar=False): def error_output(message_string, running_sugar=False): - """ unified debugging output """ + ''' unified debugging output ''' if running_sugar: _logger.error(message_string) else: @@ -92,7 +95,7 @@ def convert(x, fn, try_ord=True): def chr_to_ord(x): - """ Try to comvert a string to an ord """ + ''' Try to comvert a string to an ord ''' if strtype(x) and len(x) == 1: try: return ord(x[0]), True @@ -102,7 +105,7 @@ def chr_to_ord(x): def strtype(x): - """ Is x a string type? """ + ''' Is x a string type? ''' if type(x) == str: return True if type(x) == unicode: @@ -111,15 +114,15 @@ def strtype(x): def magnitude(pos): - """ Calculate the magnitude of the distance between to blocks. """ + ''' 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. """ + ''' Load JSON data using what ever resources are available. ''' if OLD_SUGAR_SYSTEM is True: - _listdata = json.read(text) + listdata = json.read(text) else: # Strip out leading and trailing whitespace, nulls, and newlines clean_text = text.lstrip() @@ -132,137 +135,139 @@ def json_load(text): while left_count > right_count: clean_text += ']' right_count = clean_text.count(']') - _io = StringIO(clean_text) + io = StringIO(clean_text) try: - _listdata = jload(_io) + listdata = jload(io) except ValueError: # Assume that text is ascii list - _listdata = text.split() - for i, value in enumerate(_listdata): - _listdata[i] = convert(value, float) + listdata = text.split() + for i, value in enumerate(listdata): + listdata[i] = convert(value, float) # json converts tuples to lists, so we need to convert back, - return _tuplify(_listdata) + return _tuplify(listdata) def _tuplify(tup): - """ Convert to tuples """ + ''' 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. """ + ''' Get a connection block ID. ''' if connection is not None and hasattr(connection, 'id'): return connection.id return None def json_dump(data): - """ Save data using available JSON tools. """ + ''' 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() + 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, + ''' 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) + 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, + ''' 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) + 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) + dialog.set_current_name(save_file_name + suffix) + return do_dialog(dialog, suffix, load_save_folder) def chooser(parent_window, filter, action): - """ Choose an object from the datastore and take some action """ + ''' Choose an object from the datastore and take some action ''' from sugar.graphics.objectchooser import ObjectChooser - _chooser = None + chooser = None try: - _chooser = ObjectChooser(parent=parent_window, what_filter=filter) + chooser = ObjectChooser(parent=parent_window, what_filter=filter) except TypeError: - _chooser = ObjectChooser(None, parent_window, + chooser = ObjectChooser(None, parent_window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) - if _chooser is not None: + if chooser is not None: try: - result = _chooser.run() + result = chooser.run() if result == gtk.RESPONSE_ACCEPT: - dsobject = _chooser.get_selected_object() + dsobject = chooser.get_selected_object() action(dsobject) dsobject.destroy() finally: - _chooser.destroy() - del _chooser + chooser.destroy() + del chooser def data_from_file(ta_file): - """ Open the .ta file, ignoring any .png file that might be present. """ - file_handle = open(ta_file, "r") + ''' Open the .ta file, ignoring any .png file that might be present. ''' + file_handle = open(ta_file, 'r') # # We try to maintain read-compatibility with all versions of Turtle Art. # Try pickle first; then different versions of json. # try: - _data = pickle.load(file_handle) + 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) + text = file_handle.read() + data = data_from_string(text) file_handle.close() - return _data + return data def data_from_string(text): - """ JSON load data from a string. """ + ''' JSON load data from a string. ''' return json_load(text.replace(']],\n', ']], ')) def data_to_file(data, ta_file): - """ Write data to a file. """ - file_handle = file(ta_file, "w") + ''' 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. """ + ''' JSON dump a string. ''' return json_dump(data).replace(']], ', ']],\n') def do_dialog(dialog, suffix, load_save_folder): - """ Open a file dialog. """ - _result = None + ''' Open a file dialog. ''' + result = None file_filter = gtk.FileFilter() file_filter.add_pattern('*' + suffix) - file_filter.set_name("Turtle Art") + 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() + 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 + return result, load_save_folder def save_picture(canvas, file_name): - """ Save the canvas to a file """ + ''' Save the canvas to a file ''' x_surface = canvas.canvas.get_target() img_surface = cairo.ImageSurface(cairo.FORMAT_RGB24, canvas.width, canvas.height) @@ -273,6 +278,7 @@ def save_picture(canvas, file_name): def get_canvas_data(canvas): + ''' Get pixel data from the turtle canvas ''' x_surface = canvas.canvas.get_target() img_surface = cairo.ImageSurface(cairo.FORMAT_RGB24, canvas.width, canvas.height) @@ -282,116 +288,117 @@ def get_canvas_data(canvas): return img_surface.get_data() - def save_svg(string, file_name): - """ Write a string to a file. """ - file_handle = file(file_name, "w") + ''' 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. """ + ''' Load a pixbuf from a Journal object. ''' try: - _pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(dsobject.file_path, - int(w), int(h)) + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(dsobject.file_path, + int(w), int(h)) except: try: - _pixbufloader = \ + 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() + 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 + pixbuf = None + return pixbuf def get_path(activity, subpath): - """ Find a Rainbow-approved place for temporary files. """ + ''' 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)) + return(os.path.join(os.environ['HOME'], '.sugar/default', + 'org.laptop.TurtleArtActivity', subpath)) def image_to_base64(image_path, tmp_path): - """ Convert an image to base64-encoded data """ + ''' Convert an image to base64-encoded data ''' base64 = os.path.join(tmp_path, 'base64tmp') - cmd = "base64 <" + image_path + " >" + base64 + cmd = 'base64 <' + image_path + ' >' + base64 subprocess.check_call(cmd, shell=True) file_handle = open(base64, 'r') data = file_handle.read() file_handle.close() + os.remove(base64) return data def base64_to_image(data, path_name): - """ Convert base64-encoded data to an image """ + ''' Convert base64-encoded data to an image ''' base64 = os.path.join(path_name, 'base64tmp') file_handle = open(base64, 'w') file_handle.write(data) file_handle.close() file_name = os.path.join(path_name, 'imagetmp.png') - cmd = "base64 -d <" + base64 + ">" + file_name + cmd = 'base64 -d <' + base64 + '>' + file_name subprocess.check_call(cmd, shell=True) return file_name def movie_media_type(name): - """ Is it movie media? """ + ''' Is it movie media? ''' return name.lower().endswith(('.ogv', '.vob', '.mp4', '.wmv', '.mov', '.mpeg', '.ogg', '.webm')) def audio_media_type(name): - """ Is it audio media? """ + ''' Is it audio media? ''' return name.lower().endswith(('.oga', '.m4a')) def image_media_type(name): - """ Is it image media? """ + ''' Is it image media? ''' return name.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.tiff', '.tif', '.svg')) def text_media_type(name): - """ Is it text media? """ + ''' Is it text media? ''' return name.lower().endswith(('.txt', '.py', '.lg', '.rtf')) def round_int(num): - """ Remove trailing decimal places if number is an int """ + ''' Remove trailing decimal places if number is an int ''' try: float(num) except TypeError: - raise pythonerror("#syntaxerror") + raise pythonerror('#syntaxerror') if int(float(num)) == num: return int(num) else: if float(num) < 0: - _nn = int((float(num) - 0.005) * 100) / 100. + 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 + 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. """ + ''' Calculate the maximum size for placing an image onto a sprite. ''' return int(max(spr.label_safe_width(), 1)), \ int(max(spr.label_safe_height(), 1)) + # Collapsible stacks live between 'sandwichtop' and 'sandwichbottom' blocks def reset_stack_arm(top): - """ When we undock, retract the 'arm' that extends from 'sandwichtop'. """ + ''' When we undock, retract the 'arm' that extends from 'sandwichtop'. ''' if top is not None and top.name in ['sandwichtop', 'sandwichtop_no_label']: if top.ey > 0: top.reset_y() @@ -400,109 +407,103 @@ def reset_stack_arm(top): def grow_stack_arm(top): - """ When we dock, grow an 'arm' from 'sandwichtop'. """ + ''' When we dock, grow an 'arm' from 'sandwichtop'. ''' if top is not None and top.name in ['sandwichtop', 'sandwichtop_no_label']: - _bot = find_sandwich_bottom(top) - if _bot is None: + 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) + 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.svg.set_hide(True) top.refresh() def find_sandwich_top(blk): - """ Find the sandwich top above this block. """ + ''' 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: + cblk = blk.connections[0] + while cblk is not None: + if cblk.name in COLLAPSIBLE: return None - if _blk.name in ['repeat', 'if', 'ifelse', 'forever', 'while']: - if blk != _blk.connections[len(_blk.connections) - 1]: + if cblk.name in ['repeat', 'if', 'ifelse', 'forever', 'while']: + if blk != cblk.connections[len(cblk.connections) - 1]: return None - if _blk.name in ['sandwichtop', 'sandwichtop_no_label', - 'sandwichtop_no_arm', 'sandwichtop_no_arm_no_label']: - return _blk - blk = _blk - _blk = _blk.connections[0] + if cblk.name in SANDWICHES: + return cblk + blk = cblk + cblk = cblk.connections[0] return None def find_sandwich_bottom(blk): - """ Find the sandwich bottom below this block. """ + ''' 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 in ['sandwichtop', 'sandwichtop_no_label', - 'sandwichtop_no_arm', 'sandwichtop_no_arm_no_label']: + cblk = blk.connections[len(blk.connections) - 1] + while cblk is not None: + if cblk.name in SANDWICHES: return None - if _blk.name in COLLAPSIBLE: - return _blk - _blk = _blk.connections[len(_blk.connections) - 1] + if cblk.name in COLLAPSIBLE: + return cblk + cblk = cblk.connections[len(cblk.connections) - 1] return None def find_sandwich_top_below(blk): - """ Find the sandwich top below this block. """ - if blk.name in ['sandwichtop', 'sandwichtop_no_label', - 'sandwichtop_no_arm', 'sandwichtop_no_arm_no_label']: + ''' Find the sandwich top below this block. ''' + if blk.name in SANDWICHES: 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 in ['sandwichtop', 'sandwichtop_no_label', - 'sandwichtop_no_arm', 'sandwichtop_no_arm_no_label']: - return _blk - _blk = _blk.connections[len(_blk.connections) - 1] + cblk = blk.connections[len(blk.connections) - 1] + while cblk is not None: + if cblk.name in SANDWICHES: + return cblk + cblk = cblk.connections[len(cblk.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) + ''' 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 gblk in group: + if not hit_bottom and gblk == bot: + hit_bottom = True + if len(gblk.values) == 0: + gblk.values.append(0) else: - _blk.values[0] = 0 - _olddx = _blk.docks[1][2] - _olddy = _blk.docks[1][3] + gblk.values[0] = 0 + olddx = gblk.docks[1][2] + olddy = gblk.docks[1][3] # Replace 'sandwichcollapsed' shape with 'sandwichbottom' shape - _blk.name = 'sandwichbottom' - _blk.spr.set_label(' ', 1) - _blk.svg.set_show(False) - _blk.svg.set_hide(True) - _blk.refresh() + gblk.name = 'sandwichbottom' + gblk.spr.set_label(' ', 1) + gblk.svg.set_show(False) + gblk.svg.set_hide(True) + gblk.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)) + you = gblk.connections[0] + (yx, yy) = you.spr.get_xy() + yd = you.docks[len(you.docks) - 1] + (bx, by) = gblk.spr.get_xy() + dx = yx + yd[2] - gblk.docks[0][2] - bx + dy = yy + yd[3] - gblk.docks[0][3] - by + gblk.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 + dx += gblk.docks[1][2] - olddx + dy += gblk.docks[1][3] - olddy else: - if not _hit_bottom: - _blk.spr.restore() - _blk.status = None + if not hit_bottom: + gblk.spr.restore() + gblk.status = None else: - _blk.spr.move_relative((_dx, _dy)) + gblk.spr.move_relative((dx, dy)) # Add 'sandwichtop' arm if top.name == 'sandwichtop_no_arm': top.name = 'sandwichtop' @@ -515,7 +516,7 @@ def restore_stack(top): def uncollapse_forks(top, looping=False): - """ From the top, find and restore any collapsible stacks on forks. """ + ''' From the top, find and restore any collapsible stacks on forks. ''' if top == None: return if looping and top.name in ['sandwichtop_no_arm', @@ -524,68 +525,66 @@ def uncollapse_forks(top, looping=False): return if len(top.connections) == 0: return - _blk = top.connections[len(top.connections) - 1] - while _blk is not None: - if _blk.name in COLLAPSIBLE: + cblk = top.connections[len(top.connections) - 1] + while cblk is not None: + if cblk.name in COLLAPSIBLE: return - if _blk.name in ['sandwichtop_no_arm', 'sandwichtop_no_arm_no_label']: - restore_stack(_blk) + if cblk.name in ['sandwichtop_no_arm', 'sandwichtop_no_arm_no_label']: + restore_stack(cblk) return # Follow a fork - if _blk.name in ['repeat', 'if', 'ifelse', 'forever', 'while', + if cblk.name in ['repeat', 'if', 'ifelse', 'forever', 'while', 'until']: top = find_sandwich_top_below( - _blk.connections[len(_blk.connections) - 2]) + cblk.connections[len(cblk.connections) - 2]) if top is not None: uncollapse_forks(top, True) - if _blk.name == 'ifelse': + if cblk.name == 'ifelse': top = find_sandwich_top_below( - _blk.connections[len(_blk.connections) - 3]) + cblk.connections[len(cblk.connections) - 3]) if top is not None: uncollapse_forks(top, True) - _blk = _blk.connections[len(_blk.connections) - 1] + cblk = cblk.connections[len(cblk.connections) - 1] return def collapse_stack(top): - """ Hide all the blocks between the sandwich top and sandwich bottom. """ + ''' 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 + hit_bottom = False + bot = find_sandwich_bottom(top) + group = find_group(top.connections[len(top.connections) - 1]) + for gblk in group: + if not hit_bottom and gblk == bot: + hit_bottom = True # Replace 'sandwichbottom' with invisible 'sandwichcollapsed' - if len(_blk.values) == 0: - _blk.values.append(1) + if len(gblk.values) == 0: + gblk.values.append(1) else: - _blk.values[0] = 1 - _olddx = _blk.docks[1][2] - _olddy = _blk.docks[1][3] - _blk.name = 'sandwichcollapsed' - _blk.resize() - _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)) + gblk.values[0] = 1 + olddx = gblk.docks[1][2] + olddy = gblk.docks[1][3] + gblk.name = 'sandwichcollapsed' + gblk.resize() + you = find_sandwich_top(gblk) + (yx, yy) = you.spr.get_xy() + yd = you.docks[len(you.docks) - 1] + (bx, by) = gblk.spr.get_xy() + dx = yx + yd[2] - gblk.docks[0][2] - bx + dy = yy + yd[3] - gblk.docks[0][3] - by + gblk.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 + dx += gblk.docks[1][2] - olddx + dy += gblk.docks[1][3] - olddy else: - if not _hit_bottom: - _blk.spr.hide() - _blk.status = 'collapsed' + if not hit_bottom: + gblk.spr.hide() + gblk.status = 'collapsed' else: - _blk.spr.move_relative((_dx, _dy)) + gblk.spr.move_relative((dx, dy)) # Remove 'sandwichtop' arm if top.name == 'sandwichtop' or top.name == 'sandwichtop_no_arm': top.name = 'sandwichtop_no_arm' @@ -601,7 +600,7 @@ def collapse_stack(top): def collapsed(blk): - """ Is this stack collapsed? """ + ''' 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 @@ -609,7 +608,7 @@ def collapsed(blk): def collapsible(blk): - """ Can this stack be collapsed? """ + ''' Can this stack be collapsed? ''' if blk is None or blk.name not in COLLAPSIBLE: return False if find_sandwich_top(blk) is None: @@ -618,32 +617,32 @@ def collapsible(blk): 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 == HIT_HIDE: + ''' Did the sprite's hide (contract) button get hit? ''' + red, green, blue, alpha = spr.get_pixel((x, y)) + if red == HIT_HIDE: 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 == HIT_SHOW: + ''' Did the sprite's show (expand) button get hit? ''' + red, green, blue, alpha = spr.get_pixel((x, y)) + if green == HIT_SHOW: return True else: return False def numeric_arg(value): - """ Dock test: looking for a numeric 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 """ + ''' Dock test: looking for a zero argument ''' if numeric_arg(value): if convert(value, float) == 0: return True @@ -651,7 +650,7 @@ def zero_arg(value): def neg_arg(value): - """ Dock test: looking for a negative argument """ + ''' Dock test: looking for a negative argument ''' if numeric_arg(value): if convert(value, float) < 0: return True @@ -659,7 +658,7 @@ def neg_arg(value): def journal_check(blk1, blk2, dock1, dock2): - """ Dock blocks only if arg is Journal block """ + ''' Dock blocks only if arg is Journal block ''' if blk1 == None or blk2 == None: return True if (blk1.name == 'skin' and dock1 == 1) and blk2.name != 'journal': @@ -670,7 +669,7 @@ def journal_check(blk1, blk2, dock1, dock2): def arithmetic_check(blk1, blk2, dock1, dock2): - """ Dock strings only if they convert to numbers. Avoid /0 and root(-1)""" + ''' 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\ @@ -704,7 +703,7 @@ def arithmetic_check(blk1, blk2, dock1, dock2): 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; + # Non-numeric stings are OK only if both args are strings; # Lots of test conditions... if dock1 == 1 and blk1.connections[2] is not None: if blk1.connections[2].name == 'number': @@ -745,25 +744,24 @@ def arithmetic_check(blk1, blk2, dock1, dock2): def xy(event): - """ Where is the mouse event? """ + ''' Where is the mouse event? ''' return map(int, event.get_coords()) -""" -Utilities related to finding blocks in stacks. -""" + +# 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': + ''' 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. """ + ''' Find the top block in a stack. ''' if blk is None: return None if len(blk.connections) == 0: @@ -774,7 +772,7 @@ def find_top_block(blk): def find_start_stack(blk): - """ Find a stack with a 'start' block on top. """ + ''' Find a stack with a 'start' block on top. ''' if blk is None: return False if find_top_block(blk).name == 'start': @@ -784,34 +782,34 @@ def find_start_stack(blk): def find_group(blk): - """ Find the connected group of block in a stack. """ + ''' Find the connected group of block in a stack. ''' if blk is None: return [] - _group = [blk] + 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 + for cblk in blk.connections[1:]: + if cblk is not None: + group.extend(find_group(cblk)) + return group def find_blk_below(blk, name): - """ Find a specific block below this block. """ + ''' 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 + group = find_group(blk) + for gblk in _group: + if gblk.name == name: + return gblk return None def get_hardware(): - """ Determine whether we are using XO 1.0, 1.5, or "unknown" hardware """ + ''' Determine whether we are using XO 1.0, 1.5, or 'unknown' hardware ''' product = _get_dmi('product_name') if product is None: if os.path.exists('/sys/devices/platform/lis3lv02d/position'): - return XO175 # FIXME: temporary check for XO 1.75 + return XO175 # FIXME: temporary check for XO 1.75 and XO 3.0 elif os.path.exists('/etc/olpc-release') or \ os.path.exists('/sys/power/olpc-pm'): return XO1 diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py index 4b097fb..77459e5 100644 --- a/TurtleArt/tawindow.py +++ b/TurtleArt/tawindow.py @@ -49,11 +49,11 @@ from taconstants import HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE, \ MACROS, TOP_LAYER, BLOCK_LAYER, OLD_NAMES, DEFAULT_TURTLE, TURTLE_LAYER, \ CURSOR, EXPANDABLE, COLLAPSIBLE, DEAD_DICTS, DEAD_KEYS, NO_IMPORT, \ TEMPLATES, PYTHON_SKIN, PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK, \ - EXPANDABLE_ARGS, XO1, XO15, XO175, TITLEXY, CONTENT_ARGS, \ - CONSTANTS, EXPAND_SKIN + EXPANDABLE_ARGS, XO1, XO15, XO175, XO30, TITLEXY, CONTENT_ARGS, \ + CONSTANTS, EXPAND_SKIN, PROTO_LAYER from tapalette import palette_names, palette_blocks, expandable_blocks, \ block_names, content_blocks, default_values, special_names, block_styles, \ - help_strings + help_strings, hidden_proto_blocks from talogo import LogoCode from tacanvas import TurtleGraphics from tablock import Blocks, Block @@ -73,6 +73,8 @@ from sprites import Sprites, Sprite if GST_AVAILABLE: from tagplay import stop_media +MOTION_THRESHOLD = 6 +SNAP_THRESHOLD = 200 class TurtleArtWindow(): """ TurtleArt Window class abstraction """ @@ -190,6 +192,7 @@ class TurtleArtWindow(): self.selected_blk = None self.selected_spr = None self.selected_turtle = None + self.triangle_sprs = [] self.drag_group = None self.drag_turtle = 'move', 0, 0 self.drag_pos = 0, 0 @@ -206,6 +209,8 @@ class TurtleArtWindow(): self.sprite_list = None self.canvas = TurtleGraphics(self, self.width, self.height) + if self.hw == XO175 and self.canvas.width == 1024: + self.hw = XO30 # FIXME: temporary test if self.interactive_mode: self.sprite_list.set_cairo_context(self.canvas.canvas) @@ -290,8 +295,8 @@ class TurtleArtWindow(): self._plugins.append(plugins.values()[0](self)) debug_output('successfully importing %s' % (plugin_class), self.running_sugar) - except ImportError: - debug_output('failed to import %s' % (plugin_class), + except ImportError as e: + debug_output('failed to import %s: %s' % (plugin_class, str(e)), self.running_sugar) ''' exec f in globals(), plugins @@ -313,8 +318,18 @@ class TurtleArtWindow(): icon_theme.append_search_path(icon_path) self._icon_paths.append(icon_path) + def _get_plugin_instance(self, plugin_name): + """ Returns the plugin 'plugin_name' instance """ + list_plugins = self._get_plugins_from_plugins_dir( + self._get_plugin_home()) + if plugin_name in list_plugins: + number_plugin = list_plugins.index(plugin_name) + return self._plugins[number_plugin] + else: + return None + def _setup_plugins(self): - """ Initial setup -- call just once. """ + """ Initial setup -- called just once. """ for plugin in self._plugins: plugin.setup() @@ -329,7 +344,7 @@ class TurtleArtWindow(): plugin.stop() def clear_plugins(self): - """ Clear is called from the clean block and erase button """ + """ Clear is called from the clean block and erase button. """ for plugin in self._plugins: if hasattr(plugin, 'clear'): plugin.clear() @@ -344,7 +359,7 @@ class TurtleArtWindow(): for plugin in self._plugins: plugin.return_to_foreground() - def _quit_plugins(self): + def quit_plugins(self): """ Quit is called upon program exit. """ for plugin in self._plugins: plugin.quit() @@ -407,6 +422,20 @@ class TurtleArtWindow(): self.toolbar_shapes[name].type = 'toolbar' self.toolbar_shapes['stopiton'].hide() + # Make the triangle buttons for adjusting numeric values + svg = SVG() + svg.set_scale(self.scale) + self.triangle_sprs.append(Sprite(self.sprite_list, 0, 0, + svg_str_to_pixbuf(svg.triangle_up(["#FF00FF", "#A000A0"])))) + self.triangle_sprs[-1].set_label('+') + self.triangle_sprs[-1].type = 'control' + self.triangle_sprs[-1].hide() + self.triangle_sprs.append(Sprite(self.sprite_list, 0, 0, + svg_str_to_pixbuf(svg.triangle_down(["#FF00FF", "#A000A0"])))) + self.triangle_sprs[-1].set_label('-') + self.triangle_sprs[-1].type = 'control' + self.triangle_sprs[-1].hide() + def set_sharing(self, shared): self._sharing = shared @@ -587,8 +616,13 @@ class TurtleArtWindow(): self.activity.recenter() self.show_palette() - def show_palette(self, n=0): - """ Show palette """ + def show_palette(self, n=None): + """ Show palette. """ + if n is None: + if self.selected_palette is None: + n = 0 + else: + n = self.selected_palette self.show_toolbar_palette(n) self.palette_button[self.orientation].set_layer(TAB_LAYER) self.palette_button[2].set_layer(TAB_LAYER) @@ -674,7 +708,8 @@ class TurtleArtWindow(): if blk.name in BLOCKS_WITH_SKIN: self._resize_skin(blk) - def show_toolbar_palette(self, n, init_only=False, regenerate=False): + def show_toolbar_palette(self, n, init_only=False, regenerate=False, + show=True): """ Show the toolbar palettes, creating them on init_only """ # If we are running the 0.86+ toolbar, the selectors are already # created, as toolbar buttons. Otherwise, we need to create them. @@ -692,36 +727,52 @@ class TurtleArtWindow(): if init_only: return - # Hide the previously displayed palette - self._hide_previous_palette() + if show: + # Hide the previously displayed palette + self._hide_previous_palette() + else: + save_selected = self.selected_palette + save_previous = self.previous_palette self.selected_palette = n self.previous_palette = self.selected_palette # Make sure all of the selectors are visible. (We don't need to do # this for 0.86+ toolbars since the selectors are toolbar buttons.) - if self.activity is None or not self.activity.has_toolbarbox: + if show and (self.activity is None or not self.activity.has_toolbarbox): self.selected_selector = self.selectors[n] self.selectors[n].set_shape(self.selector_shapes[n][1]) for i in range(len(palette_blocks)): self.selectors[i].set_layer(TAB_LAYER) - # Show the palette with the current orientation. - if self.palette_sprs[n][self.orientation] is not None: - self.palette_sprs[n][self.orientation].set_layer(CATEGORY_LAYER) + # Show the palette with the current orientation. + if self.palette_sprs[n][self.orientation] is not None: + self.palette_sprs[n][self.orientation].set_layer(CATEGORY_LAYER) # Create 'proto' blocks for each palette entry - self._create_proto_blocks(n, regenerate=regenerate) + self._create_proto_blocks(n) - self._layout_palette(n, regenerate=regenerate) + if show or save_selected == n: + self._layout_palette(n, regenerate=regenerate) + else: + self._layout_palette(n, regenerate=regenerate, show=False) for blk in self.palettes[n]: - blk.spr.set_layer(TAB_LAYER) + if blk.get_visibility(): + blk.spr.set_layer(PROTO_LAYER) + else: + blk.spr.hide() if n == palette_names.index('trash'): for blk in self.trash_stack: for gblk in find_group(blk): if gblk.status != 'collapsed': gblk.spr.set_layer(TAB_LAYER) + if not show: + if not save_selected == n: + self._hide_previous_palette(palette=n) + self.selected_palette = save_selected + self.previous_palette = save_previous + def _create_the_selectors(self): ''' Create the palette selector buttons: only when running old-style Sugar toolbars or from GNOME ''' @@ -798,30 +849,42 @@ class TurtleArtWindow(): self.palette_button[2].type = 'palette' self.palette_button[2].set_layer(TAB_LAYER) - def _create_proto_blocks(self, n, regenerate=False): + def _create_proto_blocks(self, n): ''' Create the protoblocks that will populate a palette. ''' - if regenerate: - for blk in self.palettes[n]: - blk.type = 'trash' - self.palettes[n] = [] - - if self.palettes[n] == []: - for i, name in enumerate(palette_blocks[n]): - self.palettes[n].append(Block(self.block_list, - self.sprite_list, name, 0, 0, 'proto', [], PALETTE_SCALE)) - self.palettes[n][i].spr.set_layer(TAB_LAYER) - self.palettes[n][i].unhighlight() - - # Some proto blocks get a skin. - if name in block_styles['box-style-media']: - self._proto_skin(name + 'small', n, i) - elif name[:8] == 'template': # Deprecated - self._proto_skin(name[8:], n, i) - elif name[:7] == 'picture': # Deprecated - self._proto_skin(name[7:], n, i) - elif name in PYTHON_SKIN: - self._proto_skin('pythonsmall', n, i) + # Reload the palette, but reuse the existing blocks + # If a block doesn't exist, add it + for blk in self.palettes[n]: + blk.spr.hide() + old_blocks = self.palettes[n][:] + self.palettes[n] = [] + for i, name in enumerate(palette_blocks[n]): + found_block = False + for oblk in old_blocks: + if oblk.name == name: + self.palettes[n].append(oblk) + found_block = True + break + if not found_block: + self.palettes[n].append(Block( + self.block_list, self.sprite_list, name, 0, 0, + 'proto', [], PALETTE_SCALE)) + if name in hidden_proto_blocks: + self.palettes[n][i].set_visibility(False) + self.palettes[n][i].spr.set_layer(PROTO_LAYER) + self.palettes[n][i].unhighlight() + + # Some proto blocks get a skin. + if name in block_styles['box-style-media']: + self._proto_skin(name + 'small', n, i) + elif name[:8] == 'template': # Deprecated + self._proto_skin(name[8:], n, i) + elif name[:7] == 'picture': # Deprecated + self._proto_skin(name[7:], n, i) + elif name in PYTHON_SKIN: + self._proto_skin('pythonsmall', n, i) + return + def _hide_toolbar_palette(self): """ Hide the toolbar palettes """ self._hide_previous_palette() @@ -833,24 +896,24 @@ class TurtleArtWindow(): not self.activity.has_toolbarbox: 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): + def _hide_previous_palette(self, palette=None): """ Hide just the previously viewed toolbar palette """ - # Hide previous palette - if self.previous_palette is not None: - for proto in self.palettes[self.previous_palette]: + if palette is None: + palette = self.previous_palette + # Hide previously selected palette + if palette is not None: + for proto in self.palettes[palette]: proto.spr.hide() - self.palette_sprs[self.previous_palette][self.orientation].hide() + self.palette_sprs[palette][self.orientation].hide() if self.activity is None or not self.activity.has_toolbarbox: - self.selectors[self.previous_palette].set_shape( - self.selector_shapes[self.previous_palette][0]) - elif self.previous_palette is not None and \ - self.previous_palette != self.selected_palette: - self.activity.palette_buttons[self.previous_palette].set_icon( - palette_names[self.previous_palette] + 'off') - if self.previous_palette == palette_names.index('trash'): + self.selectors[palette].set_shape( + self.selector_shapes[palette][0]) + elif palette is not None and palette != self.selected_palette \ + and not self.activity.has_toolbarbox: + self.activity.palette_buttons[palette].set_icon( + palette_names[palette] + 'off') + if palette == palette_names.index('trash'): for blk in self.trash_stack: for gblk in find_group(blk): gblk.spr.hide() @@ -859,6 +922,8 @@ class TurtleArtWindow(): """ Position prototypes in a horizontal palette. """ max_w = 0 for blk in blocks: + if not blk.get_visibility(): + continue w, h = self._width_and_height(blk) if y + h > PALETTE_HEIGHT + self.toolbar_offset: x += int(max_w + 3) @@ -872,7 +937,7 @@ class TurtleArtWindow(): g.spr.save_xy = g.spr.get_xy() if self.running_sugar and not self.hw in [XO1]: g.spr.move_relative((self.activity.hadj_value, - self.activity.vadj_value,)) + self.activity.vadj_value)) y += int(h + 3) if w > max_w: max_w = w @@ -883,8 +948,10 @@ class TurtleArtWindow(): row = [] row_w = 0 max_h = 0 - for b in blocks: - w, h = self._width_and_height(b) + for blk in blocks: + if not blk.get_visibility(): + continue + w, h = self._width_and_height(blk) if x + w > PALETTE_WIDTH: # Recenter row. dx = int((PALETTE_WIDTH - row_w) / 2) @@ -898,17 +965,17 @@ class TurtleArtWindow(): x = 4 y += int(max_h + 3) max_h = 0 - row.append(b) + row.append(blk) row_w += (4 + w) - (bx, by) = b.spr.get_xy() + (bx, by) = blk.spr.get_xy() dx = int(x - bx) dy = int(y - by) - for g in find_group(b): + for g in find_group(blk): g.spr.move_relative((dx, dy)) g.spr.save_xy = g.spr.get_xy() if self.running_sugar and not self.hw in [XO1]: g.spr.move_relative((self.activity.hadj_value, - self.activity.vadj_value,)) + self.activity.vadj_value)) x += int(w + 4) if h > max_h: max_h = h @@ -920,7 +987,7 @@ class TurtleArtWindow(): g.spr.save_xy = (g.spr.save_xy[0] + dx, g.spr.save_xy[1]) return x, y, max_h - def _layout_palette(self, n, regenerate=False): + def _layout_palette(self, n, regenerate=False, show=True): """ Layout prototypes in a palette. """ if n is not None: if self.orientation == HORIZONTAL_PALETTE: @@ -932,11 +999,8 @@ class TurtleArtWindow(): w = x + max_w + 25 self._make_palette_spr(n, 0, self.toolbar_offset, w, PALETTE_HEIGHT, regenerate) - self.palette_button[2].move((w - 20, self.toolbar_offset)) - self.palette_button[2].save_xy = self.palette_button[2].get_xy() - if self.running_sugar and not self.hw in [XO1]: - self.palette_button[2].move_relative( - (self.activity.hadj_value, self.activity.vadj_value)) + if show: + self.palette_button[2].move((w - 20, self.toolbar_offset)) else: x, y = 5, self.toolbar_offset + 15 x, y, max_h = self._vertical_layout(x, y, self.palettes[n]) @@ -946,13 +1010,15 @@ class TurtleArtWindow(): h = y + max_h + 25 - self.toolbar_offset self._make_palette_spr(n, 0, self.toolbar_offset, PALETTE_WIDTH, h, regenerate) - self.palette_button[2].move((PALETTE_WIDTH - 20, - self.toolbar_offset)) + if show: + self.palette_button[2].move((PALETTE_WIDTH - 20, + self.toolbar_offset)) + if show: self.palette_button[2].save_xy = self.palette_button[2].get_xy() if self.running_sugar and not self.hw in [XO1]: self.palette_button[2].move_relative( (self.activity.hadj_value, self.activity.vadj_value)) - self.palette_sprs[n][self.orientation].set_layer(CATEGORY_LAYER) + self.palette_sprs[n][self.orientation].set_layer(CATEGORY_LAYER) def _make_palette_spr(self, n, x, y, w, h, regenerate=False): ''' Make the background for the palette. ''' @@ -987,8 +1053,25 @@ class TurtleArtWindow(): def button_press(self, mask, x, y): self.block_operation = 'click' + # Find out what was clicked + spr = self.sprite_list.find_sprite((x, y)) + # Unselect things that may have been selected earlier if self.selected_blk is not None: + if self.selected_blk.name == 'number' and spr in self.triangle_sprs: + # increment or decrement a number block + nf = float(self.selected_blk.spr.labels[0].replace(CURSOR, '')) + ni = int(nf) + if ni == nf: + n = ni + else: + n = nf + if spr == self.triangle_sprs[0]: + n += 1 + else: + n -= 1 + self.selected_blk.spr.set_label(str(n) + CURSOR) + return True self._unselect_block() self.selected_turtle = None @@ -996,8 +1079,6 @@ class TurtleArtWindow(): 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: @@ -1035,22 +1116,25 @@ class TurtleArtWindow(): # You cannot mix and match sensor blocks elif blk.name in ['sound', 'volume', 'pitch']: if len(self.block_list.get_similar_blocks( - 'block', ['resistance', 'voltage'])) > 0: + 'block', ['resistance', 'voltage', + 'resistance2', 'voltage2'])) > 0: self.showlabel('incompatible') return True - elif blk.name in ['resistance', 'voltage']: + elif blk.name in ['resistance', 'voltage', + 'resistance2', 'voltage2']: if len(self.block_list.get_similar_blocks( 'block', ['sound', 'volume', 'pitch'])) > 0: self.showlabel('incompatible') return True - if blk.name == 'resistance': + if blk.name in ['resistance', 'resistance2']: if len(self.block_list.get_similar_blocks( - 'block', 'voltage')) > 0: + 'block', ['voltage', 'voltage2'])) > 0: self.showlabel('incompatible') return True - elif blk.name == 'voltage': + elif blk.name in ['voltage', 'voltage2']: if len(self.block_list.get_similar_blocks( - 'block', 'resistance')) > 0: + 'block', ['resistance', + 'resistance2'])) > 0: self.showlabel('incompatible') return True blk.highlight() @@ -1224,12 +1308,7 @@ class TurtleArtWindow(): self.trash_stack = [] def _in_the_trash(self, x, y): - """ Is x, y over the trash can? """ - """ - if self.selected_palette == palette_names.index('trash') and \ - self.palette_sprs[palette_names.index('trash')][self.orientation].hit((x, y)): - return True - """ + """ Is x, y over a palette? """ if self.selected_palette is not None and \ self.palette_sprs[self.selected_palette][self.orientation].hit( (x, y)): @@ -1257,6 +1336,8 @@ class TurtleArtWindow(): # After unselecting a 'number' block, we need to check its value if self.selected_blk.name == 'number': self._number_check() + for spr in self.triangle_sprs: + spr.hide() elif self.selected_blk.name == 'string': self._string_check() self.selected_blk.unhighlight() @@ -1332,11 +1413,14 @@ class TurtleArtWindow(): newblk.connections[i + 1] = argblk self.drag_group = find_group(newblk) self.block_operation = 'new' + debug_output(newblk.name, True) if len(newblk.spr.labels) > 0 and newblk.spr.labels[0] is not None \ and newblk.name not in ['', 'number', 'string']: - if newblk.spr.labels[0] not in self.used_block_list: - if len(self.used_block_list) > 0: - self.used_block_list.append(', ') + if len(self.used_block_list) > 0: + self.used_block_list.append(', ') + if newblk.name in special_names: + self.used_block_list.append(special_names[newblk.name]) + elif newblk.spr.labels[0] not in self.used_block_list: self.used_block_list.append(newblk.spr.labels[0]) def _new_macro(self, name, x, y): @@ -1545,7 +1629,7 @@ class TurtleArtWindow(): dx = x - dragx - sx dy = y - dragy - sy - # Take no action if there was a move of 0,0. + # Take no action if there was a move of 0, 0. if dx == 0 and dy == 0: return @@ -1661,6 +1745,7 @@ class TurtleArtWindow(): def _buttonrelease_cb(self, win, event): """ Button release """ x, y = xy(event) + self.mouse_flag = 0 self.mouse_x = x self.mouse_y = y self.button_release(x, y) @@ -1701,7 +1786,7 @@ class TurtleArtWindow(): return blk = self.drag_group[0] - # Remove blocks by dragging them onto the trash palette. + # Remove blocks by dragging them onto any palette. if self.block_operation == 'move' and self._in_the_trash(x, y): self._put_in_trash(blk, x, y) self.drag_group = None @@ -1726,7 +1811,11 @@ class TurtleArtWindow(): self.drag_group = None # Find the block we clicked on and process it. - if self.block_operation == 'click': + # Consider a very small move a click (for touch interfaces) + if self.block_operation == 'click' or \ + (self.hw in [XO175, XO30] and self.block_operation == 'move' and ( + abs(self.dx) < MOTION_THRESHOLD and \ + abs(self.dy < MOTION_THRESHOLD))): self._click_block(x, y) def remote_turtle(self, name): @@ -1776,6 +1865,14 @@ class TurtleArtWindow(): if blk.name == 'number' or blk.name == 'string': self.saved_string = blk.spr.labels[0] blk.spr.labels[0] += CURSOR + if blk.name == 'number': + bx, by = blk.spr.get_xy() + bw, bh = blk.spr.get_dimensions() + tw, th = self.triangle_sprs[0].get_dimensions() + for spr in self.triangle_sprs: + spr.set_layer(TOP_LAYER) + self.triangle_sprs[0].move((int(bx + (bw - tw) / 2), by - th)) + self.triangle_sprs[1].move((int(bx + (bw - tw) / 2), by + bh)) elif blk.name in block_styles['box-style-media'] and \ blk.name not in NO_IMPORT: @@ -1828,7 +1925,7 @@ class TurtleArtWindow(): return if blk.name in block_styles['boolean-style']: - self._expand_boolean(blk, blk.connections[1], dy) + self._expand_boolean(blk, blk.connections[2], dy) else: self._expand_expandable(blk, blk.connections[1], dy) @@ -1926,7 +2023,8 @@ class TurtleArtWindow(): for gblk in find_group(blk): if gblk not in group: gblk.spr.move_relative((0, dy * blk.scale)) - if blk.name in block_styles['compare-style']: + if blk.name in block_styles['compare-style'] or \ + blk.name in block_styles['compare-porch-style']: for gblk in find_group(blk): gblk.spr.move_relative((0, -dy * blk.scale)) @@ -1963,7 +2061,8 @@ class TurtleArtWindow(): for gblk in find_group(blk): if gblk not in group: gblk.spr.move_relative((0, dy * blk.scale)) - if blk.name in block_styles['compare-style']: + if blk.name in block_styles['compare-style'] or \ + blk.name in block_styles['compare-porch-style']: for gblk in find_group(blk): gblk.spr.move_relative((0, -dy * blk.scale)) else: @@ -2014,7 +2113,7 @@ class TurtleArtWindow(): """ selected_block = self.drag_group[0] best_destination = None - d = 200 + d = SNAP_THRESHOLD for selected_block_dockn in range(len(selected_block.docks)): for destination_block in self.just_blocks(): # Don't link to a block that is hidden @@ -2034,7 +2133,8 @@ class TurtleArtWindow(): best_destination = destination_block best_destination_dockn = destination_dockn best_selected_block_dockn = selected_block_dockn - if d < 200: + if d < SNAP_THRESHOLD: + # Some combinations of blocks are not valid if not arithmetic_check(selected_block, best_destination, best_selected_block_dockn, best_destination_dockn): @@ -2043,6 +2143,8 @@ class TurtleArtWindow(): best_selected_block_dockn, best_destination_dockn): return + + # Move the selected blocks into the docked position for blk in self.drag_group: (sx, sy) = blk.spr.get_xy() blk.spr.move((sx + best_xy[0], sy + best_xy[1])) @@ -2053,17 +2155,27 @@ class TurtleArtWindow(): blk_in_dock.connections[0] = None self._put_in_trash(blk_in_dock) + # Note the connection in destination dock best_destination.connections[best_destination_dockn] = \ selected_block + + # And in the selected block dock if selected_block.connections is not None: if best_selected_block_dockn < len(selected_block.connections): selected_block.connections[best_selected_block_dockn] = \ best_destination + # Some destination blocks expand to accomodate large blocks if best_destination.name in block_styles['boolean-style']: if best_destination_dockn == 2 and \ - selected_block.name in block_styles['compare-style']: + (selected_block.name in block_styles['boolean-style'] or \ + selected_block.name in block_styles['compare-style'] or \ + selected_block.name in block_styles['compare-porch-style']): dy = selected_block.ey - best_destination.ey + if selected_block.name in block_styles['boolean-style']: + # Even without expanding, boolean blocks are + # too large to fit in the lower dock position + dy += 45 best_destination.expand_in_y(dy) self._expand_boolean(best_destination, selected_block, dy) elif best_destination.name in expandable_blocks and \ @@ -2083,8 +2195,8 @@ class TurtleArtWindow(): if best_destination.ey > 0: dy = best_destination.reset_y() if dy != 0: - self._expand_expandable(best_destination, selected_block, - dy) + self._expand_expandable( + best_destination, selected_block, dy) self._cascade_expandable(best_destination) grow_stack_arm(find_sandwich_top(best_destination)) @@ -2210,7 +2322,7 @@ class TurtleArtWindow(): if keyname == "p": self.hideshow_button() elif keyname == 'q': - self._quit_plugins() + self.quit_plugins() if self.gst_available: stop_media(self.lc) exit() @@ -2481,8 +2593,14 @@ class TurtleArtWindow(): else: n = 0 self.selected_blk.spr.set_label(str(n)) - self.selected_blk.values[0] = float(str(n).replace(self.decimal_point, - '.')) + debug_output(str(n), True) + try: + self.selected_blk.values[0] = \ + float(str(n).replace(self.decimal_point, '.')) + except ValueError: + self.selected_blk.values[0] = float(str(n)) + except IndexError: + self.selected_blk.values[0] = float(str(n)) def _string_check(self): s = self.selected_blk.spr.labels[0].replace(CURSOR, '') @@ -2818,9 +2936,11 @@ class TurtleArtWindow(): blk.connections = 'check' if self.running_sugar and len(blk.spr.labels) > 0 and \ blk.name not in ['', ' ', 'number', 'string']: - if blk.spr.labels[0] not in self.used_block_list: - if len(self.used_block_list) > 0: - self.used_block_list.append(', ') + if len(self.used_block_list) > 0: + self.used_block_list.append(', ') + if blk.name in special_names: + self.used_block_list.append(special_names[blk.name]) + elif blk.spr.labels[0] not in self.used_block_list: self.used_block_list.append(blk.spr.labels[0]) return blk @@ -2937,8 +3057,7 @@ class TurtleArtWindow(): shp = shp[1:] label = '' self.status_spr.set_shape(self.status_shapes[shp]) - self.status_spr.set_label_attributes(6.0 * self.block_scale, - rescale=False) + self.status_spr.set_label_attributes(12.0, rescale=False) self.status_spr.set_label(str(label)) self.status_spr.set_layer(STATUS_LAYER) if shp == 'info': @@ -3005,9 +3124,6 @@ class TurtleArtWindow(): 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: from sugar.datastore import datastore from sugar import profile @@ -3026,6 +3142,10 @@ class TurtleArtWindow(): dsobject.set_file_path(file_path) datastore.write(dsobject) dsobject.destroy() + self.saved_pictures.append((dsobject.object_id, svg)) + os.remove(file_path) + else: + self.saved_pictures.append((file_path, svg)) def just_blocks(self): """ Filter out 'proto', 'trash', and 'deleted' blocks """ |