From 36bc6d737db3f6fcf7366f29010d566eb9664b48 Mon Sep 17 00:00:00 2001 From: Walter Bender Date: Mon, 09 Jul 2012 22:23:09 +0000 Subject: resynch with TA 149 --- diff --git a/TurtleArt/sprites.py b/TurtleArt/sprites.py index c400d88..ce456eb 100644 --- a/TurtleArt/sprites.py +++ b/TurtleArt/sprites.py @@ -171,6 +171,8 @@ class Sprite: self._rescale = [True] self._horiz_align = ["center"] self._vert_align = ["middle"] + self._x_pos = [None] + self._y_pos = [None] self._fd = None self._bold = False self._italic = False @@ -289,6 +291,8 @@ class Sprite: self._rescale.append(self._rescale[0]) self._horiz_align.append(self._horiz_align[0]) self._vert_align.append(self._vert_align[0]) + self._x_pos.append(self._x_pos[0]) + self._y_pos.append(self._y_pos[0]) def set_font(self, font): ''' Set the font for a label ''' @@ -310,13 +314,15 @@ class Sprite: return def set_label_attributes(self, scale, rescale=True, horiz_align="center", - vert_align="middle", i=0): + vert_align="middle", x_pos=None, y_pos=None, 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 + self._x_pos[i] = x_pos + self._y_pos[i] = y_pos def hide(self): ''' Hide a sprite ''' @@ -399,14 +405,18 @@ class Sprite: pl.set_font_description(self._fd) w = pl.get_size()[0] / pango.SCALE j -= 1 - if self._horiz_align[i] == "center": + if self._x_pos[i] is not None: + x = int(self.rect.x + self._x_pos[i]) + elif 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": + if self._y_pos[i] is not None: + y = int(self.rect.y + self._y_pos[i]) + elif 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]) diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py index e26950d..6bff0dc 100644 --- a/TurtleArt/tabasics.py +++ b/TurtleArt/tabasics.py @@ -69,6 +69,7 @@ from talogo import primitive_dictionary, logoerror from tautils import convert, chr_to_ord, round_int, strtype from taconstants import BLACK, WHITE, CONSTANTS, XO30 + def _num_type(x): """ Is x a number type? """ if type(x) == int: @@ -137,7 +138,7 @@ class Palettes(): lambda self, x: primitive_dictionary['move']( self.tw.canvas.forward, -x)) - primitive_dictionary['clean'] = self._prim_clean + primitive_dictionary['clean'] = self.tw.lc.prim_clear palette.add_block('clean', style='basic-style-extended-vertical', label=_('clean'), @@ -292,7 +293,6 @@ setxy :x :y\rpendown\rend\r') lambda self: self.tw.canvas.setpen(True)) palette.add_block('setpensize', - hidden=True, style='basic-style-1arg', label=_('set pen size'), prim_name='setpensize', @@ -307,7 +307,6 @@ turtle')) round :a\rend\r') palette.add_block('fillscreen', - hidden=True, style='basic-style-2arg', label=[_('fill screen'), _('color'), _('shade')], prim_name='fillscreen', @@ -321,7 +320,6 @@ shade)')) :shade\rtasetshade :shade\rsetbackground :color\rend\r') palette.add_block('pensize', - hidden=True, style='box-style', label=_('pen size'), help_string=_('holds current pen size (can be used \ @@ -333,36 +331,6 @@ in place of a number block)'), define_logo_function('tapensize', 'to tapensize\routput first round \ pensize\rend\r') - palette.add_block('startfill', - style='basic-style-extended-vertical', - label=_('start fill'), - prim_name='startfill', - help_string=_('starts filled polygon (used with end \ -fill block)')) - self.tw.lc.def_prim('startfill', 0, - lambda self: self.tw.canvas.start_fill()) - - palette.add_block('stopfill', - style='basic-style-extended-vertical', - label=_('end fill'), - prim_name='stopfill', - help_string=_('completes filled polygon (used with \ -start fill block)')) - self.tw.lc.def_prim('stopfill', 0, - lambda self: self.tw.canvas.stop_fill()) - - def _color_palette(self): - """ The basic Turtle Art color palette """ - - 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', label=_('set color'), @@ -376,7 +344,6 @@ turtle')) 'color', self.tw.canvas.setcolor, x)) palette.add_block('setshade', - hidden=True, style='basic-style-1arg', label=_('set shade'), prim_name='setshade', @@ -389,7 +356,6 @@ turtle')) 'shade', self.tw.canvas.setshade, x)) palette.add_block('setgray', - hidden=True, style='basic-style-1arg', label=_('set gray'), prim_name='setgray', @@ -411,7 +377,6 @@ in place of a number block)'), self.tw.lc.def_prim('color', 0, lambda self: self.tw.canvas.color) palette.add_block('shade', - hidden=True, style='box-style', label=_('shade'), help_string=_('holds current pen shade'), @@ -421,15 +386,44 @@ in place of a number block)'), self.tw.lc.def_prim('shade', 0, lambda self: self.tw.canvas.shade) palette.add_block('gray', - hidden=True, style='box-style', label=_('gray'), - help_string=_('holds current gray level (can be used \ -in place of a number block)'), + help_string=_('holds current gray level (can be \ +used in place of a number block)'), value_block=True, prim_name='gray') self.tw.lc.def_prim('gray', 0, lambda self: self.tw.canvas.gray) + palette.add_block('startfill', + style='basic-style-extended-vertical', + label=_('start fill'), + prim_name='startfill', + help_string=_('starts filled polygon (used with end \ +fill block)')) + self.tw.lc.def_prim('startfill', 0, + lambda self: self.tw.canvas.start_fill()) + + palette.add_block('stopfill', + style='basic-style-extended-vertical', + label=_('end fill'), + prim_name='stopfill', + help_string=_('completes filled polygon (used with \ +start fill block)')) + self.tw.lc.def_prim('stopfill', 0, + lambda self: self.tw.canvas.stop_fill()) + + def _color_palette(self): + """ The basic Turtle Art color palette """ + + 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')) + self._make_constant(palette, 'red', _('red'), CONSTANTS['red']) self._make_constant(palette, 'orange', _('orange'), CONSTANTS['orange']) @@ -569,8 +563,8 @@ tasetshade :shade \r') top numeric input')) self.tw.lc.def_prim( 'minus', 2, lambda self, x, y: primitive_dictionary['minus'](x, y)) - define_logo_function('taminus', 'to taminus :y :x\routput sum :x minus \ -:y\rend\r') + define_logo_function('taminus', 'to taminus :y :x\routput sum :x \ +minus :y\rend\r') primitive_dictionary['product'] = self._prim_product palette.add_block('product2', @@ -591,8 +585,8 @@ top numeric input')) special_name=_('divide'), prim_name='division', logo_command='quotient', - help_string=_('divides top numeric input (numerator) \ -by bottom numeric input (denominator)')) + help_string=_('divides top numeric input \ +(numerator) by bottom numeric input (denominator)')) self.tw.lc.def_prim( 'division', 2, lambda self, x, y: primitive_dictionary['division'](x, y)) @@ -621,7 +615,6 @@ blocks')) primitive_dictionary['sqrt'] = self._prim_sqrt palette.add_block('sqrt', - hidden=True, style='number-style-1arg', label=_('√'), special_name=_('square root'), @@ -639,8 +632,8 @@ blocks')) default=[0, 100], prim_name='random', logo_command='tarandom', - help_string=_('returns random number between minimum \ -(top) and maximum (bottom) values')) + help_string=_('returns random number between \ +minimum (top) and maximum (bottom) values')) self.tw.lc.def_prim( 'random', 2, lambda self, x, y: primitive_dictionary['random']( x, y)) @@ -660,8 +653,8 @@ operators')) hidden=True, style='compare-porch-style', label='>', - special_name=_('greater than'), string_or_number=True, + special_name=_('greater than'), prim_name='greater?', logo_command='greater?', help_string=_('logical greater-than operator')) @@ -669,9 +662,6 @@ operators')) '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', hidden=True, @@ -685,21 +675,18 @@ 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, style='compare-style', label='=', - string_or_number=True, special_name=_('equal'), + string_or_number=True, prim_name='equal?', logo_command='equal?', help_string=_('logical equal-to operator')) - self.tw.lc.def_prim( - 'equal?', 2, lambda self, x, y: primitive_dictionary['equal'](x, y)) + self.tw.lc.def_prim('equal?', 2, + lambda self, x, y: primitive_dictionary['equal'](x, y)) palette.add_block('not', hidden=True, @@ -743,7 +730,6 @@ operators')) primitive_dictionary['wait'] = self._prim_wait palette.add_block('wait', - hidden=True, style='basic-style-1arg', label=_('wait'), prim_name='wait', @@ -756,20 +742,21 @@ number of seconds')) primitive_dictionary['forever'] = self._prim_forever palette.add_block('forever', hidden=True, - style='flow-style', + style='clamp-style', label=_('forever'), prim_name='forever', - default=[None, 'vspace'], + default=[None, None], logo_command='forever', help_string=_('loops forever')) - self.tw.lc.def_prim('forever', 1, primitive_dictionary['forever'], True) + self.tw.lc.def_prim('forever', 1, primitive_dictionary['forever'], + True) primitive_dictionary['repeat'] = self._prim_repeat palette.add_block('repeat', - style='flow-style-1arg', - label=[' ', _('repeat')], + style='clamp-style-1arg', + label=_('repeat'), prim_name='repeat', - default=[4, None, 'vspace'], + default=[4, None, None], logo_command='repeat', special_name=_('repeat'), help_string=_('loops specified number of times')) @@ -778,10 +765,10 @@ number of seconds')) primitive_dictionary['if'] = self._prim_if palette.add_block('if', hidden=True, - style='flow-style-boolean', - label=[_('if'), ' ', _('then')], + style='clamp-style-boolean', + label=[_('if'), _('then'), ''], prim_name='if', - default=[None, None, 'vspace'], + default=[None, None, None], special_name=_('if then'), logo_command='if', help_string=_('if-then operator that uses boolean \ @@ -790,20 +777,30 @@ operators from Numbers palette')) primitive_dictionary['ifelse'] = self._prim_ifelse palette.add_block('ifelse', - hidden=True, - style='flow-style-else', - label=[_('if'), ' ', _('then else')], + hidden=True, # Too big to fit palette + style='clamp-style-else', + label=[_('if'), _('then'), _('else')], prim_name='ifelse', - default=[None, 'vspace', None, 'vspace'], + default=[None, None, None, None], logo_command='ifelse', special_name=_('if then else'), help_string=_('if-then-else operator that uses \ boolean operators from Numbers palette')) self.tw.lc.def_prim('ifelse', 3, primitive_dictionary['ifelse'], True) + # macro + palette.add_block('ifthenelse', + hidden=True, + style='basic-style-extended-vertical', + label=_('if then else'), + help_string=_('if-then-else operator that uses \ +boolean operators from Numbers palette')) + + # Deprecated palette.add_block('hspace', style='flow-style-tail', - label=' ', + hidden=True, + label='', prim_name='nop', special_name=_('horizontal space'), help_string=_('jogs stack right')) @@ -811,7 +808,7 @@ boolean operators from Numbers palette')) palette.add_block('vspace', style='basic-style-extended-vertical', - label=' ', + label='', prim_name='nop', special_name=_('vertical space'), help_string=_('jogs stack down')) @@ -850,9 +847,9 @@ buttons')) palette.add_block('storeinbox1', style='basic-style-1arg', label=_('store in box 1'), - string_or_number=True, prim_name='storeinbox1', default=100, + string_or_number=True, logo_command='make "box1', help_string=_('stores numeric value in Variable 1')) self.tw.lc.def_prim('storeinbox1', 1, @@ -862,9 +859,9 @@ buttons')) palette.add_block('storeinbox2', style='basic-style-1arg', label=_('store in box 2'), - string_or_number=True, prim_name='storeinbox2', default=100, + string_or_number=True, logo_command='make "box2', help_string=_('stores numeric value in Variable 2')) self.tw.lc.def_prim('storeinbox2', 1, @@ -872,7 +869,6 @@ buttons')) 'box2', None, x)) palette.add_block('string', - hidden=True, style='box-style', label=_('text'), default=_('text'), @@ -899,11 +895,10 @@ buttons')) primitive_dictionary['box'] = self._prim_box palette.add_block('box', - hidden=True, style='number-style-1strarg', label=_('box'), - prim_name='box', string_or_number=True, + prim_name='box', default=_('my box'), logo_command='box', help_string=_('named variable (numeric value)')) @@ -911,7 +906,6 @@ buttons')) lambda self, x: primitive_dictionary['box'](x)) palette.add_block('storein', - hidden=True, style='basic-style-2arg', label=[_('store in'), _('box'), _('value')], string_or_number=True, @@ -925,7 +919,6 @@ variable')) 'box3', x, y)) palette.add_block('hat', - hidden=True, style='basic-style-head-1arg', label=_('action'), prim_name='nop3', @@ -953,7 +946,6 @@ variable')) primitive_dictionary['stack'] = self._prim_stack palette.add_block('stack', - hidden=True, style='basic-style-1arg', label=_('action'), string_or_number=True, @@ -1005,12 +997,6 @@ variable')) # Block primitives - def _prim_clean(self): - ''' Clean block ''' - self.tw.lc.prim_clear - self.tw.display_coordinates() - self.tw.parent.restore_challenge() - def _prim_and(self, x, y): """ Logical and """ return x & y @@ -1215,6 +1201,8 @@ variable')) def _prim_less(self, x, y): """ Compare numbers and strings """ + if type(x) == list or type(y) == list: + raise logoerror("#syntaxerror") try: return float(x) < float(y) except ValueError: diff --git a/TurtleArt/tablock.py b/TurtleArt/tablock.py index f835994..7e48d43 100644 --- a/TurtleArt/tablock.py +++ b/TurtleArt/tablock.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -#Copyright (c) 2010,11 Walter Bender +#Copyright (c) 2010-2012 Walter Bender #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,8 @@ import gtk from gettext import gettext as _ from taconstants import EXPANDABLE, EXPANDABLE_ARGS, OLD_NAMES, CONSTANTS, \ - STANDARD_STROKE_WIDTH, BLOCK_SCALE, BOX_COLORS, GRADIENT_COLOR + STANDARD_STROKE_WIDTH, BLOCK_SCALE, BOX_COLORS, GRADIENT_COLOR, \ + EXPANDABLE_FLOW from tapalette import palette_blocks, block_colors, expandable_blocks, \ content_blocks, block_names, block_primitives, block_styles, \ special_block_colors @@ -139,6 +140,7 @@ class Block: self.dx = 0 self.ex = 0 self.ey = 0 + self.ey2 = 0 self._ei = 0 self._font_size = [6.0, 4.5] self._image = None @@ -156,7 +158,6 @@ class Block: 'basic-style-2arg': self._make_basic_style_2arg, 'basic-style-3arg': self._make_basic_style_3arg, 'basic-style-var-arg': self._make_basic_style_var_arg, - 'invisible': self._make_invisible_style, 'bullet-style': self._make_bullet_style, 'box-style': self._make_box_style, 'box-style-media': self._make_media_style, @@ -170,19 +171,13 @@ class Block: 'compare-porch-style': self._make_compare_porch_style, 'boolean-style': self._make_boolean_style, 'not-style': self._make_not_style, - 'flow-style': self._make_flow_style, + 'clamp-style': self._make_clamp_style, + 'clamp-style-collapsible': self._make_clamp_style_collapsible, + 'clamp-style-collapsed': self._make_clamp_style_collapsed, + 'clamp-style-1arg': self._make_clamp_style_1arg, + 'clamp-style-boolean': self._make_clamp_style_boolean, + 'clamp-style-else': self._make_clamp_style_else, '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-else': self._make_flow_style_else, - 'collapsible-top': [self._make_collapsible_style_top, True, True], - 'collapsible-top-no-arm': [self._make_collapsible_style_top, - False, True], - 'collapsible-top-no-label': [self._make_collapsible_style_top, - True, False], - 'collapsible-top-no-arm-no-label': [ - self._make_collapsible_style_top, False, False], - 'collapsible-bottom': self._make_collapsible_style_bottom, 'portfolio-style-2x2': self._make_portfolio_style_2x2, 'portfolio-style-1x1': self._make_portfolio_style_1x1, 'portfolio-style-2x1': self._make_portfolio_style_2x1, @@ -232,6 +227,8 @@ class Block: return True if self.name in EXPANDABLE_ARGS: return True + if self.name in EXPANDABLE_FLOW: + return True return False def cloneable(self): @@ -246,12 +243,12 @@ class Block: def highlight(self): """ We may want to highlight a block... """ - if self.spr is not None: + if self.spr is not None and self.status is not 'collapsed': self.spr.set_shape(self.shapes[1]) def unhighlight(self): """ Or unhighlight it. """ - if self.spr is not None: + if self.spr is not None and self.status is not 'collapsed': self.spr.set_shape(self.shapes[0]) def resize(self): @@ -290,7 +287,6 @@ class Block: self.scale = scale for i in range(len(self._font_size)): self._font_size[i] *= self.scale - self._set_label_attributes() self.svg.set_scale(self.scale) self.refresh() self.spr.inval() @@ -305,6 +301,7 @@ class Block: return self._make_block(self.svg) self._set_margins() + self._set_label_attributes() self.spr.set_shape(self.shapes[0]) def add_arg(self, keep_expanding=True): @@ -336,6 +333,22 @@ class Block: self.svg.set_show(False) self.refresh() + def expand_in_y2(self, dy): + """ We may want to grow a block vertically. """ + if self.spr is None: + return + self.ey2 += dy + if self.type == 'block': + if self.ey2 > 0: + self.svg.set_hide(True) + else: + self.svg.set_hide(False) + self.svg.set_show(True) + else: + self.svg.set_hide(False) + self.svg.set_show(False) + self.refresh() + def expand_in_x(self, dx): """ We may want to grow a block horizontally. """ if self.spr is None: @@ -349,6 +362,42 @@ class Block: self.svg.set_show(False) self.refresh() + def contract_in_y(self, dy): + """ We may want to shrink a block veritcally. """ + if self.spr is None: + return + self.ey -= dy + if self.ey < 0: + self.ey = 0 + if self.type == 'block': + if self.ey > 0: + self.svg.set_hide(True) + else: + self.svg.set_hide(False) + self.svg.set_show(True) + else: + self.svg.set_hide(False) + self.svg.set_show(False) + self.refresh() + + def contract_in_x(self, dx): + """ We may want to shrink a block horizontally. """ + if self.spr is None: + return + self.ex -= dx + if self.ex < 0: + self.ex = 0 + if self.type == 'block': + if self.ex > 0: + self.svg.set_hide(True) + else: + self.svg.set_hide(False) + self.svg.set_show(True) + else: + self.svg.set_hide(False) + self.svg.set_show(False) + self.refresh() + def reset_x(self): if self.spr is None: return 0 @@ -375,10 +424,23 @@ class Block: self.refresh() return dy + def reset_y2(self): + if self.spr is None: + return 0 + dy = -self.ey2 + self.ey2 = 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 self.spr is None: return(0, 0) - return (self.ex, self.ey) + return (self.ex, self.ey, self.ey2) def _new_block_from_factory(self, sprite_list, x, y, copy_block=None): self.svg = SVG() @@ -459,28 +521,51 @@ class Block: for i in range(n): if self.name in block_styles['compare-porch-style']: self.spr.set_label_attributes(int(self._font_size[0] + 0.5), - True, 'center', 'bottom', i) + True, 'center', 'bottom', i=i) elif self.name in block_styles['number-style-porch']: self.spr.set_label_attributes(int(self._font_size[0] + 0.5), - True, 'right', 'bottom', i) - - elif 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) + True, 'right', 'bottom', i=i) + elif self.name in EXPANDABLE_FLOW: + self._calc_moving_labels(i) elif i == 1: # top self.spr.set_label_attributes(int(self._font_size[1] + 0.5), - True, 'right', 'top', i) + True, 'right', 'top', i=i) elif i == 2: # bottom self.spr.set_label_attributes(int(self._font_size[1] + 0.5), - True, 'right', 'bottom', i) + True, 'right', 'bottom', i=i) else: self.spr.set_label_attributes(int(self._font_size[0] + 0.5), - True, 'center', 'middle', i) + True, 'center', 'middle', i=i) + + def _calc_moving_labels(self, i): + ''' Some labels move as blocks change shape/size ''' + if self.name in block_styles['clamp-style'] or \ + self.name in block_styles['clamp-style-collapsible']: + y = int((self.docks[0][3] + self.docks[1][3]) / 3) + self.spr.set_label_attributes(int(self._font_size[0] + 0.5), + True, 'right', y_pos=y, i=0) + elif self.name in block_styles['clamp-style-1arg']: + y = self.docks[1][3] - int(int(self._font_size[0] * 1.3)) + self.spr.set_label_attributes(int(self._font_size[0] + 0.5), + True, 'right', y_pos=y, i=0) + elif self.name in block_styles['clamp-style-boolean']: + y = self.docks[1][3] - int(int(self._font_size[0] * 1.3)) + self.spr.set_label_attributes(int(self._font_size[0] + 0.5), + True, 'right', y_pos=y, i=0) + y = self.docks[2][3] - int(int(self._font_size[0] * 1.3)) + self.spr.set_label_attributes(int(self._font_size[1] + 0.5), + True, 'right', y_pos=y, i=1) + elif self.name in block_styles['clamp-style-else']: + y = self.docks[1][3] - int(int(self._font_size[0] * 1.3)) + self.spr.set_label_attributes(int(self._font_size[0] + 0.5), + True, 'right', y_pos=y, i=0) + y = self.docks[2][3] - int(int(self._font_size[0] * 1.3)) + self.spr.set_label_attributes(int(self._font_size[1] + 0.5), + True, 'right', y_pos=y, i=1) + y = self.docks[3][3] - int(int(self._font_size[0] * 1.3)) + self.spr.set_label_attributes(int(self._font_size[1] + 0.5), + True, 'right', y_pos=y, i=2) + def _set_labels(self, i, label): if self.spr is None: @@ -502,8 +587,8 @@ class Block: else: self.block_methods[k](svg) return - error_output('block type not found %s' % (self.name)) - self.block_methods['basic-style'](svg) + error_output('ERROR: block type not found %s' % (self.name)) + self.block_methods['blank-style'](svg) def _set_colors(self, svg): if self._custom_colors: @@ -778,107 +863,109 @@ class Block: ['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) + def _make_clamp_style(self, svg, extend_x=0, extend_y=4): + self.svg.expand(self.dx + self.ex + extend_x, self.ey + extend_y) self.svg.set_slot(True) self.svg.set_tab(True) - self._make_block_graphics(svg, self.svg.basic_flow) + self.svg.second_clamp(False) + self._make_block_graphics(svg, self.svg.clamp) 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], ']']] + # Skip bottom of clamp + ['flow', False, self.svg.docks[3][0], + self.svg.docks[3][1], ']']] - def _make_flow_style_tail(self, svg): - self.svg.expand(10 + self.dx + self.ex, self.ey) + def _make_clamp_style_collapsible(self, svg, extend_x=0, extend_y=4): + self.svg.expand(self.dx + self.ex + extend_x, self.ey + extend_y) self.svg.set_slot(True) - self.svg.set_tab(False) - self._make_block_graphics(svg, self.svg.basic_flow) + self.svg.set_tab(True) + self.svg.set_collapsible(True) + self.svg.second_clamp(False) + self._make_block_graphics(svg, self.svg.clamp) 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]]] + self.svg.docks[1][1], '['], + # Skip bottom of clamp + ['flow', False, self.svg.docks[3][0], + self.svg.docks[3][1], ']']] - def _make_flow_style_1arg(self, svg): - self.svg.expand(self.dx + self.ex, self.ey) + def _make_clamp_style_collapsed(self, svg, extend_x=0, extend_y=4): + self.svg.expand(self.dx + self.ex + extend_x, self.ey + extend_y) + self.svg.set_show(True) + self._make_block_graphics(svg, self.svg.basic_block) + self.docks = [['flow', True, self.svg.docks[0][0], + self.svg.docks[0][1]], + ['unavailable', True, 0, self.svg.docks[0][1] + 10, '['], + ['flow', False, self.svg.docks[1][0], + self.svg.docks[1][1], ']']] + + def _make_clamp_style_1arg(self, svg, extend_x=0, extend_y=4): + self.svg.expand(self.dx + self.ex + extend_x, self.ey + extend_y) self.svg.set_slot(True) self.svg.set_tab(True) self.svg.set_innie([True]) - self._make_block_graphics(svg, self.svg.basic_flow) + self.svg.second_clamp(False) + self._make_block_graphics(svg, self.svg.clamp) 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], ']']] + # Skip bottom of clamp + ['flow', False, self.svg.docks[4][0], + self.svg.docks[4][1], ']']] - def _make_flow_style_boolean(self, svg): - self.svg.expand(self.dx + self.ex, self.ey) + def _make_clamp_style_boolean(self, svg, extend_x=0, extend_y=4): + self.svg.expand(self.dx + self.ex + extend_x, self.ey + extend_y) 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.svg.second_clamp(False) + self._make_block_graphics(svg, self.svg.clamp) 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]], + 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], ']']] + # Skip bottom of clamp + ['flow', False, self.svg.docks[4][0], + self.svg.docks[4][1], ']']] - def _make_flow_style_else(self, svg): - self.svg.expand(self.dx + self.ex, self.ey) + def _make_clamp_style_else(self, svg, extend_x=0, extend_y=4): + self.svg.expand(self.dx + self.ex + extend_x, self.ey + extend_y, + self.dx + self.ex + extend_x, self.ey2 + extend_y) self.svg.set_slot(True) self.svg.set_tab(True) - self.svg.set_else(True) self.svg.set_boolean(True) - self._make_block_graphics(svg, self.svg.basic_flow) + self.svg.second_clamp(True) + self._make_block_graphics(svg, self.svg.clamp) 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], ']['], + self.svg.docks[2][1], '['], + # Skip bottom of clamp ['flow', False, self.svg.docks[4][0], - self.svg.docks[4][1], ']']] + self.svg.docks[4][1], ']['], + # Skip bottom of clamp + ['flow', False, self.svg.docks[6][0], + self.svg.docks[6][1], ']']] - def _make_collapsible_style_top(self, svg, arm=True, label=True): - self.svg.expand(self.dx + self.ex, self.ey) - self.svg.set_arm(arm) - self.svg.set_show(not arm) - self._make_block_graphics(svg, self.svg.sandwich_top, label) - if label: - 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]]] - else: - 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_collapsible_style_bottom(self, svg): - self.svg.expand(self.dx + self.ex, self.ey) - self._make_block_graphics(svg, self.svg.sandwich_bottom) - 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_invisible_style(self, svg): - self._make_block_graphics(svg, self.svg.invisible) - # force dock positions to be the same + 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_block_graphics(svg, self.svg.basic_flow) self.docks = [['flow', True, self.svg.docks[0][0], - self.svg.docks[0][1]], ['flow', False, - self.svg.docks[0][0], self.svg.docks[0][1]]] + self.svg.docks[0][1]], + ['flow', False, self.svg.docks[1][0], + self.svg.docks[1][1]]] # Depreciated block styles diff --git a/TurtleArt/tacanvas.py b/TurtleArt/tacanvas.py index 246fe35..878d40d 100644 --- a/TurtleArt/tacanvas.py +++ b/TurtleArt/tacanvas.py @@ -21,6 +21,7 @@ #THE SOFTWARE. import gtk +import gobject from math import sin, cos, atan, pi, sqrt import os import pango @@ -116,7 +117,7 @@ class TurtleGraphics: self.cx = 0 self.cy = 0 self.fgrgb = [255, 0, 0] - self.bgrgb = [255, 255, 255] + self.bgrgb = [255, 248, 222] self.textsize = 48 # deprecated self.shade = 0 self.pendown = False @@ -189,7 +190,7 @@ class TurtleGraphics: def _clearscreen(cr): cr.move_to(0, 0) - self.bgrgb = [255, 255, 255] + self.bgrgb = [255, 248, 222] cr.set_source_rgb(self.bgrgb[0] / 255., self.bgrgb[1] / 255., self.bgrgb[2] / 255.) @@ -592,7 +593,7 @@ class TurtleGraphics: round_int(width), round_int(height), data]])) - self.tw.send_event(event) + gobject.idle_add(self.tw.send_event, event) os.remove(tmp_file) def draw_text(self, label, x, y, size, w, share=True): diff --git a/TurtleArt/tacollaboration.py b/TurtleArt/tacollaboration.py index 8316c7a..a025074 100644 --- a/TurtleArt/tacollaboration.py +++ b/TurtleArt/tacollaboration.py @@ -1,4 +1,4 @@ -#Copyright (c) 2011, Walter Bender +#Copyright (c) 2011-12 Walter Bender #Copyright (c) 2011 Collabora Ltd. #Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,10 +22,12 @@ from dbus.service import signal from dbus.gobject_service import ExportedGObject import telepathy - +import os import gtk import base64 +from gettext import gettext as _ + from TurtleArt.tautils import data_to_string, data_from_string, get_path, \ base64_to_image, debug_output, error_output from TurtleArt.taconstants import DEFAULT_TURTLE_COLORS @@ -54,7 +56,7 @@ class Collaboration(): self._setup_dispatch_table() def setup(self): - # TODO: hand off role of master is sharer leaves + # TODO: hand off role of master if sharer leaves self.pservice = presenceservice.get_instance() self.initiating = None # sharing (True) or joining (False) @@ -81,7 +83,9 @@ class Collaboration(): 'w': self._set_pen_width, 'p': self._set_pen_state, 'F': self._fill_polygon, - 'P': self._draw_pixbuf + 'P': self._draw_pixbuf, + 'B': self._paste, + 'S': self._speak } def _shared_cb(self, activity): @@ -112,6 +116,7 @@ class Collaboration(): id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( SERVICE, {}) + self._enable_share_button() def _joined_cb(self, activity): self._shared_activity = self._activity._shared_activity @@ -140,6 +145,11 @@ class Collaboration(): # Joiner should request current state from sharer. self.waiting_for_turtles = True + self._enable_share_button() + + def _enable_share_button(self): + self._activity.share_button.set_icon('shareon') + self._activity.share_button.set_tooltip(_('Share selected blocks')) def _list_tubes_reply_cb(self, tubes): for tube_info in tubes: @@ -192,11 +202,12 @@ class Collaboration(): try: command, payload = event_message.split('|', 2) - self._processing_methods[command](payload) except ValueError: - debug_output('Could not split event message.', + debug_output('Could not split event message [%s]' % event_message, self._tw.running_sugar) + self._processing_methods[command](payload) + # Restore active Turtle self._tw.canvas.set_turtle(self._tw.turtles.get_turtle_key( save_active_turtle)) @@ -376,6 +387,23 @@ class Collaboration(): poly_points[i][0], poly_points[i][1]))) self._tw.canvas.fill_polygon(shared_poly_points) + def _speak(self, payload): + if len(payload) > 0: + [nick, language_option, text] = data_from_string(payload) + if language_option == 'None': + language_option = '' + if text is not None: + os.system('espeak %s "%s" --stdout | aplay' % ( + language_option, str(text))) + + def _paste(self, payload): + if len(payload) > 0: + [nick, text] = data_from_string(payload) + if text is not None: + self._tw.process_data(data_from_string(text), + self._tw.paste_offset) + self._tw.paste_offset += 20 + def _get_dictionary(self): return {self._get_nick(): self._get_colors()} diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py index 7a82a42..8cba7c2 100644 --- a/TurtleArt/taconstants.py +++ b/TurtleArt/taconstants.py @@ -90,14 +90,13 @@ EXPANDABLE_STYLE = ['boolean-style', 'compare-porch-style', 'compare-style', 'number-style-porch', 'number-style', 'basic-style-2arg', 'number-style-block', 'box-style-media'] +# These are defined in add_block based on block style +EXPANDABLE_FLOW = [] + EXPANDABLE = ['vspace', 'hspace', 'identity2'] EXPANDABLE_ARGS = ['list', 'myfunc1arg', 'myfunc2arg', 'myfunc3arg', 'userdefined', 'userdefined2args', 'userdefined3args'] -# -# Blocks that are 'collapsible' -# -COLLAPSIBLE = ['sandwichbottom', 'sandwichcollapsed'] # # Deprecated block styles that need dock adjustments @@ -138,7 +137,7 @@ STATUS_SHAPES = ['status', 'info', 'nostack', 'dupstack', 'noinput', # Emulate Sugar toolbar when running from outside of Sugar # TOOLBAR_SHAPES = ['hideshowoff', 'eraseron', 'run-fastoff', - 'run-slowoff', 'debugoff', 'stopiton'] + 'run-slowoff', 'stopiton'] # # Legacy names @@ -218,6 +217,8 @@ VOICES = {'af': 'afrikaans', 'cy': 'welsh-test', 'el': 'greek', # Macros (groups of blocks) # MACROS = { + 'ifthenelse': # Because it is too big to fit on the palette + [[0, 'ifelse', 0, 0, [None, None, None, None, None]]], 'kbinput': [[0, 'until', 0, 0, [None, 1, 4, None]], [1, 'greater2', 0, 0, [0, 2, 3, None]], diff --git a/TurtleArt/tagplay.py b/TurtleArt/tagplay.py index 72379c6..505f6f1 100644 --- a/TurtleArt/tagplay.py +++ b/TurtleArt/tagplay.py @@ -37,8 +37,6 @@ import gst import gst.interfaces import gtk -import urllib - def play_audio_from_file(lc, file_path): """ Called from Show block of audio media """ @@ -77,6 +75,24 @@ def stop_media(lc): lc.gplay = None +def pause_media(lc): + """ From pause media block """ + if lc.gplay == None: + return False + + if lc.gplay.player is not None: + lc.gplay.player.pause() + + +def play_media(lc): + """ From play media block """ + if lc.gplay == None: + return False + + if lc.gplay.player is not None: + lc.gplay.player.play() + + def media_playing(lc): if lc.gplay == None: return False @@ -106,7 +122,7 @@ class Gplay(): if lc.tw.running_sugar: self.bin.set_transient_for(lc.tw.activity) - self.bin.move(x, y + 108) + self.bin.move(x, y) self.bin.resize(w, h) self.bin.show_all() @@ -114,6 +130,8 @@ class Gplay(): def _player_eos_cb(self, widget): logging.debug('end of stream') + # Make sure player is stopped after EOS + self.player.stop() def _player_error_cb(self, widget, message, detail): self.player.stop() @@ -133,12 +151,12 @@ class Gplay(): self.only_audio = only_audio self.got_stream_info = True - def start(self, uri=None): + def start(self, file_path=None): self._want_document = False - self.playpath = os.path.dirname(uri) - if not uri: + self.playpath = os.path.dirname(file_path) + if not file_path: return False - self.playlist.append('file://' + urllib.quote(os.path.abspath(uri))) + self.playlist.append('file://' + os.path.abspath(file_path)) if not self.player: # lazy init the player so that videowidget is realized # and has a valid widget allocation @@ -149,15 +167,13 @@ class Gplay(): try: if not self.currentplaying: - logging.info('Playing: ' + self.playlist[0]) + logging.info('Playing: %s' % (self.playlist[0])) self.player.set_uri(self.playlist[0]) self.currentplaying = 0 self.play_toggled() self.show_all() - else: - pass except: - pass + logging.error('Error playing %s' % (self.playlist[0])) return False def play_toggled(self): @@ -184,7 +200,9 @@ class GstPlayer(gobject.GObject): self.player = gst.element_factory_make('playbin', 'player') + videowidget.realize() self.videowidget = videowidget + self.videowidget_xid = videowidget.window.xid self._init_video_sink() bus = self.player.get_bus() @@ -200,7 +218,7 @@ class GstPlayer(gobject.GObject): if message.structure is None: return if message.structure.get_name() == 'prepare-xwindow-id': - self.videowidget.set_sink(message.src) + self.videowidget.set_sink(message.src, self.videowidget_xid) message.src.set_property('force-aspect-ratio', True) def on_message(self, bus, message): @@ -220,6 +238,8 @@ class GstPlayer(gobject.GObject): if old == gst.STATE_READY and new == gst.STATE_PAUSED: self.emit('stream-info', self.player.props.stream_info_value_array) + # else: + # logging.debug(message.type) def _init_video_sink(self): self.bin = gst.Bin() @@ -241,7 +261,6 @@ class GstPlayer(gobject.GObject): caps_string += 'width=%d, height=%d' % (w, h) else: caps_string += 'width=480, height=360' - caps = gst.Caps(caps_string) self.filter = gst.element_factory_make('capsfilter', 'filter') self.bin.add(self.filter) @@ -269,7 +288,7 @@ class GstPlayer(gobject.GObject): self.player.set_state(gst.STATE_NULL) self.playing = False logging.debug('stopped player') - return False + # return False def get_state(self, timeout=1): return self.player.get_state(timeout=timeout) @@ -294,7 +313,6 @@ class VideoWidget(gtk.DrawingArea): else: return True - def set_sink(self, sink): - assert self.window.xid + def set_sink(self, sink, xid): self.imagesink = sink - self.imagesink.set_xwindow_id(self.window.xid) + self.imagesink.set_xwindow_id(xid) diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py index 1e5bf07..3e8fb0f 100644 --- a/TurtleArt/talogo.py +++ b/TurtleArt/talogo.py @@ -27,6 +27,8 @@ from time import time from operator import isNumberType from UserDict import UserDict +from sugar.graphics import style + from taconstants import TAB_LAYER, DEFAULT_SCALE, PREFIX_DICTIONARY from tapalette import block_names, value_blocks from tautils import get_pixbuf_from_journal, convert, data_from_file, \ @@ -62,7 +64,6 @@ class symbol: class logoerror(Exception): def __init__(self, value): - print value self.value = value def __str__(self): @@ -149,7 +150,6 @@ class LogoCode: def stop_logo(self): """ Stop logo is called from the Stop button on the toolbar """ - self.tw.step_time = 0 self.step = _just_stop() for plugin in self.tw._plugins: plugin.stop() @@ -157,6 +157,7 @@ class LogoCode: from tagplay import stop_media stop_media(self) self.tw.active_turtle.show() + self.tw.running_blocks = False def def_prim(self, name, args, fcn, rprim=False): """ Define the primitives associated with the blocks """ @@ -192,9 +193,15 @@ class LogoCode: for b in blocks: b.unhighlight() + # Hidden macro expansions + for b in blocks: + if b.name in ['while', 'until']: + action_blk, new_blocks = self._expand_forever(b, blk, blocks) + blocks = new_blocks[:] + if b == blk: + blk = action_blk for b in blocks: - # Hidden macro expansions - if b.name in ['while', 'until', 'forever']: + if b.name in ['forever']: action_blk, new_blocks = self._expand_forever(b, blk, blocks) blocks = new_blocks[:] if b == blk: @@ -216,6 +223,7 @@ class LogoCode: except IndexError: self.tw.showlabel('#nostack') self.tw.showblocks() + self.tw.running_blocks = False return None if type(convert(x, float, False)) == float: if int(float(x)) == x: @@ -252,11 +260,10 @@ class LogoCode: if len(dock) > 4: # There could be a '(', ')', '[' or ']'. code.append(dock[4]) if blk.primitive is not None: # make a tuple (prim, blk) - # special case: expand 'while' and 'until' primitives - try: + if blk in self.tw.block_list.list: code.append((blk.primitive, self.tw.block_list.list.index(blk))) - except ValueError: + else: code.append(blk.primitive) # Hidden block elif len(blk.values) > 0: # Extract the value from content blocks. if blk.name == 'number': @@ -415,6 +422,7 @@ class LogoCode: if self.bindex is not None: self.tw.block_list.list[self.bindex].highlight() self.tw.showblocks() + self.tw.display_coordinates() raise logoerror(str(self.iresult)) self.iline = oldiline self.ireturn() @@ -454,6 +462,7 @@ class LogoCode: if token.nargs == None: self.tw.showblocks() + self.tw.display_coordinates() raise logoerror("#noinput") for i in range(token.nargs): self._no_args_check() @@ -497,6 +506,7 @@ class LogoCode: except ValueError: debug_output('generator already executing', self.tw.running_sugar) + self.tw.running_blocks = False return False else: return False @@ -507,11 +517,14 @@ class LogoCode: self.hidden_turtle = None else: self.tw.active_turtle.show() + self.tw.running_blocks = False return False except logoerror, e: self.tw.showblocks() + self.tw.display_coordinates() self.tw.showlabel('syntaxerror', str(e)) self.tw.turtles.show_all() + self.tw.running_blocks = False return False return True @@ -540,6 +553,7 @@ class LogoCode: if self.iline and self.iline[0] is not self.symnothing: return self.tw.showblocks() + self.tw.display_coordinates() raise logoerror("#noinput") # @@ -562,7 +576,7 @@ class LogoCode: self.tw.clear_plugins() if self.tw.gst_available: from tagplay import stop_media - # stop_media(self) # TODO: gplay variable + stop_media(self) self.tw.canvas.clearscreen() self.scale = DEFAULT_SCALE self.hidden_turtle = None @@ -731,6 +745,30 @@ class LogoCode: self.ireturn() yield True + def media_stop(self): + """ Stop playing media""" + if self.tw.gst_available: + from tagplay import stop_media + stop_media(self) + self.ireturn() + yield True + + def media_pause(self): + """ Pause media""" + if self.tw.gst_available: + from tagplay import pause_media + pause_media(self) + self.ireturn() + yield True + + def media_play(self): + """ Play media""" + if self.tw.gst_available: + from tagplay import play_media + play_media(self) + self.ireturn() + yield True + def play_sound(self): """ Sound file from Journal """ if self.tw.gst_available: @@ -744,8 +782,17 @@ class LogoCode: return if self.tw.gst_available: from tagplay import play_movie_from_file - play_movie_from_file(self, self.filepath, self.x2tx(), self.y2ty(), - w, h) + # The video window is an overlay, so we need to know where + # the canvas is relative to the window, e.g., which + # toolbars, if any are open. + yoffset = 0 + if self.tw.running_sugar: + if not self.tw.activity.is_fullscreen(): + yoffset += style.GRID_CELL_SIZE + if self.tw.activity.toolbars_expanded(): + yoffset += style.GRID_CELL_SIZE + play_movie_from_file(self, self.filepath, self.x2tx(), + self.y2ty() + yoffset, w, h) def _expand_forever(self, b, blk, blocks): """ Expand a while or until block into: forever, ifelse, stopstack @@ -755,6 +802,9 @@ class LogoCode: # 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 + + # TODO: Detect nesting, e.g., forever while + if b.name == 'while': while_blk = True else: @@ -804,11 +854,11 @@ class LogoCode: inflow.connections[i] = action_blk else: i = None + j = None if outflow is not None: - j = outflow.connections.index(b) - outflow.connections[j] = action_blk - else: - j = None + if b in outflow.connections: + j = outflow.connections.index(b) + outflow.connections[j] = action_blk if until_blk and whileflow is not None: action_first.connections.append(inflow) diff --git a/TurtleArt/tapalette.py b/TurtleArt/tapalette.py index ad96103..b14c207 100644 --- a/TurtleArt/tapalette.py +++ b/TurtleArt/tapalette.py @@ -22,6 +22,7 @@ help_palettes = {} help_windows = {} palette_names = [] +palette_init_on_start = [] palette_blocks = [] block_colors = [] expandable_blocks = [] @@ -49,7 +50,6 @@ block_styles = {'basic-style': [], 'basic-style-3arg': [], 'basic-style-var-arg': [], 'bullet-style': [], - 'invisible': [], 'box-style': [], 'box-style-media': [], 'number-style': [], @@ -62,17 +62,13 @@ block_styles = {'basic-style': [], 'compare-porch-style': [], 'boolean-style': [], 'not-style': [], - 'flow-style': [], 'flow-style-tail': [], - 'flow-style-1arg': [], - 'flow-style-boolean': [], - 'flow-style-while': [], - 'flow-style-else': [], - 'collapsible-top': [], - 'collapsible-top-no-arm': [], - 'collapsible-top-no-label': [], - 'collapsible-top-no-arm-no-label': [], - 'collapsible-bottom': [], + 'clamp-style': [], + 'clamp-style-collapsible': [], + 'clamp-style-collapsed': [], + 'clamp-style-1arg': [], + 'clamp-style-boolean': [], + 'clamp-style-else': [], 'portfolio-style-2x2': [], 'portfolio-style-1x1': [], 'portfolio-style-2x1': [], @@ -84,7 +80,7 @@ import gtk from sugar.graphics.icon import Icon from sugar.graphics import style -from taconstants import EXPANDABLE_STYLE +from taconstants import EXPANDABLE_STYLE, EXPANDABLE_FLOW from tautils import debug_output from util.helpbutton import add_section, add_paragraph @@ -125,7 +121,7 @@ class Palette(): self._help_box = help_palettes[self._name] self._help = 'deja vu' - def add_palette(self, position=None): + def add_palette(self, position=None, init_on_start=False): if self._name is None: debug_output('You must specify a name for your palette') return @@ -143,6 +139,9 @@ class Palette(): palette_names.insert(i, self._name) palette_blocks.insert(i, []) block_colors.insert(i, self._colors) + if init_on_start: + if not self._name in palette_init_on_start: + palette_init_on_start.append(self._name) else: return @@ -211,7 +210,8 @@ class Palette(): block.add_block() -def make_palette(palette_name, colors=None, help_string=None, position=None): +def make_palette(palette_name, colors=None, help_string=None, position=None, + init_on_start=False): """ Palette helper function """ if colors is None: palette = Palette(palette_name) @@ -219,7 +219,7 @@ def make_palette(palette_name, colors=None, help_string=None, position=None): palette = Palette(palette_name, colors) if help_string is not None: palette.set_help(help_string) - palette.add_palette(position) + palette.add_palette(position, init_on_start=init_on_start) return palette @@ -271,6 +271,12 @@ class Block(): return else: block_styles[self._style].append(self._name) + if self._style in ['clamp-style', + 'clamp-style-collapsible', + 'clamp-style-1arg', + 'clamp-style-boolean', + 'clamp-style-else']: + EXPANDABLE_FLOW.append(self._name) if self._label is not None: block_names[self._name] = self._label diff --git a/TurtleArt/tasprite_factory.py b/TurtleArt/tasprite_factory.py index 2b0e922..2c403b6 100755 --- a/TurtleArt/tasprite_factory.py +++ b/TurtleArt/tasprite_factory.py @@ -64,12 +64,16 @@ class SVG: 4 * self._stroke_width self._porch_y = self._innie_y2 self._expand_x = 0 + self._expand_x2 = 0 self._expand_y = 0 + self._expand_y2 = 0 + self._second_clamp = False self._arm = True self._else = False self._draw_innies = True self._hide = False self._show = False + self._collapsible = False self._show_x = 0 self._show_y = 0 self._hide_x = 0 @@ -426,76 +430,69 @@ class SVG: svg = "%s%s%s%s%s%s%s%s" % (" \n") svg += "%s%s%s%s%s%s%s%s%s%s" % (" \n") svg += "%s%s%s%s%s%s%s%s%s%s" % (" \n") svg += "%s%s%s%s%s%s%s%s%s%s" % (" \n") svg += "%s%s%s%s%s%s%s%s%s%s" % (" \n") - svg += "%s%s%s%s%s%s%s%s%s%s%s%s" % (" \n") + svg += '' % (self._fill, self._stroke) svg += "%s%s%s%s%s%s%s%s%s%s%s%s" % (" \n") svg += "%s%s%s%s%s" % (" \n") svg += "%s%s%s%s%s%s" % (" \n") svg += "%s%s%s%s%s%s" % (" \n") svg += "%s%s%s%s%s%s" % (" \n") svg += "%s%s%s%s%s%s" % (" \n") svg += "%s%s%s%s%s%s" % (" \n") self._width, self._height = 55, 55 svg += self.footer() @@ -522,9 +519,10 @@ class SVG: 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 ''' + def clamp(self): + ''' Special block for collapsible stacks; includes an 'arm" + that extends down the left side of a stack and a bottom jaw to + clamp the blocks. ''' self.reset_min_max() x = self._stroke_width / 2.0 y = self._stroke_width / 2.0 + self._radius @@ -534,64 +532,46 @@ class SVG: 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._radius + self._stroke_width, 0) svg += self._rline_to(self._expand_x, 0) xx = self._x svg += self._corner(1, 1) - if innie_flag: + if self._innie[0] is True: svg += self._do_innie() + else: + self.margins[2] = \ + int((self._x - self._stroke_width + 0.5) * self._scale) + if self._bool is True: + svg += self._do_boolean() 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._arm: - svg += self._inverse_corner(-1, 1, 90, 0, 0) - svg += self._rline_to(0, self._expand_y) - svg += self._rline_to(-self._radius, 0) - else: - 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() - 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 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 - 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._inverse_corner(-1, 1, 90, 0, 0) svg += self._rline_to(0, self._expand_y) svg += self._inverse_corner(1, 1, 90, 0, 0) svg += self._do_slot() svg += self._rline_to(self._radius, 0) + if self._second_clamp: + svg += self._corner(-1, 1) + svg += self.line_to(xx, self._y) + svg += self._rline_to(-self._expand_x, 0) + svg += self._do_tab() + svg += self._inverse_corner(-1, 1, 90, 0, 0) + svg += self._rline_to(0, self._expand_y2) + 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._do_tab() 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: + if self._collapsible: svg += self._hide_dot() - if self._show is True: - svg += self._show_dot() svg += self.footer() return self.header() + svg @@ -635,6 +615,9 @@ class SVG: def set_show(self, flag=False): self._show = flag + def set_collapsible(self, flag=False): + self._collapsible = flag + def get_width(self): return self._width @@ -656,9 +639,14 @@ class SVG: def set_orientation(self, orientation=0): self._orientation = orientation - def expand(self, w=0, h=0): + def second_clamp(self, flag=False): + self._second_clamp = flag + + def expand(self, w=0, h=0, w2=0, h2=0): self._expand_x = w self._expand_y = h + self._expand_x2 = w2 + self._expand_y2 = h2 def set_stroke_width(self, stroke_width=1.5): self._stroke_width = stroke_width @@ -1011,8 +999,8 @@ class SVG: 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)) + self._rline_to(self._slot_x / 2.0, -self._slot_y * 3.0), + self._rline_to(self._slot_x / 2.0, self._slot_y * 3.0)) else: return self._rline_to(self._slot_x, 0) @@ -1021,8 +1009,8 @@ class SVG: return self._rline_to(-self._slot_x, 0) elif self._tail: 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)) + self._rline_to(-self._slot_x / 2.0, self._slot_y * 3.0), + self._rline_to(-self._slot_x / 2.0, -self._slot_y * 3.0)) else: return self._rline_to(-self._slot_x, 0) @@ -1167,8 +1155,8 @@ class SVG: 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 + y += self._slot_y * 3.0 + self.margins[1] += self._slot_y * 3.0 elif self._slot is True: self.margins[1] += self._slot_y self.margins[0] *= self._scale @@ -1190,13 +1178,13 @@ def close_file(f): def generator(datapath): svg0 = SVG() - f = open_file(datapath, "basic.svg") - svg0.set_innie([True, True]) + f = open_file(datapath, "clamp.svg") svg0.set_scale(2) - svg0.set_tab(True) - svg0.set_slot(True) svg0.set_arm(True) - svg_str = svg0.basic_block() + svg0.expand(0, 0, 0, 21) + svg0.set_collapsible(True) + svg0.set_hide(True) + svg_str = svg0.clamp() f.write(svg_str) close_file(f) diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py index 40c39e9..bd7309f 100644 --- a/TurtleArt/tautils.py +++ b/TurtleArt/tautils.py @@ -1,4 +1,4 @@ -#Copyright (c) 2007-8, Playful Invention Company. +#copyright (c) 2007-8, Playful Invention Company. #Copyright (c) 2008-12, Walter Bender #Permission is hereby granted, free of charge, to any person obtaining a copy @@ -41,11 +41,7 @@ except (ImportError, AttributeError): OLD_SUGAR_SYSTEM = True from StringIO import StringIO -from taconstants import COLLAPSIBLE, HIT_HIDE, HIT_SHOW, XO1, XO15, XO175, \ - XO30, UNKNOWN - -SANDWICHES = ['sandwichtop', 'sandwichtop_no_label', 'sandwichtop_no_arm', - 'sandwichtop_no_arm_no_label'] +from taconstants import HIT_HIDE, HIT_SHOW, XO1, XO15, XO175, XO30, UNKNOWN import logging _logger = logging.getLogger('turtleart-activity') @@ -235,8 +231,14 @@ def data_from_file(ta_file): def data_from_string(text): ''' JSON load data from a string. ''' - return json_load(text.replace(']],\n', ']], ')) - + if type(text) == str: + return json_load(text.replace(']],\n', ']], ')) + elif type(text) == unicode: + text = text.encode('ascii', 'replace') + return json_load(text.replace(']],\n', ']], ')) + else: + print 'type error (%s) in data_from_string' % (type(text)) + return None def data_to_file(data, ta_file): ''' Write data to a file. ''' @@ -390,226 +392,77 @@ def calc_image_size(spr): 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'. ''' - if top is not None and top.name in ['sandwichtop', 'sandwichtop_no_label']: - if top.ey > 0: - top.reset_y() - top.svg.set_hide(False) - top.refresh() - - -def grow_stack_arm(top): - ''' 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: - 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.svg.set_hide(True) - 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. - cblk = blk.connections[0] - while cblk is not None: - if cblk.name in COLLAPSIBLE: - return None - if cblk.name in ['repeat', 'if', 'ifelse', 'forever', 'while']: - if blk != cblk.connections[len(cblk.connections) - 1]: - return None - 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. ''' - # Always follow the main branch of a flow: the last connection. - cblk = blk.connections[len(blk.connections) - 1] - while cblk is not None: - if cblk.name in SANDWICHES: - return None - 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 SANDWICHES: - return blk - # Always follow the main branch of a flow: the last connection. - 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_clamp(top): + ''' Restore the blocks in a sandwich clamp. ''' + if top is None: + return -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 gblk in group: - if not hit_bottom and gblk == bot: - hit_bottom = True - if len(gblk.values) == 0: - gblk.values.append(0) - else: - gblk.values[0] = 0 - olddx = gblk.docks[1][2] - olddy = gblk.docks[1][3] - # Replace 'sandwichcollapsed' shape with 'sandwichbottom' shape - 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 = 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. - dx += gblk.docks[1][2] - olddx - dy += gblk.docks[1][3] - olddy - else: - if not hit_bottom: - gblk.spr.restore() - gblk.status = None - else: - gblk.spr.move_relative((dx, dy)) - # Add 'sandwichtop' arm - if top.name == 'sandwichtop_no_arm': - top.name = 'sandwichtop' - else: - top.name = 'sandwichtop_no_label' - top.spr.set_label(' ', 1) + if top.name == 'sandwichclampcollapsed': + y1 = top.docks[2][3] + if top.connections[1] is not None: + for blk in find_group(top.connections[1]): + blk.spr.restore() + blk.status = None + + # If you come across a 'sandwichclampcollapsed', do not + # restore its clamp + for blk in find_group(top.connections[1]): + if blk.name == 'sandwichclampcollapsed': + if blk.connections[1] is not None: + for b in find_group(blk.connections[1]): + b.spr.hide() + b.status = 'collapsed' + + bot = top.connections[2] + top.name = 'sandwichclamp' + top.spr.set_label('') top.resize() - top.refresh() - grow_stack_arm(top) + top.svg.set_hide(True) + top.refresh() + y2 = top.docks[2][3] + dy = y2 - y1 + if bot is not None: + for blk in find_group(bot): + blk.spr.move_relative((0, dy)) + if top.connections[1] is not None: + # Make sure stack is aligned to dock + x1, y1 = top.connections[1].spr.get_xy() + x1 += top.connections[1].docks[0][2] + y1 += top.connections[1].docks[0][3] + x2, y2 = top.spr.get_xy() + x2 += top.docks[1][2] + y2 += top.docks[1][3] + if x1 != x2 or y1 != y2: + for blk in find_group(top.connections[1]): + blk.spr.move_relative((x2 - x1, y2 - y1)) + return -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 in ['sandwichtop_no_arm', - 'sandwichtop_no_arm_no_label']: - restore_stack(top) - return - if len(top.connections) == 0: - return - cblk = top.connections[len(top.connections) - 1] - while cblk is not None: - if cblk.name in COLLAPSIBLE: - return - if cblk.name in ['sandwichtop_no_arm', 'sandwichtop_no_arm_no_label']: - restore_stack(cblk) - return - # Follow a fork - if cblk.name in ['repeat', 'if', 'ifelse', 'forever', 'while', - 'until']: - top = find_sandwich_top_below( - cblk.connections[len(cblk.connections) - 2]) - if top is not None: - uncollapse_forks(top, True) - if cblk.name == 'ifelse': - top = find_sandwich_top_below( - cblk.connections[len(cblk.connections) - 3]) - if top is not None: - uncollapse_forks(top, True) - cblk = cblk.connections[len(cblk.connections) - 1] - return - - -def collapse_stack(top): - ''' Hide all the blocks between the sandwich top and sandwich bottom. ''' - # First uncollapse any nested stacks +def collapse_clamp(top, transform=True): + ''' Hide all the blocks in the clamp. ''' 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 gblk in group: - if not hit_bottom and gblk == bot: - hit_bottom = True - # Replace 'sandwichbottom' with invisible 'sandwichcollapsed' - if len(gblk.values) == 0: - gblk.values.append(1) - else: - 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. - dx += gblk.docks[1][2] - olddx - dy += gblk.docks[1][3] - olddy - else: - if not hit_bottom: - gblk.spr.hide() - gblk.status = 'collapsed' - else: - gblk.spr.move_relative((dx, dy)) - # Remove 'sandwichtop' arm - if top.name == 'sandwichtop' or top.name == 'sandwichtop_no_arm': - top.name = 'sandwichtop_no_arm' - else: - top.name = 'sandwichtop_no_arm_no_label' - top.spr.set_label(' ') - top.spr.set_label(' ', 1) - top.resize() - top.spr.set_label(_('click to open'), 1) - top.resize() - top.svg.set_hide(False) - 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 + if top.name in ['sandwichclampcollapsed', 'sandwichclamp']: + y1 = top.docks[2][3] + if top.connections[1] is not None: + for blk in find_group(top.connections[1]): + blk.spr.hide() + blk.status = 'collapsed' + if transform: + bot = top.connections[2] + top.name = 'sandwichclampcollapsed' + top.spr.set_label(_('click to open')) + top.reset_y() + top.resize() + top.svg.set_hide(False) + top.refresh() + y2 = top.docks[2][3] + dy = y2 - y1 + if bot is not None: + for blk in find_group(bot): + blk.spr.move_relative((0, dy)) + return def hide_button_hit(spr, x, y): @@ -760,6 +613,8 @@ def find_top_block(blk): ''' Find the top block in a stack. ''' if blk is None: return None + if blk.connections is None: + return blk if len(blk.connections) == 0: return blk while blk.connections[0] is not None: @@ -767,6 +622,19 @@ def find_top_block(blk): return blk +def find_bot_block(blk): + ''' Find the bottom block in a stack. ''' + if blk is None: + return None + if blk.connections is None: + return blk + if len(blk.connections) == 0: + return blk + while blk.connections[-1] is not None: + blk = blk.connections[-1] + return blk + + def find_start_stack(blk): ''' Find a stack with a 'start' block on top. ''' if blk is None: diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py index fe55ee7..baf2212 100644 --- a/TurtleArt/tawindow.py +++ b/TurtleArt/tawindow.py @@ -48,14 +48,14 @@ from taconstants import HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE, \ TOOLBAR_SHAPES, TAB_LAYER, RETURN, OVERLAY_LAYER, CATEGORY_LAYER, \ BLOCKS_WITH_SKIN, ICON_SIZE, PALETTE_SCALE, PALETTE_WIDTH, SKIN_PATHS, \ MACROS, TOP_LAYER, BLOCK_LAYER, OLD_NAMES, DEFAULT_TURTLE, TURTLE_LAYER, \ - CURSOR, EXPANDABLE, COLLAPSIBLE, DEAD_DICTS, DEAD_KEYS, NO_IMPORT, \ + CURSOR, EXPANDABLE, DEAD_DICTS, DEAD_KEYS, NO_IMPORT, \ TEMPLATES, PYTHON_SKIN, PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK, \ EXPANDABLE_ARGS, XO1, XO15, XO175, XO30, TITLEXY, CONTENT_ARGS, \ - CONSTANTS, EXPAND_SKIN, PROTO_LAYER + CONSTANTS, EXPAND_SKIN, PROTO_LAYER, EXPANDABLE_FLOW from tapalette import palette_names, palette_blocks, expandable_blocks, \ block_names, content_blocks, default_values, special_names, block_styles, \ help_strings, hidden_proto_blocks, string_or_number_args, \ - make_palette, palette_name_to_index + make_palette, palette_name_to_index, palette_init_on_start from talogo import LogoCode, primitive_dictionary, logoerror from tacanvas import TurtleGraphics from tablock import Blocks, Block @@ -63,12 +63,11 @@ 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, \ - calc_image_size, get_path, reset_stack_arm, grow_stack_arm, \ - find_sandwich_top, find_sandwich_bottom, restore_stack, collapse_stack, \ - collapsed, collapsible, hide_button_hit, show_button_hit, chooser, \ + calc_image_size, get_path, hide_button_hit, show_button_hit, chooser, \ arithmetic_check, xy, find_block_to_run, find_top_block, journal_check, \ find_group, find_blk_below, data_to_string, find_start_stack, \ - get_hardware, debug_output, error_output, data_to_string, convert + get_hardware, debug_output, error_output, data_to_string, convert, \ + find_bot_block, restore_clamp, collapse_clamp from tasprite_factory import SVG, svg_str_to_pixbuf, svg_from_file from sprites import Sprites, Sprite @@ -77,10 +76,11 @@ if GST_AVAILABLE: MOTION_THRESHOLD = 6 SNAP_THRESHOLD = 200 +NO_DOCK = (100, 100) # Blocks cannot be docked class TurtleArtWindow(): - """ TurtleArt Window class abstraction """ + ''' TurtleArt Window class abstraction ''' timeout_tag = [0] _PLUGIN_SUBPATH = 'plugins' @@ -89,6 +89,7 @@ class TurtleArtWindow(): self._loaded_project = '' self._sharing = False self.parent = parent + self.window_init_complete = False self.turtle_canvas = turtle_canvas self.send_event = None # method to send events over the network self.gst_available = GST_AVAILABLE @@ -266,13 +267,19 @@ class TurtleArtWindow(): if self.interactive_mode: self._setup_misc() - self.show_toolbar_palette(0, False) - + for name in palette_init_on_start: + debug_output('initing palette %s' % (name), self.running_sugar) + self.show_toolbar_palette(palette_names.index(name), + init_only=False, regenerate=True, + show=False) + self.show_toolbar_palette(0, init_only=False, regenerate=True, + show=True) self.saved_pictures = [] self.block_operation = '' + self.window_init_complete = True def _get_plugin_home(self): - """ Look in the execution directory """ + ''' Look in the execution directory ''' path = os.path.join(self.path, self._PLUGIN_SUBPATH) if os.path.exists(path): return path @@ -280,7 +287,7 @@ class TurtleArtWindow(): return None def _get_plugins_from_plugins_dir(self, path): - """ Look for plugin files in plugin dir. """ + ''' Look for plugin files in plugin dir. ''' plugin_files = [] if path is not None: candidates = os.listdir(path) @@ -291,13 +298,13 @@ class TurtleArtWindow(): return plugin_files def _init_plugins(self): - """ Try importing plugin files from the plugin dir. """ + ''' Try importing plugin files from the plugin dir. ''' for plugin_dir in self._get_plugins_from_plugins_dir( self._get_plugin_home()): self.init_plugin(plugin_dir) def init_plugin(self, plugin_dir): - """ Initialize plugin in plugin_dir """ + ''' Initialize plugin in plugin_dir ''' plugin_class = plugin_dir.capitalize() f = "def f(self): from plugins.%s.%s import %s; return %s(self)" \ % (plugin_dir, plugin_dir, plugin_class, plugin_class) @@ -323,7 +330,7 @@ class TurtleArtWindow(): self._icon_paths.append(icon_path) def _get_plugin_instance(self, plugin_name): - """ Returns the plugin 'plugin_name' instance """ + ''' Returns the plugin 'plugin_name' instance ''' list_plugins = self._get_plugins_from_plugins_dir( self._get_plugin_home()) if plugin_name in list_plugins: @@ -333,48 +340,47 @@ class TurtleArtWindow(): return None def _setup_plugins(self): - """ Initial setup -- called just once. """ + ''' Initial setup -- called just once. ''' for plugin in self._plugins: plugin.setup() def _start_plugins(self): - """ Start is called everytime we execute blocks. """ + ''' Start is called everytime we execute blocks. ''' for plugin in self._plugins: plugin.start() def _stop_plugins(self): - """ Stop is called whenever we stop execution. """ + ''' Stop is called whenever we stop execution. ''' for plugin in self._plugins: 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() def background_plugins(self): - """ Background is called when we are pushed to the background. """ + ''' Background is called when we are pushed to the background. ''' for plugin in self._plugins: plugin.goto_background() def foreground_plugins(self): - """ Foreground is called when we are return from the background. """ + ''' Foreground is called when we are return from the background. ''' for plugin in self._plugins: plugin.return_to_foreground() def quit_plugins(self): - """ Quit is called upon program exit. """ + ''' Quit is called upon program exit. ''' for plugin in self._plugins: plugin.quit() def _setup_events(self): - """ Register the events we listen to. """ + ''' 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('realize', self.do_realize) 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) @@ -382,7 +388,7 @@ class TurtleArtWindow(): self.window.connect("key-press-event", self._keypress_cb) def load_media_shapes(self): - """ Media shapes get positioned onto blocks """ + ''' Media shapes get positioned onto blocks ''' for name in MEDIA_SHAPES: if name in self.media_shapes: continue @@ -399,7 +405,7 @@ class TurtleArtWindow(): break def _setup_misc(self): - """ Misc. sprites for status, overlays, etc. """ + ''' Misc. sprites for status, overlays, etc. ''' self.load_media_shapes() for i, name in enumerate(STATUS_SHAPES): # Temporary hack to use wider shapes @@ -457,16 +463,16 @@ class TurtleArtWindow(): return self._sharing def is_project_empty(self): - """ Check to see if project has any blocks in use """ + ''' Check to see if project has any blocks in use ''' return len(self.just_blocks()) == 1 def _expose_cb(self, win=None, event=None): - """ Repaint """ + ''' Repaint ''' self.do_expose_event(event) return True - # Handle the expose-event by drawing def do_expose_event(self, event=None): + ''' Handle the expose-event by drawing ''' # Create the cairo context cr = self.window.window.cairo_create() @@ -492,16 +498,16 @@ class TurtleArtWindow(): self.sprite_list.redraw_sprites(cr=cr) def eraser_button(self): - """ Eraser_button (hide status block when clearing the screen.) """ + ''' Eraser_button (hide status block when clearing the screen.) ''' if self.status_spr is not None: self.status_spr.hide() self.lc.find_value_blocks() # Are there blocks to update? self.lc.prim_clear() - self.display_coordinates() self.parent.restore_challenge() + self.display_coordinates() def run_button(self, time, running_from_button_push=False): - """ Run turtle! """ + ''' Run turtle! ''' if self.running_sugar: self.activity.recenter() if self.status_spr is not None: @@ -534,12 +540,12 @@ class TurtleArtWindow(): return def stop_button(self): - """ Stop button """ + ''' Stop button ''' self.lc.stop_logo() - self._stop_plugins() + # self._stop_plugins() def set_userdefined(self, blk=None): - """ Change icon for user-defined blocks after loading Python code. """ + ''' Change icon for user-defined blocks after loading Python code. ''' if blk is not None: if blk.name in PYTHON_SKIN: x, y = self._calc_image_offset('pythonon', blk.spr) @@ -547,13 +553,13 @@ class TurtleArtWindow(): self._resize_skin(blk) def set_fullscreen(self): - """ Enter fullscreen mode """ + ''' Enter fullscreen mode ''' if self.running_sugar: self.activity.fullscreen() self.activity.recenter() def set_cartesian(self, flag): - """ Turn on/off Cartesian coordinates """ + ''' Turn on/off Cartesian coordinates ''' if self.coord_scale == 1: self.draw_overlay('Cartesian_labeled') else: @@ -561,12 +567,12 @@ class TurtleArtWindow(): return def set_polar(self, flag): - """ Turn on/off polar coordinates """ + ''' Turn on/off polar coordinates ''' self.draw_overlay('polar') return def set_metric(self, flag): - """ Turn on/off metric coordinates """ + ''' Turn on/off metric coordinates ''' self.draw_overlay('metric') return @@ -583,7 +589,7 @@ class TurtleArtWindow(): self.canvas.heading = save_heading def update_overlay_position(self, widget, event): - """ Reposition the overlays when window size changes """ + ''' Reposition the overlays when window size changes ''' self.width = event.width self.height = event.height for name in OVERLAY_SHAPES: @@ -610,7 +616,7 @@ class TurtleArtWindow(): self.canvas.move_turtle() def hideshow_button(self): - """ Hide/show button """ + ''' Hide/show button ''' if not self.hide: for blk in self.just_blocks(): blk.spr.hide() @@ -629,12 +635,12 @@ class TurtleArtWindow(): self.inval_all() def inval_all(self): - """ Force a refresh """ + ''' Force a refresh ''' if self.interactive_mode: self.window.queue_draw_area(0, 0, self.width, self.height) def hideshow_palette(self, state): - """ Hide or show palette """ + ''' Hide or show palette ''' if not state: self.palette = False if self.running_sugar: @@ -648,7 +654,7 @@ class TurtleArtWindow(): self.show_palette() def show_palette(self, n=None): - """ Show palette. """ + ''' Show palette. ''' if n is None: if self.selected_palette is None: n = 0 @@ -663,7 +669,7 @@ class TurtleArtWindow(): self.palette = True def hide_palette(self): - """ Hide the palette. """ + ''' Hide the palette. ''' self._hide_toolbar_palette() self.palette_button[self.orientation].hide() self.palette_button[2].hide() @@ -674,7 +680,7 @@ class TurtleArtWindow(): self.palette = False def move_palettes(self, x, y): - """ Move the palettes. """ + ''' Move the palettes. ''' for p in self.palettes: for blk in p: blk.spr.move((x + blk.spr.save_xy[0], y + blk.spr.save_xy[1])) @@ -696,7 +702,7 @@ class TurtleArtWindow(): y + gblk.spr.save_xy[1])) def hideblocks(self): - """ Callback from 'hide blocks' block """ + ''' Callback from 'hide blocks' block ''' if not self.interactive_mode: return self.hide = False @@ -705,7 +711,7 @@ class TurtleArtWindow(): self.activity.do_hide_blocks() def showblocks(self): - """ Callback from 'show blocks' block """ + ''' Callback from 'show blocks' block ''' if not self.interactive_mode: return self.hide = True @@ -714,33 +720,16 @@ class TurtleArtWindow(): self.activity.do_show_blocks() def resize_blocks(self, blocks=None): - """ Resize blocks or if blocks is None, all of the blocks """ + ''' 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 in ['sandwichtop', 'sandwichtop_no_label']: - 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: @@ -767,7 +756,7 @@ class TurtleArtWindow(): def show_toolbar_palette(self, n, init_only=False, regenerate=False, show=True): - """ Show the toolbar palettes, creating them on init_only """ + ''' 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. if (self.activity is None or not self.activity.has_toolbarbox) and \ @@ -827,6 +816,7 @@ class TurtleArtWindow(): blk.spr.hide() if n == palette_names.index('trash'): for blk in self.trash_stack: + # Deprecated for gblk in find_group(blk): if gblk.status != 'collapsed': gblk.spr.set_layer(TAB_LAYER) @@ -837,6 +827,39 @@ class TurtleArtWindow(): self.selected_palette = save_selected self.previous_palette = save_previous + def regenerate_palette(self, n): + ''' Regenerate palette (used by some plugins) ''' + if (self.activity is None or not self.activity.has_toolbarbox) and \ + self.selectors == []: + return + if self.palette_sprs == []: + return + + save_selected = self.selected_palette + save_previous = self.previous_palette + self.selected_palette = n + self.previous_palette = self.selected_palette + + if save_selected == n: + self._layout_palette(n, regenerate=True) + else: + self._layout_palette(n, regenerate=True, show=False) + + for blk in self.palettes[n]: + if blk.get_visibility(): + if hasattr(blk.spr, 'set_layer'): + blk.spr.set_layer(PROTO_LAYER) + else: + debug_output('WARNING: block sprite is None' % (blk.name), + self.running_sugar) + else: + blk.spr.hide() + + if not save_selected == n: + self._hide_previous_palette(palette=n) + self.selected_palette = save_selected + self.previous_palette = save_previous + def _display_palette_shift_button(self, n): ''' Palettes too wide (or tall) for the screen get a shift button ''' if self.palette_sprs[n][self.orientation].type == \ @@ -990,7 +1013,7 @@ class TurtleArtWindow(): return def _hide_toolbar_palette(self): - """ Hide the toolbar palettes """ + ''' Hide the toolbar palettes ''' self._hide_previous_palette() if self.activity is None or not self.activity.has_toolbarbox: # Hide the selectors @@ -1002,7 +1025,7 @@ class TurtleArtWindow(): palette_names[self.selected_palette] + 'off') def _hide_previous_palette(self, palette=None): - """ Hide just the previously viewed toolbar palette """ + ''' Hide just the previously viewed toolbar palette ''' if palette is None: palette = self.previous_palette # Hide previously selected palette @@ -1029,7 +1052,7 @@ class TurtleArtWindow(): gblk.spr.hide() def _horizontal_layout(self, x, y, blocks): - """ Position prototypes in a horizontal palette. """ + ''' Position prototypes in a horizontal palette. ''' max_w = 0 for blk in blocks: if not blk.get_visibility(): @@ -1054,7 +1077,7 @@ class TurtleArtWindow(): return x, y, max_w def _vertical_layout(self, x, y, blocks): - """ Position prototypes in a vertical palette. """ + ''' Position prototypes in a vertical palette. ''' row = [] row_w = 0 max_h = 0 @@ -1098,7 +1121,7 @@ class TurtleArtWindow(): return x, y, max_h def _layout_palette(self, n, regenerate=False, show=True): - """ Layout prototypes in a palette. """ + ''' Layout prototypes in a palette. ''' if n is not None: if self.orientation == HORIZONTAL_PALETTE: x, y = 20, self.toolbar_offset + 5 @@ -1161,7 +1184,7 @@ class TurtleArtWindow(): svg_str_to_pixbuf(svg.palette(w, h))) def _buttonpress_cb(self, win, event): - """ Button press """ + ''' Button press ''' self.window.grab_focus() x, y = xy(event) self.mouse_flag = 1 @@ -1230,6 +1253,11 @@ class TurtleArtWindow(): # From the sprite at x, y, look for a corresponding block blk = self.block_list.spr_to_block(spr) + ''' If we were copying and didn't click on a block... ''' + if self.running_sugar and \ + (self.activity.copying or self.activity.sharing_blocks): + if blk is None or blk.type != 'block': + self.activity.restore_cursor() if blk is not None: if blk.type == 'block': self.selected_blk = blk @@ -1352,7 +1380,7 @@ class TurtleArtWindow(): return True def _update_action_names(self, name): - """ change the label on action blocks of the same name """ + ''' change the label on action blocks of the same name ''' if CURSOR in name: name = name.replace(CURSOR, '') for blk in self.just_blocks(): @@ -1364,11 +1392,11 @@ class TurtleArtWindow(): blk.spr.labels[0] = name blk.values[0] = name blk.spr.set_layer(BLOCK_LAYER) - self._change_proto_name(name, 'stack_%s' % (self._saved_action_name), + self._update_proto_name(name, 'stack_%s' % (self._saved_action_name), 'stack_%s' % (name), 'basic-style-1arg') def _update_box_names(self, name): - """ change the label on box blocks of the same name """ + ''' change the label on box blocks of the same name ''' if CURSOR in name: name = name.replace(CURSOR, '') for blk in self.just_blocks(): @@ -1380,41 +1408,56 @@ class TurtleArtWindow(): blk.spr.labels[0] = name blk.values[0] = name blk.spr.set_layer(BLOCK_LAYER) - self._change_proto_name(name, 'box_%s' % (self._saved_box_name), + self._update_proto_name(name, 'box_%s' % (self._saved_box_name), 'box_%s' % (name), 'number-style-1strarg') - def _change_proto_name(self, name, old, new, style, palette='blocks'): - """ change the name of a proto block """ - for blk in self.just_protos(): + def _update_proto_name(self, name, old, new, style, palette='blocks'): + ''' Change the name of a proto block ''' + # The name change has to happen in multiple places: + # (1) The proto block itself + # (2) The list of block styles + # (3) The list of proto blocks on the palette + # (4) The list of block names + if old == new: + debug_output('update_proto_name: %s == %s' % (old, new), + self.running_sugar) + return + found = False + + i = palette_name_to_index(palette) + for blk in self.palettes[i]: # self.just_protos(): if blk.name == old: blk.name = new blk.spr.labels[0] = name blk.spr.set_layer(PROTO_LAYER) - i = palette_name_to_index(palette) - if old in palette_blocks[i]: - j = palette_blocks[i].index(old) - palette_blocks[i][j] = new - if old in block_styles[style]: - block_styles[style].remove(old) - block_styles[style].append(new) - else: - debug_output('%s not in %s' % (old, block_styles[style]), - self.running_sugar) - if old in block_names: - del block_names[old] - block_names[new] = name - else: - debug_output('%s not in %s' % (old, block_names), - self.running_sugar) blk.resize() - return + break # Should only be one proto block by this name + + if old in palette_blocks[i]: + palette_blocks[i].remove(old) + if not new in palette_blocks[i]: + palette_blocks[i].append(new) + + if old in block_styles[style]: + block_styles[style].remove(old) + if not new in block_styles[style]: + block_styles[style].append(new) + + if old in block_names: + del block_names[old] + if not new in block_names: + block_names[new] = name + + self.show_toolbar_palette(i, regenerate=True) def _action_name(self, blk, hat=False): - """ is this a label for an action block? """ + ''' is this a label for an action block? ''' if blk is None: return False if blk.name != 'string': # Ignoring int names return False + if blk.connections is None: + return False if blk.connections[0] is None: return False if hat and blk.connections[0].name == 'hat': @@ -1424,11 +1467,13 @@ class TurtleArtWindow(): return False def _box_name(self, blk, storein=False): - """ is this a label for a storein block? """ + ''' is this a label for a storein block? ''' if blk is None: return False if blk.name != 'string': # Ignoring int names return False + if blk.connections is None: + return False if blk.connections[0] is None: return False if storein and blk.connections[0].name == 'storein': @@ -1438,7 +1483,7 @@ class TurtleArtWindow(): return False def _select_category(self, spr): - """ Select a category from the toolbar """ + ''' Select a category from the toolbar ''' i = self.selectors.index(spr) spr.set_shape(self.selector_shapes[i][1]) if self.selected_selector is not None: @@ -1451,7 +1496,7 @@ class TurtleArtWindow(): self.show_palette(i) def _select_toolbar_button(self, spr): - """ Select a toolbar button (Used when not running Sugar). """ + ''' Select a toolbar button (Used when not running Sugar). ''' if not hasattr(spr, 'name'): return if spr.name == 'run-fastoff': @@ -1463,10 +1508,6 @@ class TurtleArtWindow(): self.lc.trace = 1 self.showblocks() self.run_button(3) - elif spr.name == 'debugoff': - self.lc.trace = 1 - self.showblocks() - self.run_button(6) elif spr.name == 'stopiton': self.stop_button() self.display_coordinates() @@ -1478,76 +1519,72 @@ class TurtleArtWindow(): self.hideshow_button() def _put_in_trash(self, blk, x=0, y=0): - """ Put a group of blocks into the trash. """ + ''' 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(palette_names.index('trash')) if self.selected_palette != palette_names.index('trash'): for gblk in group: gblk.spr.hide() - # if there was a named hat or storein, remove it from the proto palette + # If there was a named hat or storein, remove it from the + # proto palette, the palette name list, the block name list, + # and the style list for gblk in group: - if gblk.name == 'hat' and \ - gblk.connections is not None and \ - gblk.connections[1] is not None and \ - gblk.connections[1].name == 'string' and \ - gblk.connections[1].values[0] != _('action'): - i = palette_name_to_index('blocks') - name = 'stack_%s' % (gblk.connections[1].values[0]) - if name in palette_blocks[i]: - palette_blocks[i].remove(name) - self.show_toolbar_palette(i, regenerate=True) - if gblk.name == 'storein' and \ + if (gblk.name == 'hat' or gblk.name == 'storein') and \ gblk.connections is not None and \ gblk.connections[1] is not None and \ - gblk.connections[1].name == 'string' and \ - gblk.connections[1].values[0] != _('box'): + gblk.connections[1].name == 'string': + if gblk.name == 'hat': + name = 'stack_%s' % gblk.connections[1].values[0] + style = 'basic-style-1arg' + else: + name = 'box_%s' % gblk.connections[1].values[0] + style = 'number-style-1strarg' i = palette_name_to_index('blocks') - name = 'box_%s' % (gblk.connections[1].values[0]) if name in palette_blocks[i]: palette_blocks[i].remove(name) - self.show_toolbar_palette(i, regenerate=True) + for blk in self.palettes[i]: + if blk.name == name: + blk.spr.hide() + self.palettes[i].remove(blk) + self.show_toolbar_palette(i, regenerate=True) + if name in block_styles[style]: + block_styles[style].remove(name) + if name in block_names: + del block_names[name] def _restore_all_from_trash(self): - """ Restore all the blocks in the trash can. """ + ''' 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. """ + ''' 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: + if gblk.name == 'sandwichclampcollapsed': + restore_clamp(gblk) + self.resize_parent_clamps(gblk) + for gblk in group: gblk.rescale(self.block_scale) gblk.spr.set_layer(BLOCK_LAYER) @@ -1557,13 +1594,9 @@ class TurtleArtWindow(): 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: @@ -1573,7 +1606,7 @@ class TurtleArtWindow(): self.trash_stack.remove(blk) def _empty_trash(self): - """ Permanently remove all blocks presently in the trash can. """ + ''' Permanently remove all blocks presently in the trash can. ''' for blk in self.block_list.list: if blk.type == 'trash': blk.type = 'deleted' @@ -1581,7 +1614,7 @@ class TurtleArtWindow(): self.trash_stack = [] def _in_the_trash(self, x, y): - """ Is x, y over a palette? """ + ''' 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)): @@ -1589,7 +1622,7 @@ class TurtleArtWindow(): return False def _block_pressed(self, x, y, blk): - """ Block pressed """ + ''' Block pressed ''' if blk is not None: blk.highlight() self._disconnect(blk) @@ -1599,7 +1632,16 @@ class TurtleArtWindow(): for blk in self.drag_group: if blk.status != 'collapsed': blk.spr.set_layer(TOP_LAYER) - blk.highlight() + if self.running_sugar and \ + (self.activity.copying or self.activity.sharing_blocks): + for blk in self.drag_group: + if blk.status != 'collapsed': + blk.highlight() + self.block_operation = 'copying' + if self.activity.copying: + self.activity.send_to_clipboard() + else: + self.activity.share_blocks() if self.running_sugar and self._sharing and \ hasattr(self.activity, 'share_button'): self.activity.share_button.set_tooltip( @@ -1608,14 +1650,11 @@ class TurtleArtWindow(): self.saved_string = blk.spr.labels[0] self._saved_action_name = self.saved_string self._saved_box_name = self.saved_string - debug_output('_block_pressed: %s (%s, %s)' % ( - self.saved_string, self._saved_action_name, - self._saved_box_name), self.running_sugar) else: self.saved_string = '' def _unselect_block(self): - """ Unselect block """ + ''' Unselect block ''' # After unselecting a 'number' block, we need to check its value if self.selected_blk.name == 'number': self._number_check() @@ -1627,7 +1666,7 @@ class TurtleArtWindow(): self.selected_blk = None def _new_block(self, name, x, y, defaults=None): - """ Make a new block. """ + ''' Make a new block. ''' x_pos = x - 20 y_pos = y - 20 if name in content_blocks: @@ -1706,54 +1745,67 @@ class TurtleArtWindow(): self.used_block_list.append(newblk.spr.labels[0]) def _new_macro(self, name, x, y): - """ Create a "macro" (predefined stack of blocks). """ + ''' 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). """ - + ''' Process block_data (from a macro, a file, or the clipboard). ''' + self._process_block_data = [] + for blk in block_data: + if not self._found_a_turtle(blk): + self._process_block_data.append( + [blk[0], blk[1], blk[2], blk[3], blk[4]]) + self._extra_block_data = [] # Create the blocks (or turtle). blocks = [] - for blk in block_data: + for blk in self._process_block_data: if not self._found_a_turtle(blk): - blocks.append(self.load_block(blk, offset)) + newblk = self.load_block(blk, offset) + if newblk is not None: + blocks.append(newblk) + # Some extra blocks may have been added by load_block + for blk in self._extra_block_data: + self._process_block_data.append(blk) + newblk = self.load_block(blk, offset) + if newblk is not None: + blocks.append(newblk) # Make the connections. for i in range(len(blocks)): cons = [] # Normally, it is simply a matter of copying the connections. if blocks[i].connections is None: - if block_data[i][4] is not None: - for c in block_data[i][4]: + if self._process_block_data[i][4] is not None: + for c in self._process_block_data[i][4]: if c is None or c > (len(blocks) - 1): cons.append(None) else: cons.append(blocks[c]) else: - debug_output("connection error %s" % (str(block_data[i])), + debug_output("connection error %s" % ( + str(self._process_block_data[i])), self.running_sugar) cons.append(None) elif blocks[i].connections == 'check': # Convert old-style boolean and arithmetic blocks cons.append(None) # Add an extra connection. - for c in block_data[i][4]: + for c in self._process_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 block_styles['boolean-style']: - if block_data[i][4][0] is not None: - c = block_data[i][4][0] - cons[0] = blocks[block_data[c][4][0]] - c0 = block_data[c][4][0] - for j, cj in enumerate(block_data[c0][4]): + if self._process_block_data[i][4][0] is not None: + c = self._process_block_data[i][4][0] + cons[0] = blocks[self._process_block_data[c][4][0]] + c0 = self._process_block_data[c][4][0] + for j, cj in enumerate(self._process_block_data[c0][4]): if cj == c: blocks[c0].connections[j] = blocks[i] if c < i: @@ -1764,11 +1816,11 @@ class TurtleArtWindow(): debug_output("Warning: dock to the future", self.running_sugar) else: - if block_data[i][4][0] is not None: - c = block_data[i][4][0] - cons[0] = blocks[block_data[c][4][0]] - c0 = block_data[c][4][0] - for j, cj in enumerate(block_data[c0][4]): + if self._process_block_data[i][4][0] is not None: + c = self._process_block_data[i][4][0] + cons[0] = blocks[self._process_block_data[c][4][0]] + c0 = self._process_block_data[c][4][0] + for j, cj in enumerate(self._process_block_data[c0][4]): if cj == c: blocks[c0].connections[j] = blocks[i] if c < i: @@ -1788,15 +1840,27 @@ class TurtleArtWindow(): for blk in blocks: self._adjust_dock_positions(blk) - # Look for any stacks that need to be collapsed or sandwiched + # Look for any stacks that need to be collapsed 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)) + if blk.name == 'sandwichclampcollapsed': + collapse_clamp(blk, False) + + # process in reverse order + for i in range(len(blocks)): + blk = blocks[-i - 1] + if blk.name in EXPANDABLE_FLOW: + if blk.name in block_styles['clamp-style-1arg'] or\ + blk.name in block_styles['clamp-style-boolean']: + if blk.connections[2] is not None: + self._resize_clamp(blk, blk.connections[2]) + elif blk.name in block_styles['clamp-style']: + if blk.connections[1] is not None: + self._resize_clamp(blk, blk.connections[1]) + elif blk.name in block_styles['clamp-style-else']: + if blk.connections[2] is not None: + self._resize_clamp(blk, blk.connections[2], dockn=2) + if blk.connections[3] is not None: + self._resize_clamp(blk, blk.connections[3], dockn=3) # Resize blocks to current scale self.resize_blocks(blocks) @@ -1807,7 +1871,7 @@ class TurtleArtWindow(): return None def _adjust_dock_positions(self, blk): - """ Adjust the dock x, y positions """ + ''' Adjust the dock x, y positions ''' if not self.interactive_mode: return (sx, sy) = blk.spr.get_xy() @@ -1864,7 +1928,7 @@ class TurtleArtWindow(): self.turtle_movement_to_share = None def _mouse_move(self, x, y): - """ Process mouse movements """ + ''' Process mouse movements ''' if self.running_sugar and self.dragging_canvas[0]: dx = self.dragging_canvas[1] - x @@ -1910,10 +1974,6 @@ class TurtleArtWindow(): elif self.drag_group[0] is not None: blk = self.drag_group[0] - # Don't move a bottom blk if the stack is collapsed - if collapsed(blk): - return - self.selected_spr = blk.spr dragx, dragy = self.drag_pos (sx, sy) = blk.spr.get_xy() @@ -1973,7 +2033,7 @@ class TurtleArtWindow(): self.dy += dy def _show_popup(self, x, y): - """ Let's help our users by displaying a little help. """ + ''' 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: @@ -2009,7 +2069,7 @@ class TurtleArtWindow(): self.timeout_tag[0] = 0 def _do_show_popup(self, block_name): - """ Fetch the help text and display it. """ + ''' Fetch the help text and display it. ''' if self.no_help: return 0 if block_name in special_names: @@ -2031,7 +2091,7 @@ class TurtleArtWindow(): return 0 def _buttonrelease_cb(self, win, event): - """ Button release """ + ''' Button release ''' x, y = xy(event) self.mouse_flag = 0 self.mouse_x = x @@ -2099,7 +2159,6 @@ class TurtleArtWindow(): # 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) @@ -2112,6 +2171,14 @@ class TurtleArtWindow(): abs(self.dx) < MOTION_THRESHOLD and \ abs(self.dy < MOTION_THRESHOLD))): self._click_block(x, y) + elif self.block_operation == 'copying': + gobject.timeout_add(500, self._unhighlight_drag_group, blk) + + def _unhighlight_drag_group(self, blk): + self.drag_group = find_group(blk) + for gblk in self.drag_group: + gblk.unhighlight() + self.drag_group = None def remote_turtle(self, name): ''' Is this a remote turtle? ''' @@ -2138,7 +2205,7 @@ class TurtleArtWindow(): turtle.show() def _move_turtle(self, x, y): - """ Move the selected turtle to (x, y). """ + ''' Move the selected turtle to (x, y). ''' self.canvas.xcor = x self.canvas.ycor = y self.canvas.move_turtle() @@ -2152,7 +2219,7 @@ class TurtleArtWindow(): self.canvas.ycor / self.coord_scale) def _click_block(self, x, y): - """ Click block: lots of special cases to handle... """ + ''' Click block: lots of special cases to handle... ''' blk = self.block_list.spr_to_block(self.selected_spr) if blk is None: return @@ -2179,7 +2246,9 @@ class TurtleArtWindow(): elif blk.name == 'identity2' or blk.name == 'hspace': group = find_group(blk) if hide_button_hit(blk.spr, x, y): - dx = blk.reset_x() + dx = -20 + blk.contract_in_x(-dx) + # dx = blk.reset_x() elif show_button_hit(blk.spr, x, y): dx = 20 blk.expand_in_x(dx) @@ -2193,7 +2262,9 @@ class TurtleArtWindow(): elif blk.name == 'vspace': group = find_group(blk) if hide_button_hit(blk.spr, x, y): - dy = blk.reset_y() + dy = -20 + blk.contract_in_y(-dy) + # dy = blk.reset_y() elif show_button_hit(blk.spr, x, y): dy = 20 blk.expand_in_y(dy) @@ -2203,7 +2274,7 @@ class TurtleArtWindow(): for gblk in group: if gblk != blk: gblk.spr.move_relative((0, dy * blk.scale)) - grow_stack_arm(find_sandwich_top(blk)) + self._resize_parent_clamps(blk) elif blk.name in expandable_blocks: # Connection may be lost during expansion, so store it... @@ -2212,7 +2283,9 @@ class TurtleArtWindow(): dock0 = blk0.connections.index(blk) if hide_button_hit(blk.spr, x, y): - dy = blk.reset_y() + dy = -20 + blk.contract_in_y(-dy) + # dy = blk.reset_y() elif show_button_hit(blk.spr, x, y): dy = 20 blk.expand_in_y(dy) @@ -2231,7 +2304,7 @@ class TurtleArtWindow(): blk0.connections[dock0] = blk self._cascade_expandable(blk) - grow_stack_arm(find_sandwich_top(blk)) + self._resize_parent_clamps(blk) elif blk.name in EXPANDABLE_ARGS or blk.name == 'nop': if show_button_hit(blk.spr, x, y): @@ -2278,39 +2351,41 @@ class TurtleArtWindow(): blk.connections[n - 1] = argblk if blk.name in block_styles['number-style-var-arg']: self._cascade_expandable(blk) - grow_stack_arm(find_sandwich_top(blk)) + self._resize_parent_clamps(blk) elif blk.name in PYTHON_SKIN: self._import_py() else: self._run_stack(blk) - - elif blk.name in ['sandwichtop_no_arm_no_label', - 'sandwichtop_no_arm']: - restore_stack(blk) - - elif blk.name in COLLAPSIBLE or blk.name == 'sandwichtop_no_label': - if blk.name == 'sandwichtop_no_label': - if hide_button_hit(blk.spr, x, y): - collapse_stack(blk) - else: - self._run_stack(blk) - top = find_sandwich_top(blk) - if collapsed(blk): - restore_stack(top) # deprecated (bottom block is invisible) - elif top is not None: - collapse_stack(top) + elif blk.name == 'sandwichclampcollapsed': + restore_clamp(blk) + if blk.connections[1] is not None: + self._resize_clamp(blk, blk.connections[1], 1) + self._resize_parent_clamps(blk) + elif blk.name == 'sandwichclamp': + if hide_button_hit(blk.spr, x, y): + collapse_clamp(blk, True) + self._resize_parent_clamps(blk) + else: + self._run_stack(blk) else: self._run_stack(blk) + def _resize_parent_clamps(self, blk): + ''' If we changed size, we need to let any parent clamps know. ''' + nblk, dockn = self._expandable_flow_above(blk) + while nblk is not None: + self._resize_clamp(nblk, nblk.connections[dockn], dockn=dockn) + nblk, dockn = self._expandable_flow_above(nblk) + def _expand_boolean(self, blk, blk2, dy): - """ Expand a boolean blk if blk2 is too big to fit. """ + ''' Expand a boolean blk if blk2 is too big to fit. ''' group = find_group(blk2) for gblk in find_group(blk): if gblk not in group: gblk.spr.move_relative((0, -dy * blk.scale)) def _expand_expandable(self, blk, blk2, dy): - """ Expand an expandable blk if blk2 is too big to fit. """ + ''' Expand an expandable blk if blk2 is too big to fit. ''' if blk2 is None: group = [blk] else: @@ -2336,7 +2411,7 @@ class TurtleArtWindow(): return False def _cascade_expandable(self, blk): - """ If expanding/shrinking a block, cascade. """ + ''' If expanding/shrinking a block, cascade. ''' while self._number_style(blk.name): if blk.connections[0] is None: break @@ -2364,33 +2439,8 @@ class TurtleArtWindow(): else: break - def _check_collapsibles(self, blk): - """ Check 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 in ['sandwichtop', 'sandwichtop_no_label']: - if find_sandwich_bottom(b) is None: - reset_stack_arm(b) - gblk.refresh() - def _run_stack(self, blk): - """ Run a stack of blocks. """ + ''' Run a stack of blocks. ''' if blk is None: return self.lc.find_value_blocks() # Are there blocks to update? @@ -2409,12 +2459,12 @@ class TurtleArtWindow(): pass def _snap_to_dock(self): - """ Snap a block (selected_block) to the dock of another block - (destination_block). - """ + ''' Snap a block (selected_block) to the dock of another block + (destination_block). ''' selected_block = self.drag_group[0] best_destination = None d = SNAP_THRESHOLD + self.inserting_block_mid_stack = False 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 @@ -2425,8 +2475,9 @@ class TurtleArtWindow(): continue # Check each dock of destination for a possible connection for destination_dockn in range(len(destination_block.docks)): - this_xy = dock_dx_dy(destination_block, destination_dockn, - selected_block, selected_block_dockn) + this_xy = self.dock_dx_dy( + destination_block, destination_dockn, + selected_block, selected_block_dockn) if magnitude(this_xy) > d: continue d = magnitude(this_xy) @@ -2450,11 +2501,33 @@ class TurtleArtWindow(): (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_destination.connections[best_destination_dockn] - if blk_in_dock is not None and blk_in_dock != selected_block: - blk_in_dock.connections[0] = None - self._put_in_trash(blk_in_dock) + if self.inserting_block_mid_stack: + # If there was already a block docked there, move it + # to the bottom of the drag group. + if blk_in_dock is not None and blk_in_dock != selected_block: + bot = find_bot_block(self.drag_group[0]) + if bot is not None: + blk_in_dock.connections[0] = None + drag_group = find_group(blk_in_dock) + blk_in_dock.connections[0] = bot + bot.connections[-1] = blk_in_dock + dx = bot.spr.get_xy()[0] - \ + self.drag_group[0].spr.get_xy()[0] + \ + bot.docks[-1][2] - blk_in_dock.docks[0][2] + dy = bot.spr.get_xy()[1] - \ + self.drag_group[0].spr.get_xy()[1] + \ + bot.docks[-1][3] - blk_in_dock.docks[0][3] + # Move each sprite in the group associated + # with the block we are moving. + for gblk in drag_group: + gblk.spr.move_relative((dx, dy)) + else: + # If there was already a block docked there, move it + # to the trash. + if blk_in_dock is not None and blk_in_dock != selected_block: + 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] = \ @@ -2480,6 +2553,23 @@ class TurtleArtWindow(): dy += 45 best_destination.expand_in_y(dy) self._expand_boolean(best_destination, selected_block, dy) + elif best_destination.name in EXPANDABLE_FLOW: + if best_destination.name in block_styles['clamp-style-1arg'] or\ + best_destination.name in block_styles['clamp-style-boolean']: + if best_destination_dockn == 2: + self._resize_clamp(best_destination, self.drag_group[0]) + elif best_destination.name in block_styles['clamp-style'] or \ + best_destination.name in block_styles[ + 'clamp-style-collapsible']: + if best_destination_dockn == 1: + self._resize_clamp(best_destination, self.drag_group[0]) + elif best_destination.name in block_styles['clamp-style-else']: + if best_destination_dockn == 2: + self._resize_clamp( + best_destination, self.drag_group[0], dockn=2) + elif best_destination_dockn == 3: + self._resize_clamp( + best_destination, self.drag_group[0], dockn=3) elif best_destination.name in expandable_blocks and \ best_destination_dockn == 1: dy = 0 @@ -2500,20 +2590,30 @@ class TurtleArtWindow(): self._expand_expandable( best_destination, selected_block, dy) self._cascade_expandable(best_destination) - grow_stack_arm(find_sandwich_top(best_destination)) + # If we are in an expandable flow, expand it... + if best_destination is not None: + self._resize_parent_clamps(best_destination) + ''' + blk, dockn = self._expandable_flow_above(best_destination) + while blk is not None: + self._resize_clamp(blk, blk.connections[dockn], dockn=dockn) + blk, dockn = self._expandable_flow_above(blk) + ''' def _disconnect(self, blk): - """ Disconnect block from stack above it. """ + ''' Disconnect block from stack above it. ''' if blk is None: return - if blk.connections[0] is None: + if blk.connections is None: return - if collapsed(blk): + if blk.connections[0] is None: return + c = None blk2 = blk.connections[0] if blk in blk2.connections: c = blk2.connections.index(blk) blk2.connections[c] = None + blk3, dockn = self._expandable_flow_above(blk) if blk2.name in block_styles['boolean-style']: if c == 2 and blk2.ey > 0: @@ -2526,12 +2626,79 @@ class TurtleArtWindow(): if dy != 0: self._expand_expandable(blk2, blk, dy) self._cascade_expandable(blk2) - grow_stack_arm(find_sandwich_top(blk2)) - + elif c is not None and blk2.name in EXPANDABLE_FLOW: + if blk2.name in block_styles['clamp-style-1arg'] or\ + blk2.name in block_styles['clamp-style-boolean']: + if c == 2: + self._resize_clamp(blk2, None, c) + elif blk2.name in block_styles['clamp-style'] or \ + blk2.name in block_styles['clamp-style-collapsible']: + if c == 1: + self._resize_clamp(blk2, None) + elif blk2.name in block_styles['clamp-style-else']: + if c == 2 or c == 3: + self._resize_clamp(blk2, None, dockn=c) + while blk3 is not None and blk3.connections[dockn] is not None: + self._resize_clamp(blk3, blk3.connections[dockn], dockn=dockn) + blk3, dockn = self._expandable_flow_above(blk3) blk.connections[0] = None + def _resize_clamp(self, blk, gblk, dockn=-2): + ''' If the content of a clamp changes, resize it ''' + if dockn < 0: + dockn = len(blk.docks) + dockn + y1 = blk.docks[-1][3] + if blk.name in block_styles['clamp-style-else'] and dockn == 3: + blk.reset_y2() + else: + blk.reset_y() + dy = 0 + # Calculate height of drag group + while gblk is not None: + delta = int((gblk.docks[-1][3] - gblk.docks[0][3]) / gblk.scale) + if delta == 0: + dy += 21 # Fixme: don't hardcode size of stop action block + else: + dy += delta + gblk = gblk.connections[-1] + # Clamp has room for one "standard" block by default + if dy > 0: + dy -= 21 # Fixme: don't hardcode + if blk.name in block_styles['clamp-style-else'] and dockn == 3: + blk.expand_in_y2(dy) + else: + blk.expand_in_y(dy) + y2 = blk.docks[-1][3] + gblk = blk.connections[-1] + # Move group below clamp up or down + if blk.connections[-1] is not None: + drag_group = find_group(blk.connections[-1]) + for gblk in drag_group: + gblk.spr.move_relative((0, y2-y1)) + # We may have to move the else clamp group down too. + if blk.name in block_styles['clamp-style-else'] and dockn == 2: + if blk.connections[3] is not None: + drag_group = find_group(blk.connections[3]) + for gblk in drag_group: + gblk.spr.move_relative((0, y2 - y1)) + + def _expandable_flow_above(self, blk): + ''' Is there an expandable flow block above this one? ''' + while blk.connections[0] is not None: + if blk.connections[0].name in EXPANDABLE_FLOW: + if blk.connections[0].name == 'ifelse': + if blk.connections[0].connections[2] == blk: + return blk.connections[0], 2 + elif blk.connections[0].connections[3] == blk: + return blk.connections[0], 3 + else: + if blk.connections[0].connections[-2] == blk: + return blk.connections[0], -2 + blk = blk.connections[0] + return None, None + def _import_from_journal(self, blk): - """ Import a file from the Sugar Journal """ + ''' Import a file from the Sugar Journal ''' # TODO: check blk name to set filter if self.running_sugar: chooser(self.parent, '', self._update_media_blk) @@ -2543,7 +2710,7 @@ class TurtleArtWindow(): self._update_media_icon(blk, fname) def _load_description_block(self, blk): - """ Look for a corresponding description block """ + ''' Look for a corresponding description block ''' if blk is None or blk.name != 'journal' or len(blk.values) == 0 or \ blk.connections[0] is None: return @@ -2554,12 +2721,12 @@ class TurtleArtWindow(): self._update_media_icon(dblk, None, blk.values[0]) def _update_media_blk(self, dsobject): - """ Called from the chooser to load a media block """ + ''' Called from the chooser to load a media block ''' self._update_media_icon(self.selected_blk, dsobject, dsobject.object_id) def _update_media_icon(self, blk, name, value=''): - """ Update the icon on a 'loaded' media block. """ + ''' Update the icon on a 'loaded' media block. ''' if blk.name == 'journal': self._load_image_thumb(name, blk) elif blk.name == 'audio': @@ -2577,7 +2744,7 @@ class TurtleArtWindow(): blk.spr.set_label(' ') def _load_image_thumb(self, picture, blk): - """ Replace icon with a preview image. """ + ''' Replace icon with a preview image. ''' pixbuf = None self._block_skin('descriptionon', blk) @@ -2602,7 +2769,7 @@ class TurtleArtWindow(): self._resize_skin(blk) def _keypress_cb(self, area, event): - """ Keyboard """ + ''' Keyboard ''' keyname = gtk.gdk.keyval_name(event.keyval) keyunicode = gtk.gdk.keyval_to_unicode(event.keyval) if event.get_state() & gtk.gdk.MOD1_MASK: @@ -2691,7 +2858,7 @@ class TurtleArtWindow(): self.selected_blk.spr.set_label(newnum + CURSOR) def process_alphanumeric_input(self, keyname, keyunicode): - """ Make sure alphanumeric input is properly parsed. """ + ''' 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: @@ -2772,7 +2939,7 @@ class TurtleArtWindow(): self.selected_blk.spr.set_label("%s%s%s" % (newleft, CURSOR, oldright)) def _process_keyboard_commands(self, keyname, block_flag=True): - """ Use the keyboard to move blocks and turtle """ + ''' Use the keyboard to move blocks and turtle ''' mov_dict = {'KP_Up': [0, 20], 'j': [0, 20], 'Up': [0, 20], 'KP_Down': [0, -20], 'k': [0, -20], 'Down': [0, -20], 'KP_Left': [-20, 0], 'h': [-20, 0], 'Left': [-20, 0], @@ -2787,7 +2954,7 @@ class TurtleArtWindow(): return True if keyname in ['KP_End', 'End']: - self.run_button(0) + self.run_button(self.step_time) elif self.selected_spr is not None: if not self.lc.running and block_flag: blk = self.block_list.spr_to_block(self.selected_spr) @@ -2817,7 +2984,7 @@ class TurtleArtWindow(): return True def _jog_turtle(self, dx, dy): - """ Jog turtle """ + ''' Jog turtle ''' if dx == -1 and dy == -1: self.canvas.xcor = 0 self.canvas.ycor = 0 @@ -2830,7 +2997,7 @@ class TurtleArtWindow(): self.selected_turtle = None def _align_to_grid(self, grid=20): - """ Align blocks at the top of stacks to a grid """ + ''' Align blocks at the top of stacks to a grid ''' for blk in self.block_list.list: if blk.type == 'block': top = find_top_block(blk) @@ -2848,11 +3015,9 @@ class TurtleArtWindow(): self._jog_block(top, dx, -dy) def _jog_block(self, blk, dx, dy): - """ Jog block """ + ''' Jog block ''' if blk.type == 'proto': return - if collapsed(blk): - return if dx == 0 and dy == 0: return self._disconnect(blk) @@ -2873,7 +3038,7 @@ class TurtleArtWindow(): self.drag_group = None def _number_check(self): - """ Make sure a 'number' block contains a number. """ + ''' Make sure a 'number' block contains a number. ''' n = self.selected_blk.spr.labels[0].replace(CURSOR, '') if n in ['-', '.', '-.', ',', '-,']: n = 0 @@ -2907,7 +3072,7 @@ class TurtleArtWindow(): self.saved_string = self.selected_blk.values[0] def load_python_code_from_file(self, fname=None, add_new_block=True): - """ Load Python code from a file """ + ''' Load Python code from a file ''' id = None self.python_code = None if fname is None: @@ -2972,7 +3137,7 @@ class TurtleArtWindow(): return id def load_python_code_from_journal(self, dsobject, blk=None): - """ Read the Python code from the Journal object """ + ''' Read the Python code from the Journal object ''' self.python_code = None try: debug_output("opening %s " % dsobject.file_path, @@ -2993,7 +3158,7 @@ class TurtleArtWindow(): blk.values[0] = dsobject.object_id def _import_py(self): - """ Import Python code into a block """ + ''' Import Python code into a block ''' if self.running_sugar: chooser(self.parent, 'org.laptop.Pippy', self.load_python_code_from_journal) @@ -3006,7 +3171,7 @@ class TurtleArtWindow(): self.set_userdefined(self.selected_blk) def new_project(self): - """ Start a new project """ + ''' Start a new project ''' self.lc.stop_logo() self._loaded_project = "" # Put current project in the trash. @@ -3018,11 +3183,11 @@ class TurtleArtWindow(): self.save_file_name = None def is_new_project(self): - """ Is this a new project or was a old project loaded from a file? """ + ''' 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. """ + ''' WARNING: order of JSON serialized data may have changed. ''' try: f = open(self._loaded_project, 'r') saved_project_data = f.read() @@ -3037,13 +3202,14 @@ class TurtleArtWindow(): return saved_project_data != current_project_data def load_files(self, ta_file, create_new_project=True): - """ Load a project from a file """ + ''' 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.process_data(data_from_file(ta_file)) self._loaded_project = ta_file - def load_file(self, create_new_project=True): + def load_file_from_chooser(self, create_new_project=True): + ''' Load a project from file chooser ''' _file_name, self.load_save_folder = get_load_name('.ta', self.load_save_folder) if _file_name is None: @@ -3057,7 +3223,7 @@ class TurtleArtWindow(): self.activity.metadata['title'] = os.path.split(_file_name)[1] def _found_a_turtle(self, blk): - """ Either [-1, 'turtle', ...] or [-1, ['turtle', key], ...] """ + ''' Either [-1, 'turtle', ...] or [-1, ['turtle', key], ...] ''' if blk[1] == 'turtle': self.load_turtle(blk) return True @@ -3071,7 +3237,7 @@ class TurtleArtWindow(): return False def load_turtle(self, blk, key=1): - """ Restore a turtle from its saved state """ + ''' 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, pendown=False) @@ -3081,7 +3247,7 @@ class TurtleArtWindow(): self.canvas.setpensize(pensize) def load_block(self, b, offset=0): - """ Restore individual blocks from saved state """ + ''' Restore individual blocks from saved state ''' if self.running_sugar: from sugar.datastore import datastore @@ -3092,17 +3258,78 @@ class TurtleArtWindow(): btype, value = btype elif type(btype) == list: btype, value = btype[0], btype[1] - if btype in content_blocks or btype in COLLAPSIBLE: + + # Replace deprecated sandwich blocks + if btype == 'sandwichtop_no_label': + btype = 'sandwichclamp' + docks = [] + for d in b[4]: + docks.append(d) + docks.append(None) + b[4] = docks + elif btype == 'sandwichtop_no_arm_no_label': + btype = 'sandwichclampcollapsed' + docks = [] + for d in b[4]: + docks.append(d) + docks.append(None) + b[4] = docks + # FIXME: blocks after sandwich bottom must be attached to + # sandwich top dock[2], currently set to None + elif btype in ['sandwichbottom', 'sandwichcollapsed']: + btype = 'vspace' + # FIXME: blocks after sandwichtop should be in a sandwich clamp + elif btype in ['sandwichtop', 'sandwichtop_no_arm']: + btype = 'comment' + + # Some blocks can only appear once... + if btype in ['start', 'hat1', 'hat2']: + if self._check_for_duplicate(btype): + name = block_names[btype][0] + while self._find_proto_name('stack_%s' % (name), name): + name += '_2' + i = len(self._process_block_data) + len(self._extra_block_data) + self._extra_block_data.append( + [i, ['string', name], 0, 0, [b[0], None]]) + # To do: check for a duplicate name + self._new_stack_block(name) + btype = 'hat' + self._process_block_data[b[0]] = [ + b[0], b[1], b[2], b[3], [b[4][0], i, b[4][1]]] + elif btype == 'hat': + if b[4][1] < len(self._process_block_data): + i = b[4][1] + name = self._process_block_data[i][1][1] + else: + i = b[4][1] - len(self._process_block_data) + name = self._extra_block_data[i][1][1] + while self._find_proto_name('stack_%s' % (name), name): + name += '_2' + if b[4][1] < len(self._process_block_data): + dblk = self._process_block_data[i] + self._process_block_data[i] = [dblk[0], (dblk[1][0], name), + dblk[2], dblk[3], dblk[4]] + else: + dblk = self._extra_block_data[i] + self._extra_block_data[i] = [dblk[0], (dblk[1][0], name), + dblk[2], dblk[3], dblk[4]] + self._new_stack_block(name) + elif btype == 'storein': + if b[4][1] < len(self._process_block_data): + i = b[4][1] + name = self._process_block_data[i][1][1] + else: + i = b[4][1] - len(self._process_block_data) + name = self._extra_block_data[i][1][1] + if not self._find_proto_name('box_%s' % (name), name): + self._new_box_block(name) + + if btype in content_blocks: 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: @@ -3120,6 +3347,14 @@ class TurtleArtWindow(): b[3] + self.canvas.cy + offset, 'block', values, self.block_scale) + # If it was an unknown block type, we need to match the number + # of dock items. TODO: Try to infer the dock type from connections + if len(b[4]) > len(blk.docks): + debug_output('dock mismatch %d > %d' % (len(b[4]), len(blk.docks)), + self.running_sugar) + for i in range(len(b[4]) - len(blk.docks)): + blk.docks.append(['unavailable', True, 0, 0]) + # Some blocks get transformed. if btype in block_styles['basic-style-var-arg'] and value is not None: # Is there code stored in this userdefined block? @@ -3208,13 +3443,21 @@ class TurtleArtWindow(): blk.spr.set_label(' ') blk.resize() elif btype in EXPANDABLE or btype in expandable_blocks or \ - btype in EXPANDABLE_ARGS or btype == 'nop': + btype in EXPANDABLE_FLOW or btype in EXPANDABLE_ARGS or \ + btype == 'nop': if btype == 'vspace' or btype in expandable_blocks: 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 in EXPANDABLE_FLOW: + if value is not None: + if type(value) is int: + blk.expand_in_y(value) + else: # thenelse blocks + blk.expand_in_y(value[0]) + blk.expand_in_y2(value[1]) elif btype == 'templatelist' or btype == 'list': for i in range(len(b[4]) - 4): blk.add_arg() @@ -3233,6 +3476,7 @@ class TurtleArtWindow(): blk.spr.set_layer(BLOCK_LAYER) if check_dock: blk.connections = 'check' + if self.running_sugar and len(blk.spr.labels) > 0 and \ blk.name not in ['', ' ', 'number', 'string']: if len(self.used_block_list) > 0: @@ -3243,8 +3487,15 @@ class TurtleArtWindow(): self.used_block_list.append(blk.spr.labels[0]) return blk + def _check_for_duplicate(self, name): + ''' Is there already a block of this name? ''' + for blk in self.just_blocks(): + if blk.name == name: + return True + return False + def load_start(self, ta_file=None): - """ Start a new project with a 'start' brick """ + ''' 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, @@ -3253,7 +3504,7 @@ class TurtleArtWindow(): self.process_data(data_from_file(ta_file)) def save_file(self, _file_name=None): - """ Start a project to a file """ + ''' 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: @@ -3269,52 +3520,54 @@ class TurtleArtWindow(): 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 datastream to be serialized """ - _data = [] - _blks = [] + ''' Pack the project (or stack) into a datastream to be serialized ''' + data = [] + blks = [] if save_project: - _blks = self.just_blocks() + 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]) + 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: + if len(blk.values) > 0: + name = (blk.name, blk.values[0]) else: - _name = (_blk.name) - elif _blk.name in block_styles['basic-style-var-arg'] and \ - len(_blk.values) > 0: - _name = (_blk.name, _blk.values[0]) - elif _blk.name in EXPANDABLE or _blk.name in expandable_blocks or\ - _blk.name in EXPANDABLE_ARGS: - _ex, _ey = _blk.get_expand_x_y() - if _ex > 0: - _name = (_blk.name, _ex) - elif _ey > 0: - _name = (_blk.name, _ey) + name = (blk.name) + elif blk.name in block_styles['basic-style-var-arg'] and \ + len(blk.values) > 0: + name = (blk.name, blk.values[0]) + elif blk.name in EXPANDABLE or blk.name in expandable_blocks or \ + blk.name in EXPANDABLE_ARGS or blk.name in EXPANDABLE_FLOW: + ex, ey, ey2 = blk.get_expand_x_y() + if blk.name in block_styles['clamp-style-else']: + name = (blk.name, (ey, ey2)) + elif 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) + 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') and _blk.connections is not None: - connections = [get_id(_cblk) for _cblk in _blk.connections] + name = (blk.name) + if hasattr(blk, 'connections') and blk.connections is not None: + connections = [get_id(cblk) for cblk in blk.connections] else: connections = None - (_sx, _sy) = _blk.spr.get_xy() + (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)) + 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): # Don't save remote turtles @@ -3322,14 +3575,14 @@ class TurtleArtWindow(): # Save default turtle as 'Yertle' if turtle == self.nick: turtle = DEFAULT_TURTLE - _data.append((-1, ['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 + return data def display_coordinates(self, clear=False): - """ Display the coordinates of the current turtle on the toolbar """ + ''' Display the coordinates of the current turtle on the toolbar ''' if clear: if self.running_sugar: self.activity.coordinates_label.set_text('') @@ -3352,7 +3605,7 @@ class TurtleArtWindow(): _("heading"), h)) def showlabel(self, shp, label=''): - """ Display a message on a status block """ + ''' Display a message on a status block ''' if not self.interactive_mode: debug_output(label, self.running_sugar) return @@ -3383,7 +3636,7 @@ class TurtleArtWindow(): self.status_spr.move((0, self.height - 100)) def calc_position(self, template): - """ Relative placement of portfolio objects (deprecated) """ + ''' Relative placement of portfolio objects (deprecated) ''' w, h, x, y, dx, dy = TEMPLATES[template] x *= self.canvas.width y *= self.canvas.height @@ -3394,7 +3647,7 @@ class TurtleArtWindow(): return(w, h, x, y, dx, dy) def save_for_upload(self, _file_name): - """ Grab the current canvas and save it for upload """ + ''' 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') @@ -3403,8 +3656,8 @@ class TurtleArtWindow(): 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. """ + def save_as_image(self, name="", svg=False): + ''' Grab the current canvas and save it. ''' if svg: suffix = '.svg' else: @@ -3466,13 +3719,13 @@ class TurtleArtWindow(): os.remove(file_path) else: if svg: - output = subprocess.check_output( + subprocess.check_output( ['mv', os.path.join(datapath, 'output.svg'), os.path.join(datapath, filename)]) self.saved_pictures.append((file_path, svg)) def just_blocks(self): - """ Filter out 'proto', 'trash', and 'deleted' blocks """ + ''' Filter out 'proto', 'trash', and 'deleted' blocks ''' just_blocks_list = [] for blk in self.block_list.list: if blk.type == 'block': @@ -3480,7 +3733,7 @@ class TurtleArtWindow(): return just_blocks_list def just_protos(self): - """ Filter out 'block', 'trash', and 'deleted' blocks """ + ''' Filter out 'block', 'trash', and 'deleted' blocks ''' just_protos_list = [] for blk in self.block_list.list: if blk.type == 'proto': @@ -3488,7 +3741,7 @@ class TurtleArtWindow(): return just_protos_list def _width_and_height(self, blk): - """ What are the width and height of a stack? """ + ''' What are the width and height of a stack? ''' minx = 10000 miny = 10000 maxx = -10000 @@ -3509,7 +3762,7 @@ class TurtleArtWindow(): # 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. """ + ''' Calculate the postion for placing an image onto a sprite. ''' _l, _t = spr.label_left_top() if name == '': return _l, _t @@ -3521,7 +3774,7 @@ class TurtleArtWindow(): return int(_l + (_w - iw) / 2), int(_t + (_h - ih) / 2) def _calc_w_h(self, name, spr): - """ Calculate new image size """ + ''' Calculate new image size ''' target_w = spr.label_safe_width() target_h = spr.label_safe_height() if name == '': @@ -3538,18 +3791,18 @@ class TurtleArtWindow(): return int(new_w), int(new_h) def _proto_skin(self, name, n, i): - """ Utility for calculating proto skin images """ + ''' 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 """ + ''' 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. """ + ''' 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) @@ -3567,6 +3820,14 @@ class TurtleArtWindow(): x, y = self._calc_image_offset('', blk.spr, w, h) blk.scale_image(x, y, w, h) + def _find_proto_name(self, name, label, palette='blocks'): + ''' Look for a protoblock with this name ''' + i = palette_name_to_index(palette) + for blk in self.palettes[i]: + if blk.name == name and blk.spr.labels[0] == label: + return True + return False + def _new_stack_block(self, name): ''' Add a stack block to the 'blocks' palette ''' if CURSOR in name: @@ -3619,7 +3880,7 @@ class TurtleArtWindow(): regenerate=True) def _prim_stack(self, x): - """ Process a named stack """ + ''' Process a named stack ''' if type(convert(x, float, False)) == float: if int(float(x)) == x: x = int(x) @@ -3634,7 +3895,7 @@ class TurtleArtWindow(): yield True def _prim_box(self, x): - """ Retrieve value from named box """ + ''' Retrieve value from named box ''' if type(convert(x, float, False)) == float: if int(float(x)) == x: x = int(x) @@ -3643,34 +3904,55 @@ class TurtleArtWindow(): except KeyError: raise logoerror("#emptybox") -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 dock_dx_dy(self, block1, dock1n, block2, dock2n): + ''' Find the distance between the dock points of two blocks. ''' + # Cannot dock a block to itself + if block1 == block2: + return NO_DOCK + dock1 = block1.docks[dock1n] + dock2 = block2.docks[dock2n] + # Dock types include flow, number, string, unavailable + # Dock directions: Flow: True -> in; False -> out + # Dock directions: Number: True -> out; False -> in + # Each dock point as an associated relative x, y position on its block + d1type, d1dir, d1x, d1y = dock1[0:4] + d2type, d2dir, d2x, d2y = dock2[0:4] + # Cannot connect an innie to an innie or an outie to an outie + if d1dir == d2dir: + return NO_DOCK + # Flow blocks can be inserted into the middle of a stack + if d2type is 'flow' and dock2n is 0: + if block1.connections is not None and \ + dock1n == len(block1.connections) - 1 and \ + block1.connections[dock1n] is not None: + self.inserting_block_mid_stack = True + elif block1.connections is not None and \ + block1.name in EXPANDABLE_FLOW and \ + dock1n == 2 and \ + block1.connections[dock1n] is not None: + self.inserting_block_mid_stack = True + # Only number blocks can be docked when the dock is not empty + elif 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 NO_DOCK + if block2.connections is not None and \ + dock2n < len(block2.connections) and \ + block2.connections[dock2n] is not None: + return NO_DOCK + # Only some dock types are interchangeable + if d1type != d2type: + # Some blocks will take strings or numbers + if block1.name in string_or_number_args: + if d2type == 'number' or d2type == 'string': + pass + # Some blocks will take content blocks + elif block1.name in CONTENT_ARGS: + if d2type in content_blocks: + pass + else: + return NO_DOCK + (b1x, b1y) = block1.spr.get_xy() + (b2x, b2y) = block2.spr.get_xy() + return ((b1x + d1x) - (b2x + d2x), (b1y + d1y) - (b2y + d2y)) -- cgit v0.9.1