diff options
Diffstat (limited to 'TurtleArt/tawindow.py')
-rw-r--r-- | TurtleArt/tawindow.py | 1038 |
1 files changed, 660 insertions, 378 deletions
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)) |