# -*- coding: utf-8 -*- #Copyright (c) 2007, Playful Invention Company #Copyright (c) 2008-10, Walter Bender #Copyright (c) 2009-10 Raúl Gutiérrez Segalés #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. import pygtk pygtk.require('2.0') import gtk import gobject import os import os.path from math import atan2, pi DEGTOR = 2*pi/360 from gettext import gettext as _ try: from sugar.graphics.objectchooser import ObjectChooser from sugar.datastore import datastore from sugar import profile except ImportError: pass from taconstants import HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE, \ PALETTE_NAMES, TITLEXY, MEDIA_SHAPES, STATUS_SHAPES, \ OVERLAY_SHAPES, TOOLBAR_SHAPES, TAB_LAYER, \ OVERLAY_LAYER, CATEGORY_LAYER, BLOCKS_WITH_SKIN, \ ICON_SIZE, PALETTES, PALETTE_SCALE, BOX_STYLE_MEDIA, \ PALETTE_WIDTH, MACROS, TOP_LAYER, BLOCK_LAYER, \ CONTENT_BLOCKS, DEFAULTS, SPECIAL_NAMES, HELP_STRINGS, \ CURSOR, EXPANDABLE, COLLAPSIBLE, RETURN, \ DEAD_DICTS, DEAD_KEYS, TEMPLATES, PYTHON_SKIN, \ PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK, OLD_NAMES, \ BOOLEAN_STYLE, BLOCK_NAMES from talogo import LogoCode, stop_logo from tacanvas import TurtleGraphics from tablock import Blocks, Block from taturtle import Turtles, Turtle from tautils import magnitude, get_load_name, get_save_name, data_from_file, \ data_to_file, round_int, get_id, get_pixbuf_from_journal, \ movie_media_type, audio_media_type, image_media_type, \ save_picture, save_svg, calc_image_size, get_path, \ reset_stack_arm, grow_stack_arm, find_sandwich_top, \ find_sandwich_bottom, restore_stack, collapse_stack, \ collapsed, collapsible, hide_button_hit, show_button_hit, \ arithmetic_check, xy, find_block_to_run, find_top_block, \ find_start_stack, find_group, find_blk_below, olpc_xo_1, \ dock_dx_dy from tasprite_factory import SVG, svg_str_to_pixbuf, svg_from_file from sprites import Sprites, Sprite class TurtleArtWindow(): """ TurtleArt Window class abstraction """ timeout_tag = [0] def __init__(self, win, path, parent=None, mycolors=None): self.win = None self.window = win self.path = path self.load_save_folder = os.path.join(path, 'samples') self.save_folder = None self.save_file_name = None self.window.set_flags(gtk.CAN_FOCUS) self.width = gtk.gdk.screen_width() self.height = gtk.gdk.screen_height() if parent is not None: parent.show_all() self.running_sugar = True self.activity = parent self.nick = profile.get_nick_name() else: self.window.show_all() self.running_sugar = False self.activity = None self.nick = None self._setup_events() self.keypress = "" self.keyvalue = 0 self.dead_key = "" self.area = self.window.window self.gc = self.area.new_gc() self.orientation = HORIZONTAL_PALETTE if olpc_xo_1(): self.lead = 1.0 self.scale = 0.67 if self.running_sugar and not self.activity.new_sugar_system: self.orientation = VERTICAL_PALETTE else: self.lead = 1.0 self.scale = 1.0 self.block_scale = BLOCK_SCALE self.trash_scale = 0.5 self.myblock = None self.nop = 'nop' self.loaded = 0 self.step_time = 0 self.hide = False self.palette = True self.coord_scale = 1 self.buddies = [] self.saved_string = '' self.dx = 0 self.dy = 0 self.media_shapes = {} self.cartesian = False self.polar = False self.overlay_shapes = {} self.toolbar_shapes = {} self.toolbar_offset = 0 self.status_spr = None self.status_shapes = {} self.toolbar_spr = None self.palette_sprs = [] self.palettes = [] self.palette_button = [] self.trash_index = PALETTE_NAMES.index('trash') self.trash_stack = [] self.selected_palette = None self.previous_palette = None self.selectors = [] self.selected_selector = None self.previous_selector = None self.selector_shapes = [] self.selected_blk = None self.selected_spr = None self.drag_group = None self.drag_turtle = 'move', 0, 0 self.drag_pos = 0, 0 self.block_list = Blocks(self.scale) self.sprite_list = Sprites(self.window, self.area, self.gc) self.turtles = Turtles(self.sprite_list) if mycolors == None: Turtle(self.turtles, 1) else: Turtle(self.turtles, 1, mycolors.split(',')) self.active_turtle = self.turtles.get_turtle(1) self.saving_svg = False self.svg_string = '' self.selected_turtle = None self.canvas = TurtleGraphics(self, self.width, self.height) self.titlex = -(self.canvas.width*TITLEXY[0])/(self.coord_scale*2) self.leftx = -(self.canvas.width*TITLEXY[0])/(self.coord_scale*2) self.rightx = 0 self.titley = (self.canvas.height*TITLEXY[1])/(self.coord_scale*2) self.topy = (self.canvas.height*(TITLEXY[1]-0.125))/(self.coord_scale*2) self.bottomy = 0 self.lc = LogoCode(self) self.saved_pictures = [] self._setup_misc() self._show_toolbar_palette(0, False) self.block_operation = '' def _setup_events(self): """ Register the events we listen to. """ self.window.add_events(gtk.gdk.BUTTON_PRESS_MASK) self.window.add_events(gtk.gdk.BUTTON_RELEASE_MASK) self.window.add_events(gtk.gdk.POINTER_MOTION_MASK) self.window.add_events(gtk.gdk.KEY_PRESS_MASK) self.window.connect("expose-event", self._expose_cb) self.window.connect("button-press-event", self._buttonpress_cb) self.window.connect("button-release-event", self._buttonrelease_cb) self.window.connect("motion-notify-event", self._move_cb) self.window.connect("key_press_event", self._keypress_cb) def _setup_misc(self): """ Misc. sprites for status, overlays, etc. """ # media blocks get positioned into other blocks for _name in MEDIA_SHAPES: if _name[0:7] == 'journal' and not self.running_sugar: file_name = 'file'+_name[7:] else: file_name = _name self.media_shapes[_name] = svg_str_to_pixbuf(svg_from_file( "%s/images/%s.svg" % (self.path, file_name))) for i, _name in enumerate(STATUS_SHAPES): self.status_shapes[_name] = svg_str_to_pixbuf(svg_from_file( "%s/images/%s.svg" % (self.path, _name))) self.status_spr = Sprite(self.sprite_list, 0, self.height-200, self.status_shapes['status']) self.status_spr.hide() self.status_spr.type = 'status' for _name in OVERLAY_SHAPES: self.overlay_shapes[_name] = Sprite(self.sprite_list, int(self.width/2-600), int(self.height/2-450), svg_str_to_pixbuf(svg_from_file( "%s/images/%s.svg" % (self.path, _name)))) self.overlay_shapes[_name].hide() self.overlay_shapes[_name].type = 'overlay' if not self.running_sugar: offset = self.width-55*len(TOOLBAR_SHAPES) for i, _name in enumerate(TOOLBAR_SHAPES): self.toolbar_shapes[_name] = Sprite(self.sprite_list, i*55+offset, 0, svg_str_to_pixbuf( svg_from_file("%s/icons/%s.svg" % (self.path, _name)))) self.toolbar_shapes[_name].set_layer(TAB_LAYER) self.toolbar_shapes[_name].name = _name self.toolbar_shapes[_name].type = 'toolbar' self.toolbar_shapes['stopiton'].hide() def sharing(self): """ Is a chattube available for sharing? """ if self.running_sugar and hasattr(self.activity, 'chattube') and\ self.activity.chattube is not None: return True return False def _expose_cb(self, win, event): """ Repaint """ self.sprite_list.redraw_sprites() # self.canvas.cr_expose(event) return True def eraser_button(self): """ Eraser_button (hide status block when clearing the screen.) """ if self.status_spr is not None: self.status_spr.hide() self.lc.prim_clear() self.display_coordinates() def run_button(self, time): """ Run turtle! """ if self.running_sugar: self.activity.recenter() # Look for a 'start' block for blk in self.just_blocks(): if find_start_stack(blk): self.step_time = time print "running stack starting from %s" % (blk.name) self._run_stack(blk) return # If there is no 'start' block, run stacks that aren't 'def action' for blk in self.just_blocks(): if find_block_to_run(blk): self.step_time = time print "running stack starting from %s" % (blk.name) self._run_stack(blk) return def stop_button(self): """ Stop button """ stop_logo(self) def set_userdefined(self): """ Change icon for user-defined blocks after Python code is loaded. """ for blk in self.just_blocks(): if blk.name in PYTHON_SKIN: x, y = self._calc_image_offset('pythonon', blk.spr) blk.set_image(self.media_shapes['pythonon'], x, y) self._resize_skin(blk) self.nop = 'pythonloaded' def set_fullscreen(self): """ Enter fullscreen mode """ if self.running_sugar: self.activity.fullscreen() self.activity.recenter() def set_cartesian(self, flag): """ Turn on/off Cartesian coordinates """ if flag: if self.coord_scale == 1: self.overlay_shapes['Cartesian_labeled'].set_layer( OVERLAY_LAYER) else: self.overlay_shapes['Cartesian'].set_layer(OVERLAY_LAYER) self.cartesian = True else: if self.coord_scale == 1: self.overlay_shapes['Cartesian_labeled'].hide() else: self.overlay_shapes['Cartesian'].hide() self.cartesian = False def set_polar(self, flag): """ Turn on/off polar coordinates """ if flag: self.overlay_shapes['polar'].set_layer(OVERLAY_LAYER) self.polar = True else: self.overlay_shapes['polar'].hide() self.polar = False def hideshow_button(self): """ Hide/show button """ if not self.hide: for blk in self.just_blocks(): blk.spr.hide() self.hide_palette() self.hide = True else: for blk in self.just_blocks(): if blk.status != 'collapsed': blk.spr.set_layer(BLOCK_LAYER) self.show_palette() if self.activity is not None and self.activity.new_sugar_system: self.activity.palette_buttons[0].set_icon( PALETTE_NAMES[0] + 'on') self.hide = False self.canvas.canvas.inval() def hideshow_palette(self, state): """ Hide or show palette """ if not state: self.palette = False if self.running_sugar: self.activity.do_hidepalette() self.hide_palette() else: self.palette = True if self.running_sugar: self.activity.do_showpalette() self.show_palette() def show_palette(self, n=0): """ Show palette """ self._show_toolbar_palette(n) self.palette_button[self.orientation].set_layer(TAB_LAYER) self.palette_button[2].set_layer(TAB_LAYER) if self.activity is None or not self.activity.new_sugar_system: self.toolbar_spr.set_layer(CATEGORY_LAYER) self.palette = True def hide_palette(self): """ Hide the palette. """ self._hide_toolbar_palette() self.palette_button[self.orientation].hide() self.palette_button[2].hide() if self.activity is None or not self.activity.new_sugar_system: self.toolbar_spr.hide() self.palette = False def hideblocks(self): """ Callback from 'hide blocks' block """ self.hide = False self.hideshow_button() if self.running_sugar: self.activity.do_hide() def showblocks(self): """ Callback from 'show blocks' block """ self.hide = True self.hideshow_button() if self.running_sugar: self.activity.do_show() def resize_blocks(self): """ Resize all of the blocks """ # We need to restore collapsed stacks before resizing. for blk in self.just_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 self.just_blocks(): blk.rescale(self.block_scale) for blk in self.just_blocks(): self._adjust_dock_positions(blk) # Re-collapsed stacks after resizing. for blk in self.just_blocks(): if collapsed(blk): collapse_stack(find_sandwich_top(blk)) for blk in self.just_blocks(): if blk.name == 'sandwichtop': grow_stack_arm(blk) # Resize the skins on some blocks: media content and Python for blk in self.just_blocks(): if blk.name in BLOCKS_WITH_SKIN: self._resize_skin(blk) def _show_toolbar_palette(self, n, init_only=False): """ Show the toolbar palettes, creating them on init_only """ if (self.activity is None or not self.activity.new_sugar_system) and\ self.selectors == []: # Create the selectors svg = SVG() x, y = 50, 0 for i, name in enumerate(PALETTE_NAMES): a = svg_str_to_pixbuf(svg_from_file("%s/icons/%soff.svg" % ( self.path, name))) b = svg_str_to_pixbuf(svg_from_file("%s/icons/%son.svg" % ( self.path, name))) self.selector_shapes.append([a, b]) self.selectors.append(Sprite(self.sprite_list, x, y, a)) self.selectors[i].type = 'selector' self.selectors[i].name = name self.selectors[i].set_layer(TAB_LAYER) w = self.selectors[i].get_dimensions()[0] x += int(w) # Create the toolbar background self.toolbar_offset = ICON_SIZE self.toolbar_spr = Sprite(self.sprite_list, 0, 0, svg_str_to_pixbuf(svg.toolbar(self.width, ICON_SIZE))) self.toolbar_spr.type = 'toolbar' self.toolbar_spr.set_layer(CATEGORY_LAYER) if self.palette_sprs == []: # Create the empty palettes if len(self.palettes) == 0: for i in range(len(PALETTES)): self.palettes.append([]) # Create empty palette backgrounds for i in PALETTE_NAMES: self.palette_sprs.append([None, None]) # Create the palette orientation button self.palette_button.append(Sprite(self.sprite_list, 0, self.toolbar_offset, svg_str_to_pixbuf(svg_from_file( "%s/images/palettehorizontal.svg" %(self.path))))) self.palette_button.append(Sprite(self.sprite_list, 0, self.toolbar_offset, svg_str_to_pixbuf(svg_from_file( "%s/images/palettevertical.svg" % (self.path))))) self.palette_button[0].name = _('orientation') self.palette_button[1].name = _('orientation') self.palette_button[0].type = 'palette' self.palette_button[1].type = 'palette' self.palette_button[self.orientation].set_layer(TAB_LAYER) self.palette_button[1-self.orientation].hide() # Create the palette next button self.palette_button.append(Sprite(self.sprite_list, 16, self.toolbar_offset, svg_str_to_pixbuf(svg_from_file( "%s/images/palettenext.svg" %(self.path))))) self.palette_button[2].name = _('next') self.palette_button[2].type = 'palette' self.palette_button[2].set_layer(TAB_LAYER) if init_only: return # Hide the previously displayed palette self._hide_previous_palette() self.selected_palette = n self.previous_palette = self.selected_palette if self.activity is None or not self.activity.new_sugar_system: self.selected_selector = self.selectors[n] # Make sure all of the selectors are visible. self.selectors[n].set_shape(self.selector_shapes[n][1]) for i in range(len(PALETTES)): self.selectors[i].set_layer(TAB_LAYER) # Show the palette with the current orientation. if self.palette_sprs[n][self.orientation] is not None: self.palette_sprs[n][self.orientation].set_layer(CATEGORY_LAYER) if self.palettes[n] == []: # Create 'proto' blocks for each palette entry for i, name in enumerate(PALETTES[n]): self.palettes[n].append(Block(self.block_list, self.sprite_list, name, 0, 0, 'proto', [], PALETTE_SCALE)) self.palettes[n][i].spr.set_layer(TAB_LAYER) self.palettes[n][i].unhighlight() # Some proto blocks get a skin. if name in BOX_STYLE_MEDIA: self._proto_skin(name+'small', n, i) elif name[:8] == 'template': self._proto_skin(name[8:], n, i) elif name[:7] == 'picture': self._proto_skin(name[7:], n, i) elif name in PYTHON_SKIN: self._proto_skin('pythonsmall', n, i) self._layout_palette(n) for blk in self.palettes[n]: blk.spr.set_layer(TAB_LAYER) if n == self.trash_index: for blk in self.trash_stack: for gblk in find_group(blk): if gblk.status != 'collapsed': gblk.spr.set_layer(TAB_LAYER) def _hide_toolbar_palette(self): """ Hide the toolbar palettes """ self._hide_previous_palette() if self.activity is None or not self.activity.new_sugar_system: # Hide the selectors for i in range(len(PALETTES)): self.selectors[i].hide() elif self.selected_palette is not None: self.activity.palette_buttons[self.selected_palette].set_icon( PALETTE_NAMES[self.selected_palette] + 'off') self.selected_palette = None self.previous_palette = None def _hide_previous_palette(self): """ Hide just the previously viewed toolbar palette """ # Hide previous palette if self.previous_palette is not None: for i in range(len(PALETTES[self.previous_palette])): self.palettes[self.previous_palette][i].spr.hide() self.palette_sprs[self.previous_palette][ self.orientation].hide() if self.activity is None or not self.activity.new_sugar_system: self.selectors[self.previous_palette].set_shape( self.selector_shapes[self.previous_palette][0]) elif self.previous_palette is not None and \ self.previous_palette != self.selected_palette: self.activity.palette_buttons[self.previous_palette].set_icon( PALETTE_NAMES[self.previous_palette] + 'off') if self.previous_palette == self.trash_index: for blk in self.trash_stack: for gblk in find_group(blk): gblk.spr.hide() def _horizontal_layout(self, x, y, blocks): """ Position prototypes in a horizontal palette. """ _max_w = 0 for blk in blocks: _w, _h = self._width_and_height(blk) if y + _h > PALETTE_HEIGHT + self.toolbar_offset: x += int(_max_w+3) y = self.toolbar_offset + 3 _max_w = 0 (_bx, _by) = blk.spr.get_xy() _dx = x-_bx _dy = y-_by for g in find_group(blk): g.spr.move_relative((int(_dx), int(_dy))) y += int(_h+3) if _w > _max_w: _max_w = _w return x, y, _max_w def _vertical_layout(self, x, y, blocks): """ Position prototypes in a vertical palette. """ _row = [] _row_w = 0 _max_h = 0 for _b in blocks: _w, _h = self._width_and_height(_b) if x + _w > PALETTE_WIDTH: # Recenter row. _dx = int((PALETTE_WIDTH-_row_w)/2) for _r in _row: for _g in find_group(_r): _g.spr.move_relative((_dx, 0)) _row = [] _row_w = 0 x = 4 y += int(_max_h+3) _max_h = 0 _row.append(_b) _row_w += (4 + _w) (_bx, _by) = _b.spr.get_xy() _dx = int(x - _bx) _dy = int(y - _by) for _g in find_group(_b): _g.spr.move_relative((_dx, _dy)) x += int(_w + 4) if _h > _max_h: _max_h = _h # Recenter last row. _dx = int((PALETTE_WIDTH - _row_w)/2) for _r in _row: for _g in find_group(_r): _g.spr.move_relative((_dx, 0)) return x, y, _max_h def _layout_palette(self, n): """ Layout prototypes in a palette. """ if n is not None: if self.orientation == HORIZONTAL_PALETTE: _x, _y = 20, self.toolbar_offset + 5 _x, _y, _max = self._horizontal_layout(_x, _y, self.palettes[n]) if n == self.trash_index: _x, _y, _max = self._horizontal_layout(_x+_max, _y, self.trash_stack) _w = _x + _max + 25 if self.palette_sprs[n][self.orientation] is None: svg = SVG() self.palette_sprs[n][self.orientation] = Sprite( self.sprite_list, 0, self.toolbar_offset, svg_str_to_pixbuf(svg.palette(_w, PALETTE_HEIGHT))) self.palette_sprs[n][self.orientation].type = 'category' if n == PALETTE_NAMES.index('trash'): svg = SVG() self.palette_sprs[n][self.orientation].set_shape( svg_str_to_pixbuf(svg.palette(_w, PALETTE_HEIGHT))) self.palette_button[2].move((_w-20, self.toolbar_offset)) else: _x, _y = 5, self.toolbar_offset + 15 _x, _y, _max = self._vertical_layout(_x, _y, self.palettes[n]) if n == PALETTE_NAMES.index('trash'): _x, _y, _max = self._vertical_layout(_x, _y + _max, self.trash_stack) _h = _y + _max + 25 - self.toolbar_offset if self.palette_sprs[n][self.orientation] is None: svg = SVG() self.palette_sprs[n][self.orientation] = \ Sprite(self.sprite_list, 0, self.toolbar_offset, svg_str_to_pixbuf(svg.palette(PALETTE_WIDTH, _h))) self.palette_sprs[n][self.orientation].type = 'category' if n == PALETTE_NAMES.index('trash'): svg = SVG() self.palette_sprs[n][self.orientation].set_shape( svg_str_to_pixbuf(svg.palette(PALETTE_WIDTH, _h))) self.palette_button[2].move((PALETTE_WIDTH-20, self.toolbar_offset)) self.palette_sprs[n][self.orientation].set_layer(CATEGORY_LAYER) def _buttonpress_cb(self, win, event): """ Button press """ self.window.grab_focus() x, y = xy(event) self.button_press(event.get_state()>k.gdk.CONTROL_MASK, x, y) return True def button_press(self, mask, x, y, verbose=False): if verbose: print "processing remote button press: %d, %d" % (x, y) self.block_operation = 'click' # Unselect things that may have been selected earlier if self.selected_blk is not None: self._unselect_block() self.selected_turtle = None # Always hide the status layer on a click if self.status_spr is not None: self.status_spr.hide() # Find out what was clicked spr = self.sprite_list.find_sprite((x, y)) self.dx = 0 self.dy = 0 if spr is None: return True self.selected_spr = spr # From the sprite at x, y, look for a corresponding block blk = self.block_list.spr_to_block(spr) if blk is not None: if blk.type == 'block': self.selected_blk = blk self._block_pressed(x, y, blk) elif blk.type == 'trash': self._restore_from_trash(find_top_block(blk)) elif blk.type == 'proto': if blk.name == 'restoreall': self._restore_all_from_trash() elif blk.name == 'restore': self._restore_latest_from_trash() elif blk.name == 'empty': self._empty_trash() elif MACROS.has_key(blk.name): self._new_macro(blk.name, x + 20, y + 20) else: blk.highlight() self._new_block(blk.name, x, y) blk.unhighlight() return True # Next, look for a turtle t = self.turtles.spr_to_turtle(spr) if t is not None: self.selected_turtle = t self.canvas.set_turtle(self.turtles.get_turtle_key(t)) self._turtle_pressed(x, y) return True # Finally, check for anything else if hasattr(spr, 'type'): if spr.type == "canvas": pass # spr.set_layer(CANVAS_LAYER) elif spr.type == 'selector': self._select_category(spr) elif spr.type == 'category': if hide_button_hit(spr, x, y): self.hideshow_palette(False) elif spr.type == 'palette': if spr.name == _('next'): i = self.selected_palette + 1 if i == len(PALETTE_NAMES): i = 0 if self.activity is None or \ not self.activity.new_sugar_system: self._select_category(self.selectors[i]) else: if self.selected_palette is not None: self.activity.palette_buttons[ self.selected_palette].set_icon( PALETTE_NAMES[self.selected_palette] + 'off') self.activity.palette_buttons[i].set_icon( PALETTE_NAMES[i] + 'on') self.show_palette(i) else: self.orientation = 1 - self.orientation self.palette_button[self.orientation].set_layer(TAB_LAYER) self.palette_button[1 - self.orientation].hide() self.palette_sprs[self.selected_palette][ 1 - self.orientation].hide() self._layout_palette(self.selected_palette) self.show_palette(self.selected_palette) elif spr.type == 'toolbar': self._select_toolbar_button(spr) return True def _select_category(self, spr): """ Select a category from the toolbar (old Sugar systems only). """ i = self.selectors.index(spr) spr.set_shape(self.selector_shapes[i][1]) if self.selected_selector is not None: j = self.selectors.index(self.selected_selector) if i == j: return self.selected_selector.set_shape(self.selector_shapes[j][0]) self.previous_selector = self.selected_selector self.selected_selector = spr self.show_palette(i) def _select_toolbar_button(self, spr): """ Select a toolbar button (Used when not running Sugar). """ if not hasattr(spr, 'name'): return if spr.name == 'run-fastoff': self.lc.trace = 0 self.run_button(0) elif spr.name == 'run-slowoff': self.lc.trace = 0 self.run_button(3) elif spr.name == 'debugoff': self.lc.trace = 1 self.run_button(6) elif spr.name == 'stopiton': self.stop_button() self.toolbar_shapes['stopiton'].hide() elif spr.name == 'eraseron': self.eraser_button() elif spr.name == 'hideshowoff': self.hideshow_button() def _put_in_trash(self, blk, x=0, y=0): """ Put a group of blocks into the trash. """ self.trash_stack.append(blk) group = find_group(blk) for gblk in group: if gblk.status == 'collapsed': # Collapsed stacks are restored for rescaling # and then recollapsed after they are moved to the trash. bot = find_sandwich_bottom(gblk) if collapsed(bot): dy = bot.values[0] restore_stack(find_sandwich_top(gblk)) bot.values[0] = dy gblk.type = 'trash' gblk.rescale(self.trash_scale) blk.spr.move((x, y)) for gblk in group: self._adjust_dock_positions(gblk) # Re-collapsing any stacks we had restored for scaling for gblk in group: if collapsed(gblk): collapse_stack(find_sandwich_top(gblk)) # And resize any skins. for gblk in group: if gblk.name in BLOCKS_WITH_SKIN: self._resize_skin(gblk) # self.show_palette(self.trash_index) if self.selected_palette != self.trash_index: for gblk in group: gblk.spr.hide() def _restore_all_from_trash(self): """ Restore all the blocks in the trash can. """ for blk in self.block_list.list: if blk.type == 'trash': self._restore_from_trash(blk) def _restore_latest_from_trash(self): """ Restore most recent blocks from the trash can. """ if len(self.trash_stack) == 0: return self._restore_from_trash(self.trash_stack[len(self.trash_stack) - 1]) def _restore_from_trash(self, blk): group = find_group(blk) for gblk in group: gblk.rescale(self.block_scale) gblk.spr.set_layer(BLOCK_LAYER) x, y = gblk.spr.get_xy() if self.orientation == 0: gblk.spr.move((x, y + PALETTE_HEIGHT + self.toolbar_offset)) else: gblk.spr.move((x + PALETTE_WIDTH, y)) gblk.type = 'block' for gblk in group: self._adjust_dock_positions(gblk) # If the stack had been collapsed before going into the trash, # collapse it again now. for gblk in group: if collapsed(gblk): collapse_stack(find_sandwich_top(gblk)) # And resize any skins. for gblk in group: if gblk.name in BLOCKS_WITH_SKIN: self._resize_skin(gblk) self.trash_stack.remove(blk) def _empty_trash(self): """ Permanently remove all blocks presently in the trash can. """ for blk in self.block_list.list: if blk.type == 'trash': blk.type = 'deleted' blk.spr.hide() self.trash_stack = [] def _in_the_trash(self, x, y): """ Is x, y over the trash can? """ """ if self.selected_palette == self.trash_index and \ self.palette_sprs[self.trash_index][self.orientation].hit((x, y)): return True """ if self.selected_palette is not None and\ self.palette_sprs[self.selected_palette][self.orientation].hit( (x, y)): return True return False def _block_pressed(self, x, y, blk): """ Block pressed """ if blk is not None: blk.highlight() self._disconnect(blk) self.drag_group = find_group(blk) (sx, sy) = blk.spr.get_xy() self.drag_pos = x-sx, y-sy for blk in self.drag_group: if blk.status != 'collapsed': blk.spr.set_layer(TOP_LAYER) self.saved_string = blk.spr.labels[0] def _unselect_block(self): """ Unselect block """ # After unselecting a 'number' block, we need to check its value if self.selected_blk.name == 'number': self._number_check() elif self.selected_blk.name == 'string': self._string_check() self.selected_blk.unhighlight() self.selected_blk = None def _new_block(self, name, x, y): """ Make a new block. """ if name in CONTENT_BLOCKS: newblk = Block(self.block_list, self.sprite_list, name, x - 20, y - 20, 'block', DEFAULTS[name], self.block_scale) else: newblk = Block(self.block_list, self.sprite_list, name, x - 20, y - 20, 'block', [], self.block_scale) # Add a 'skin' to some blocks if name in PYTHON_SKIN: if self.nop == 'pythonloaded': self._block_skin('pythonon', newblk) else: self._block_skin('pythonoff', newblk) elif name in BOX_STYLE_MEDIA: self._block_skin(name+'off', newblk) newspr = newblk.spr newspr.set_layer(TOP_LAYER) self.drag_pos = 20, 20 newblk.connections = [None]*len(newblk.docks) if DEFAULTS.has_key(newblk.name): for i, argvalue in enumerate(DEFAULTS[newblk.name]): # skip the first dock position since it is always a connector dock = newblk.docks[i + 1] argname = dock[0] if argname == 'unavailable': continue if argname == 'media': argname = 'journal' elif argname == 'number' and \ (type(argvalue) is str or type(argvalue) is unicode): argname = 'string' elif argname == 'bool': argname = argvalue elif argname == 'flow': argname = argvalue (sx, sy) = newspr.get_xy() if argname is not None: if argname in CONTENT_BLOCKS: argblk = Block(self.block_list, self.sprite_list, argname, 0, 0, 'block', [argvalue], self.block_scale) else: argblk = Block(self.block_list, self.sprite_list, argname, 0, 0, 'block', [], self.block_scale) argdock = argblk.docks[0] nx = sx + dock[2] - argdock[2] ny = sy + dock[3] - argdock[3] if argname == 'journal': self._block_skin('journaloff', argblk) argblk.spr.move((nx, ny)) argblk.spr.set_layer(TOP_LAYER) argblk.connections = [newblk, None] newblk.connections[i + 1] = argblk self.drag_group = find_group(newblk) self.block_operation = 'new' def _new_macro(self, name, x, y): """ Create a "macro" (predefined stack of blocks). """ macro = MACROS[name] macro[0][2] = x macro[0][3] = y top = self.process_data(macro) self.block_operation = 'new' self._check_collapsibles(top) self.drag_group = find_group(top) def process_data(self, data): """ Process data (from a macro, a file, or the clipboard). """ # Create the blocks (or turtle). blocks = [] for blk in data: if not self._found_a_turtle(blk): blocks.append(self.load_block(blk)) # Make the connections. for i in range(len(blocks)): cons = [] # Normally, it is simply a matter of copying the connections. if blocks[i].connections == None: for c in data[i][4]: if c is None: cons.append(None) else: cons.append(blocks[c]) elif blocks[i].connections == 'check': # Corner case to convert old-style boolean and arithmetic blocks cons.append(None) # Add an extra connection. for c in data[i][4]: if c is None: cons.append(None) else: cons.append(blocks[c]) # If the boolean op was connected, readjust the plumbing. if blocks[i].name in BOOLEAN_STYLE: if data[i][4][0] is not None: c = data[i][4][0] cons[0] = blocks[data[c][4][0]] c0 = data[c][4][0] for j, cj in enumerate(data[c0][4]): if cj == c: blocks[c0].connections[j] = blocks[i] if c < i: blocks[c].connections[0] = blocks[i] blocks[c].connections[3] = None else: # Connection was to a block we haven't seen yet. print "WARNING: dock check couldn't see the future" else: if data[i][4][0] is not None: c = data[i][4][0] cons[0] = blocks[data[c][4][0]] c0 = data[c][4][0] for j, cj in enumerate(data[c0][4]): if cj == c: blocks[c0].connections[j] = blocks[i] if c < i: blocks[c].connections[0] = blocks[i] blocks[c].connections[1] = None else: # Connection was to a block we haven't seen yet. print "WARNING: dock check couldn't see the future" else: print "WARNING: unknown connection state %s" % \ (str(blocks[i].connections)) blocks[i].connections = cons[:] # Block sizes and shapes may have changed. for blk in blocks: self._adjust_dock_positions(blk) # Look for any stacks that need to be collapsed or sandwiched for blk in blocks: if collapsed(blk): collapse_stack(find_sandwich_top(blk)) elif blk.name == 'sandwichbottom' and collapsible(blk): blk.svg.set_hide(True) blk.svg.set_show(False) blk.refresh() grow_stack_arm(find_sandwich_top(blk)) if len(blocks) > 0: return blocks[0] else: return None def _adjust_dock_positions(self, blk): """ Adjust the dock x, y positions """ (sx, sy) = blk.spr.get_xy() for i, c in enumerate(blk.connections): if i > 0 and c is not None: bdock = blk.docks[i] for j in range(len(c.docks)): if c.connections[j] == blk: cdock = c.docks[j] nx = sx + bdock[2] - cdock[2] ny = sy + bdock[3] - cdock[3] c.spr.move((nx, ny)) self._adjust_dock_positions(c) def _turtle_pressed(self, x, y): """ Turtle pressed """ (tx, ty) = self.selected_turtle.get_xy() dx = x - tx - 30 dy = y - ty - 30 if (dx * dx) + (dy * dy) > 200: self.drag_turtle = ('turn', self.canvas.heading - atan2(dy, dx)/DEGTOR, 0) else: self.drag_turtle = ('move', x - tx, y - ty) def _move_cb(self, win, event): """ Mouse move """ x, y = xy(event) self._mouse_move(x, y) return True def _mouse_move(self, x, y, verbose=False, mdx=0, mdy=0): if verbose: print "processing remote mouse move: %d, %d" % (x, y) self.block_operation = 'move' # First, check to see if we are dragging or rotating a turtle. if self.selected_turtle is not None: dtype, dragx, dragy = self.drag_turtle (sx, sy) = self.selected_turtle.get_xy() if dtype == 'move': if mdx != 0 or mdy != 0: dx, dy = mdx, mdy else: dx = x - dragx - sx dy = y - dragy - sy self.selected_turtle.move((sx + dx, sy + dy)) else: if mdx != 0 or mdy != 0: dx = mdx dy = mdy else: dx = x - sx - 30 dy = y - sy - 30 self.canvas.seth(int(dragx + atan2(dy, dx)/DEGTOR + 5)/10 * 10) # If we are hoving, show popup help. elif self.drag_group is None: self._show_popup(x, y) return # If we have a stack of blocks selected, move them. elif self.drag_group[0] is not None: blk = self.drag_group[0] # Don't move a bottom blk is the stack is collapsed if collapsed(blk): return self.selected_spr = blk.spr dragx, dragy = self.drag_pos if mdx != 0 or mdy != 0: dx = mdx dy = mdy else: (sx, sy) = blk.spr.get_xy() dx = x - dragx - sx dy = y - dragy - sy # Take no action if there was a move of 0,0. if dx == 0 and dy == 0: return self.drag_group = find_group(blk) # Prevent blocks from ending up with a negative x... for gblk in self.drag_group: (bx, by) = gblk.spr.get_xy() if bx + dx < 0: dx += -(bx + dx) """ # ...or under the palette. if self.selected_palette is not None and\ self.selected_palette != self.trash_index: w, h = self.palette_sprs[self.selected_palette][ self.orientation].get_dimensions() if self.orientation == HORIZONTAL_PALETTE: if bx < w and\ by+dy < self.toolbar_offset+PALETTE_HEIGHT: dy += -(by+dy)+self.toolbar_offset+PALETTE_HEIGHT else: if by < h+self.toolbar_offset and bx+dx < PALETTE_WIDTH: dx += -(bx+dx)+PALETTE_WIDTH """ # Move the stack. for gblk in self.drag_group: (bx, by) = gblk.spr.get_xy() gblk.spr.move((bx + dx, by + dy)) if mdx != 0 or mdy != 0: dx = 0 dy = 0 else: self.dx += dx self.dy += dy def _show_popup(self, x, y): """ Let's help our users by displaying a little help. """ spr = self.sprite_list.find_sprite((x, y)) blk = self.block_list.spr_to_block(spr) if spr and blk is not None: if self.timeout_tag[0] == 0: self.timeout_tag[0] = self._do_show_popup(blk.name) self.selected_spr = spr else: if self.timeout_tag[0] > 0: try: gobject.source_remove(self.timeout_tag[0]) self.timeout_tag[0] = 0 except: self.timeout_tag[0] = 0 elif spr and hasattr(spr,'type') and (spr.type == 'selector' or\ spr.type == 'palette' or\ spr.type == 'toolbar'): if self.timeout_tag[0] == 0 and hasattr(spr, 'name'): self.timeout_tag[0] = self._do_show_popup(spr.name) self.selected_spr = spr else: if self.timeout_tag[0] > 0: try: gobject.source_remove(self.timeout_tag[0]) self.timeout_tag[0] = 0 except: self.timeout_tag[0] = 0 else: if self.timeout_tag[0] > 0: try: gobject.source_remove(self.timeout_tag[0]) self.timeout_tag[0] = 0 except: self.timeout_tag[0] = 0 def _do_show_popup(self, block_name): """ Fetch the help text and display it. """ if SPECIAL_NAMES.has_key(block_name): block_name_s = SPECIAL_NAMES[block_name] elif BLOCK_NAMES.has_key(block_name): block_name_s = BLOCK_NAMES[block_name][0] elif block_name in TOOLBAR_SHAPES: block_name_s = '' else: block_name_s = _(block_name) if HELP_STRINGS.has_key(block_name): if block_name_s == '': label = HELP_STRINGS[block_name] else: label = block_name_s + ": " + HELP_STRINGS[block_name] else: label = block_name_s if self.running_sugar: self.activity.hover_help_label.set_text(label) self.activity.hover_help_label.show() else: self.win.set_title(_("Turtle Art") + " — " + label) return 0 def _buttonrelease_cb(self, win, event): """ Button release """ x, y = xy(event) self.button_release(x, y) return True def button_release(self, x, y, verbose=False): # We may have been moving the turtle if self.selected_turtle is not None: (tx, ty) = self.selected_turtle.get_xy() (cx, cy) = self.canvas.canvas.get_xy() # self.canvas.xcor = tx - self.canvas.canvas._width/2 + 30 - cx # self.canvas.ycor = self.canvas.canvas._height/2 - ty - 30 + cy self.canvas.xcor = tx - self.canvas.width/2 + 30 - cx self.canvas.ycor = self.canvas.height/2 - ty - 30 + cy self.canvas.move_turtle() if self.running_sugar: self.display_coordinates() self.selected_turtle = None return # If we don't have a group of blocks, then there is nothing to do. if self.drag_group == None: return blk = self.drag_group[0] # Remove blocks by dragging them onto the trash palette. if self.block_operation == 'move' and self._in_the_trash(x, y): self._put_in_trash(blk, x, y) self.drag_group = None return # Pull a stack of new blocks off of the category palette. if self.block_operation == 'new': for gblk in self.drag_group: (bx, by) = gblk.spr.get_xy() if self.orientation == 0: gblk.spr.move((bx+20, by+PALETTE_HEIGHT+self.toolbar_offset)) else: gblk.spr.move((bx+PALETTE_WIDTH, by+20)) # Look to see if we can dock the current stack. self._snap_to_dock() self._check_collapsibles(blk) for gblk in self.drag_group: if gblk.status != 'collapsed': gblk.spr.set_layer(BLOCK_LAYER) self.drag_group = None # Find the block we clicked on and process it. if self.block_operation == 'click': self._click_block(x, y) def _click_block(self, x, y): """ Click block """ blk = self.block_list.spr_to_block(self.selected_spr) if blk is None: return self.selected_blk = blk if blk.name == 'number' or blk.name == 'string': self.saved_string = blk.spr.labels[0] blk.spr.labels[0] += CURSOR elif blk.name in BOX_STYLE_MEDIA: self._import_from_journal(self.selected_blk) if blk.name == 'journal' and self.running_sugar: self._load_description_block(blk) elif blk.name == 'identity2' or blk.name == 'hspace': group = find_group(blk) if hide_button_hit(blk.spr, x, y): dx = blk.reset_x() elif show_button_hit(blk.spr, x, y): dx = 20 blk.expand_in_x(dx) else: dx = 0 for gblk in group: if gblk != blk: gblk.spr.move_relative((dx * blk.scale, 0)) elif blk.name == 'vspace': group = find_group(blk) if hide_button_hit(blk.spr, x, y): dy = blk.reset_y() elif show_button_hit(blk.spr, x, y): dy = 20 blk.expand_in_y(dy) else: dy = 0 for gblk in group: if gblk != blk: gblk.spr.move_relative((0, dy * blk.scale)) grow_stack_arm(find_sandwich_top(blk)) elif blk.name in EXPANDABLE or blk.name == 'nop': if show_button_hit(blk.spr, x, y): n = len(blk.connections) group = find_group(blk.connections[n-1]) if blk.name == 'myfunc1arg': blk.spr.labels[1] = 'f(x, y)' blk.spr.labels[2] = ' ' dy = blk.add_arg() blk.primitive = 'myfunction2' blk.name = 'myfunc2arg' elif blk.name == 'myfunc2arg': blk.spr.labels[1] = 'f(x, y, z)' dy = blk.add_arg(False) blk.primitive = 'myfunction3' blk.name = 'myfunc3arg' elif blk.name == 'userdefined': dy = blk.add_arg() blk.primitive = 'userdefined2' blk.name = 'userdefined2args' elif blk.name == 'userdefined2args': dy = blk.add_arg(False) blk.primitive = 'userdefined3' blk.name = 'userdefined3args' else: dy = blk.add_arg() for gblk in group: gblk.spr.move_relative((0, dy)) blk.connections.append(blk.connections[n-1]) argname = blk.docks[n-1][0] argvalue = DEFAULTS[blk.name][len(DEFAULTS[blk.name])-1] argblk = Block(self.block_list, self.sprite_list, argname, 0, 0, 'block', [argvalue], self.block_scale) argdock = argblk.docks[0] (bx, by) = blk.spr.get_xy() nx = bx + blk.docks[n - 1][2] - argdock[2] ny = by + blk.docks[n - 1][3] - argdock[3] argblk.spr.move((nx, ny)) argblk.spr.set_layer(TOP_LAYER) argblk.connections = [blk, None] blk.connections[n - 1] = argblk grow_stack_arm(find_sandwich_top(blk)) elif blk.name in PYTHON_SKIN and self.myblock == None: self._import_py() else: self._run_stack(blk) elif blk.name in COLLAPSIBLE: top = find_sandwich_top(blk) if collapsed(blk): restore_stack(top) elif top is not None: collapse_stack(top) else: self._run_stack(blk) def _check_collapsibles(self, blk): """ Check the state of collapsible blocks upon change in dock state. """ group = find_group(blk) for gblk in group: if gblk.name in COLLAPSIBLE: if collapsed(gblk): gblk.svg.set_show(True) gblk.svg.set_hide(False) reset_stack_arm(find_sandwich_top(gblk)) elif collapsible(gblk): gblk.svg.set_hide(True) gblk.svg.set_show(False) grow_stack_arm(find_sandwich_top(gblk)) else: gblk.svg.set_hide(False) gblk.svg.set_show(False) # Ouch: When you tear off the sandwich bottom, you # no longer have access to the group with the sandwich top # so check them all. for b in self.just_blocks(): if b.name == 'sandwichtop': if find_sandwich_bottom(b) is None: reset_stack_arm(b) gblk.refresh() def _run_stack(self, blk): """ Run a stack of blocks. """ if blk is None: return self.lc.ag = None top = find_top_block(blk) self.lc.run_blocks(top, self.just_blocks(), True) gobject.idle_add(self.lc.doevalstep) def _snap_to_dock(self): """ Snap a block to the dock of another block. """ my_block = self.drag_group[0] d = 200 for my_dockn in range(len(my_block.docks)): for your_block in self.just_blocks(): # don't link to a block to which you're already connected if your_block in self.drag_group: continue # check each dock of your_block for a possible connection for your_dockn in range(len(your_block.docks)): this_xy = dock_dx_dy(your_block, your_dockn, my_block, my_dockn) if magnitude(this_xy) > d: continue d = magnitude(this_xy) best_xy = this_xy best_you = your_block best_your_dockn = your_dockn best_my_dockn = my_dockn if d < 200: if not arithmetic_check(my_block, best_you, best_my_dockn, best_your_dockn): return for blk in self.drag_group: (sx, sy) = blk.spr.get_xy() blk.spr.move((sx + best_xy[0], sy + best_xy[1])) # If there was already a block docked there, move it to the trash. blk_in_dock = best_you.connections[best_your_dockn] if blk_in_dock is not None: blk_in_dock.connections[0] = None self._put_in_trash(blk_in_dock) best_you.connections[best_your_dockn] = my_block if my_block.connections is not None: my_block.connections[best_my_dockn] = best_you def _import_from_journal(self, blk): """ Import a file from the Sugar Journal """ if self.running_sugar: chooser = ObjectChooser('Choose image', None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) try: result = chooser.run() if result == gtk.RESPONSE_ACCEPT: dsobject = chooser.get_selected_object() self._update_media_icon(blk, dsobject, dsobject.object_id) dsobject.destroy() finally: chooser.destroy() del chooser else: fname, self.load_save_folder = \ get_load_name('.*', self.load_save_folder) if fname is None: return self._update_media_icon(blk, fname) def _load_description_block(self, blk): """ Look for a corresponding description block """ if blk == None or blk.name != 'journal' or len(blk.values) == 0 or\ blk.connections[0] is None: return _blk = blk.connections[0] dblk = find_blk_below(_blk, 'description') # Autoupdate the block if it is empty if dblk != None and (len(dblk.values) == 0 or dblk.values[0] == None): self._update_media_icon(dblk, None, blk.values[0]) def _update_media_icon(self, blk, name, value=''): """ Update the icon on a 'loaded' media block. """ if blk.name == 'journal': self._load_image_thumb(name, blk) elif blk.name == 'audio': self._block_skin('audioon', blk) else: self._block_skin('descriptionon', blk) if value == '': value = name if len(blk.values) > 0: blk.values[0] = value else: blk.values.append(value) blk.spr.set_label(' ') def _load_image_thumb(self, picture, blk): """ Replace icon with a preview image. """ pixbuf = None self._block_skin('descriptionon', blk) if self.running_sugar: w, h = calc_image_size(blk.spr) pixbuf = get_pixbuf_from_journal(picture, w, h) else: if movie_media_type(picture): self._block_skin('journalon', blk) elif audio_media_type(picture): self._block_skin('audioon', blk) blk.name = 'audio' elif image_media_type(picture): w, h = calc_image_size(blk.spr) pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(picture, w, h) else: blk.name = 'description' if pixbuf is not None: x, y = self._calc_image_offset('', blk.spr) blk.set_image(pixbuf, x, y) self._resize_skin(blk) def _disconnect(self, blk): """ Disconnect block from stack above it. """ if blk.connections[0] == None: return if collapsed(blk): return blk2 = blk.connections[0] blk2.connections[blk2.connections.index(blk)] = None blk.connections[0] = None def _keypress_cb(self, area, event): """ Keyboard """ keyname = gtk.gdk.keyval_name(event.keyval) keyunicode = gtk.gdk.keyval_to_unicode(event.keyval) if event.get_state()>k.gdk.MOD1_MASK: alt_mask = True alt_flag = 'T' else: alt_mask = False alt_flag = 'F' self._key_press(alt_mask, keyname, keyunicode) return keyname def _key_press(self, alt_mask, keyname, keyunicode, verbose=False): if keyname is None: return False if verbose: print "processing remote key press: %s" % (keyname) self.keypress = keyname # First, process Alt keys. if alt_mask and self.selected_blk is not None: if keyname == "p": self.hideshow_button() elif keyname == 'q': exit() return True # Process keyboard input for 'number' blocks if self.selected_blk is not None and \ self.selected_blk.name == 'number': self._process_numeric_input(keyname) return True # Process keyboard input for 'string' blocks elif self.selected_blk is not None and \ self.selected_blk.name == 'string': self.process_alphanumeric_input(keyname, keyunicode) if self.selected_blk is not None: self.selected_blk.resize() return True # Otherwise, use keyboard input to move blocks or turtles else: self._process_keyboard_commands(keyname) if self.selected_blk is None: return False def _process_numeric_input(self, keyname): ''' Make sure numeric input is valid. ''' oldnum = self.selected_blk.spr.labels[0].replace(CURSOR, '') if len(oldnum) == 0: oldnum = '0' if keyname == 'minus': if oldnum == '0': newnum = '-' elif oldnum[0] != '-': newnum = '-' + oldnum else: newnum = oldnum elif keyname == 'period' and '.' not in oldnum: newnum = oldnum + '.' elif keyname == 'BackSpace': if len(oldnum) > 0: newnum = oldnum[:len(oldnum)-1] else: newnum = '' elif keyname in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: if oldnum == '0': newnum = keyname else: newnum = oldnum + keyname elif keyname == 'Return': self._unselect_block() return else: newnum = oldnum if newnum == '.': newnum = '0.' if len(newnum) > 0 and newnum != '-': try: float(newnum) except ValueError, e: newnum = oldnum self.selected_blk.spr.set_label(newnum + CURSOR) def process_alphanumeric_input(self, keyname, keyunicode): """ Make sure alphanumeric input is properly parsed. """ if len(self.selected_blk.spr.labels[0]) > 0: c = self.selected_blk.spr.labels[0].count(CURSOR) if c == 0: oldleft = self.selected_blk.spr.labels[0] oldright = '' elif len(self.selected_blk.spr.labels[0]) == 1: oldleft = '' oldright = '' else: try: # Why are getting a ValueError on occasion? oldleft, oldright = \ self.selected_blk.spr.labels[0].split(CURSOR) except ValueError: print "[%s]" % self.selected_blk.spr.labels[0] oldleft = self.selected_blk.spr.labels[0] oldright = '' else: oldleft = '' oldright = '' newleft = oldleft if keyname in ['Shift_L', 'Shift_R', 'Control_L', 'Caps_Lock', \ 'Alt_L', 'Alt_R', 'KP_Enter', 'ISO_Level3_Shift']: keyname = '' keyunicode = 0 # Hack until I sort out input and unicode and dead keys, if keyname[0:5] == 'dead_': self.dead_key = keyname keyname = '' keyunicode = 0 if keyname == 'space': keyunicode = 32 elif keyname == 'Tab': keyunicode = 9 if keyname == 'BackSpace': if len(oldleft) > 1: newleft = oldleft[:len(oldleft)-1] else: newleft = '' elif keyname == 'Home': oldright = oldleft+oldright newleft = '' elif keyname == 'Left': if len(oldleft) > 0: oldright = oldleft[len(oldleft)-1:]+oldright newleft = oldleft[:len(oldleft)-1] elif keyname == 'Right': if len(oldright) > 0: newleft = oldleft + oldright[0] oldright = oldright[1:] elif keyname == 'End': newleft = oldleft+oldright oldright = '' elif keyname == 'Return': newleft = oldleft+RETURN elif keyname == 'Down': self._unselect_block() return elif keyname == 'Up' or keyname == 'Escape': # Restore previous state self.selected_blk.spr.set_label(self.saved_string) self._unselect_block() return else: if self.dead_key is not '': keyunicode = \ DEAD_DICTS[DEAD_KEYS.index(self.dead_key[5:])][keyname] self.dead_key = '' if keyunicode > 0: if unichr(keyunicode) != '\x00': newleft = oldleft+unichr(keyunicode) else: newleft = oldleft elif keyunicode == -1: # clipboard text if keyname == '\n': newleft = oldleft+RETURN else: newleft = oldleft+keyname self.selected_blk.spr.set_label("%s%s%s" % (newleft, CURSOR, oldright)) def _process_keyboard_commands(self, keyname): """ Use the keyboard to move blocks and turtle """ mov_dict = {'KP_Up':[0, 10], 'j':[0, 10], 'Up':[0, 10], 'KP_Down':[0, -10], 'k':[0, -10], 'Down':[0, -10], 'KP_Left':[-10, 0], 'h':[-10, 0], 'Left':[-10, 0], 'KP_Right':[10, 0], 'l':[10, 0], 'Right':[10, 0], 'KP_Page_Down':[0, 0], 'KP_Page_Up':[0, 0], 'KP_End':[0, 0], 'KP_Home':[-1, -1], 'Return':[-1, -1], 'Esc':[0, 0]} if not mov_dict.has_key(keyname): return if keyname == 'KP_End': self.run_button(0) elif self.selected_spr is not None: blk = self.block_list.spr_to_block(self.selected_spr) tur = self.turtles.spr_to_turtle(self.selected_spr) if not self.lc.running and blk is not None: if keyname == 'Return' or keyname == 'KP_Page_Up': (x, y) = blk.spr.get_xy() self._click_block(x, y) elif keyname == 'KP_Page_Down': if self.drag_group == None: self.drag_group = find_group(blk) for gblk in self.drag_group: gblk.spr.hide() self.drag_group = None else: self._jog_block(blk, mov_dict[keyname][0], mov_dict[keyname][1]) elif tur is not None: self._jog_turtle(mov_dict[keyname][0], mov_dict[keyname][1]) return True def _jog_turtle(self, dx, dy): """ Jog turtle """ if dx == -1 and dy == -1: self.canvas.xcor = 0 self.canvas.ycor = 0 else: self.canvas.xcor += dx self.canvas.ycor += dy self.canvas.move_turtle() self.display_coordinates() self.selected_turtle = None def _jog_block(self, blk, dx, dy): """ Jog block """ if collapsed(blk): return self.drag_group = find_group(blk) # check to see if any block ends up with a negative x for blk in self.drag_group: (sx, sy) = blk.spr.get_xy() if sx+dx < 0: dx += -(sx + dx) # move the stack for blk in self.drag_group: (sx, sy) = blk.spr.get_xy() blk.spr.move((sx + dx, sy - dy)) self._snap_to_dock() self.drag_group = None def _number_check(self): """ Make sure a 'number' block contains a number. """ n = self.selected_blk.spr.labels[0].replace(CURSOR, '') if n in ['-', '.', '-.']: n = 0 if n is not None: try: f = float(n) if f > 1000000: n = 1 self.showlabel("#overflowerror") elif f < -1000000: n = -1 self.showlabel("#overflowerror") except ValueError: n = 0 self.showlabel("#notanumber") else: n = 0 self.selected_blk.spr.set_label(n) self.selected_blk.values[0] = n def _string_check(self): s = self.selected_blk.spr.labels[0].replace(CURSOR, '') self.selected_blk.spr.set_label(s) self.selected_blk.values[0] = s.replace(RETURN, "\12") def load_python_code(self): """ Load Python code from a file """ fname, self.load_save_folder = get_load_name('.py', self.load_save_folder) if fname == None: return f = open(fname, 'r') self.myblock = f.read() f.close() def _import_py(self): """ Import Python code into a block """ if self.running_sugar: self.activity.import_py() else: self.load_python_code() self.set_userdefined() def new_project(self): """ Start a new project """ stop_logo(self) # Put current project in the trash. while len(self.just_blocks()) > 0: blk = self.just_blocks()[0] top = find_top_block(blk) self._put_in_trash(top) self.canvas.clearscreen() self.save_file_name = None def load_files(self, ta_file, create_new_project=True): """ Load a project from a file """ if create_new_project: self.new_project() self._check_collapsibles(self.process_data(data_from_file(ta_file))) def load_file(self, create_new_project=True): _file_name, self.load_save_folder = get_load_name('.ta', self.load_save_folder) if _file_name == None: return if _file_name[-3:] == '.ta': _file_name = _file_name[0:-3] self.load_files(_file_name+'.ta', create_new_project) if create_new_project: self.save_file_name = os.path.basename(_file_name) def _found_a_turtle(self, blk): """ Either [-1, 'turtle', ...] or [-1, ['turtle', key], ...] """ if blk[1] == 'turtle': self.load_turtle(blk) return True elif type(blk[1]) == list and blk[1][0] == 'turtle': self.load_turtle(blk, blk[1][1]) return True elif type(blk[1]) == tuple: _btype, _key = blk[1] if _btype == 'turtle': self.load_turtle(blk, _key) return True return False def load_turtle(self, blk, key=1): """ Restore a turtle from its saved state """ tid, name, xcor, ycor, heading, color, shade, pensize = blk self.canvas.set_turtle(key) self.canvas.setxy(xcor, ycor) self.canvas.seth(heading) self.canvas.setcolor(color) self.canvas.setshade(shade) self.canvas.setpensize(pensize) def load_block(self, b): """ Restore individual blocks from saved state """ # A block is saved as: (i, (btype, value), x, y, (c0,... cn)) # The x, y position is saved/loaded for backward compatibility btype, value = b[1], None if type(btype) == tuple: btype, value = btype elif type(btype) == list: btype, value = btype[0], btype[1] if btype in CONTENT_BLOCKS or btype in COLLAPSIBLE: if btype == 'number': try: values = [int(value)] except ValueError: values = [float(value)] elif btype in COLLAPSIBLE: if value is not None: values = [int(value)] else: values = [] else: values = [value] else: values = [] if btype in OLD_DOCK: check_dock = True else: check_dock = False if OLD_NAMES.has_key(btype): btype = OLD_NAMES[btype] blk = Block(self.block_list, self.sprite_list, btype, b[2] + self.canvas.cx, b[3] + self.canvas.cy, 'block', values, self.block_scale) # Some blocks get transformed. if btype == 'string': blk.spr.set_label(blk.values[0].replace('\n', RETURN)) elif btype in EXPANDABLE or btype == 'nop': if btype == 'vspace': if value is not None: blk.expand_in_y(value) elif btype == 'hspace' or btype == 'identity2': if value is not None: blk.expand_in_x(value) elif btype == 'templatelist' or btype == 'list': for i in range(len(b[4])-4): blk.add_arg() elif btype == 'myfunc2arg' or btype == 'myfunc3arg' or\ btype == 'userdefined2args' or btype == 'userdefined3args': blk.add_arg() if btype == 'myfunc3arg' or btype == 'userdefined3args': blk.add_arg(False) if btype in PYTHON_SKIN: if self.nop == 'pythonloaded': self._block_skin('pythonon', blk) else: self._block_skin('pythonoff', blk) elif btype in BOX_STYLE_MEDIA: if len(blk.values) == 0 or blk.values[0] == 'None' or\ blk.values[0] == None: self._block_skin(btype+'off', blk) elif btype == 'audio' or btype == 'description': self._block_skin(btype+'on', blk) elif self.running_sugar: try: dsobject = datastore.get(blk.values[0]) if not movie_media_type(dsobject.file_path[-4:]): w, h, = calc_image_size(blk.spr) pixbuf = get_pixbuf_from_journal(dsobject, w, h) if pixbuf is not None: x, y = self._calc_image_offset('', blk.spr) blk.set_image(pixbuf, x, y) else: self._block_skin('journalon', blk) dsobject.destroy() except: try: w, h, = calc_image_size(blk.spr) pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( blk.values[0], w, h) x, y = self._calc_image_offset('', blk.spr) blk.set_image(pixbuf, x, y) except: print "Warning: Couldn't open dsobject (%s)" % \ (blk.values[0]) self._block_skin('journaloff', blk) else: if not movie_media_type(blk.values[0][-4:]): try: w, h, = calc_image_size(blk.spr) pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( blk.values[0], w, h) x, y = self._calc_image_offset('', blk.spr) blk.set_image(pixbuf, x, y) except: self._block_skin('journaloff', blk) else: self._block_skin('journalon', blk) blk.spr.set_label(' ') blk.resize() blk.spr.set_layer(BLOCK_LAYER) if check_dock: blk.connections = 'check' return blk def load_start(self): """ Start a new project with a 'start' brick """ self.process_data([[0, "start", PALETTE_WIDTH + 20, self.toolbar_offset+PALETTE_HEIGHT + 20, [None, None]]]) def save_file(self): """ Start a project to a file """ if self.save_folder is not None: self.load_save_folder = self.save_folder _file_name, self.load_save_folder = get_save_name('.ta', self.load_save_folder, self.save_file_name) if _file_name is None: return if _file_name[-3:] == '.ta': _file_name = _file_name[0:-3] data_to_file(self.assemble_data_to_save(), _file_name + '.ta') self.save_file_name = os.path.basename(_file_name) def assemble_data_to_save(self, save_turtle=True, save_project=True): """ Pack the project (or stack) into a data stream to be serialized """ _data = [] _blks = [] if save_project: _blks = self.just_blocks() else: _blks = find_group(find_top_block(self.selected_blk)) for _i, _blk in enumerate(_blks): _blk.id = _i for _blk in _blks: if _blk.name in CONTENT_BLOCKS or _blk.name in COLLAPSIBLE: if len(_blk.values) > 0: _name = (_blk.name, _blk.values[0]) else: _name = (_blk.name) elif _blk.name in EXPANDABLE: _ex, _ey = _blk.get_expand_x_y() if _ex > 0: _name = (_blk.name, _ex) elif _ey > 0: _name = (_blk.name, _ey) else: _name = (_blk.name, 0) else: _name = (_blk.name) if hasattr(_blk, 'connections'): connections = [get_id(_cblk) for _cblk in _blk.connections] else: connections = None (_sx, _sy) = _blk.spr.get_xy() # Add a slight offset for copy/paste if not save_project: _sx += 20 _sy += 20 _data.append((_blk.id, _name, _sx-self.canvas.cx, _sy-self.canvas.cy, connections)) if save_turtle: for _turtle in iter(self.turtles.dict): self.canvas.set_turtle(_turtle) _data.append((-1, ['turtle', _turtle], self.canvas.xcor, self.canvas.ycor, self.canvas.heading, self.canvas.color, self.canvas.shade, self.canvas.pensize)) return _data def display_coordinates(self): """ Display the coordinates of the current turtle on the toolbar """ x = round_int(self.canvas.xcor/self.coord_scale) y = round_int(self.canvas.ycor/self.coord_scale) h = round_int(self.canvas.heading) if self.running_sugar: self.activity.coordinates_label.set_text("%s: %d %s: %d %s: %d" % ( _("xcor"), x, _("ycor"), y, _("heading"), h)) self.activity.coordinates_label.show() else: self.win.set_title("%s — %s: %d %s: %d %s: %d" % (_("Turtle Art"), _("xcor"), x, _("ycor"), y, _("heading"), h)) def showlabel(self, shp, label = ''): """ Display a message on a status block """ if shp == 'syntaxerror' and str(label) != '': if self.status_shapes.has_key(str(label)[1:]): shp = str(label)[1:] label = '' else: shp = 'status' elif shp[0] == '#': shp = shp[1:] label = '' if shp == 'notanumber': shp = 'overflowerror' self.status_spr.set_shape(self.status_shapes[shp]) self.status_spr.set_label(str(label)) self.status_spr.set_layer(STATUS_LAYER) if shp == 'info': self.status_spr.move((PALETTE_WIDTH, self.height-400)) else: self.status_spr.move((PALETTE_WIDTH, self.height-200)) def calc_position(self, template): """ Relative placement of portfolio objects (depreciated) """ w, h, x, y, dx, dy = TEMPLATES[template] x *= self.canvas.width y *= self.canvas.height w *= (self.canvas.width-x) h *= (self.canvas.height-y) dx *= w dy *= h return(w, h, x, y, dx, dy) def save_as_image(self, name="", svg=False): """ Grab the current canvas and save it. """ if self.running_sugar: if svg: if len(name) == 0: filename = "ta.svg" else: filename = name+".svg" else: if len(name) == 0: filename = "ta.png" else: filename = name+".png" datapath = get_path(self.activity, 'instance') elif len(name) == 0: name = "ta" if self.save_folder is not None: self.load_save_folder = self.save_folder if svg: filename, self.load_save_folder = get_save_name('.svg', self.load_save_folder, name) else: filename, self.load_save_folder = get_save_name('.png', self.load_save_folder, name) datapath = self.load_save_folder else: datapath = os.getcwd() if svg: filename = name+".svg" else: filename = name+".png" if filename is None: return file_path = os.path.join(datapath, filename) if svg: if self.svg_string == '': return save_svg(self.svg_string, file_path) self.svg_string = '' else: save_picture(self.canvas, file_path) # keep a log of the saved pictures for export to HTML self.saved_pictures.append(file_path) if self.running_sugar: dsobject = datastore.create() if len(name) == 0: dsobject.metadata['title'] = "%s %s" % ( self.activity.metadata['title'], _("image")) else: dsobject.metadata['title'] = name dsobject.metadata['icon-color'] = profile.get_color().to_string() if svg: dsobject.metadata['mime_type'] = 'image/svg+xml' else: dsobject.metadata['mime_type'] = 'image/png' dsobject.set_file_path(file_path) datastore.write(dsobject) dsobject.destroy() def just_blocks(self): """ Filter out 'proto', 'trash', and 'deleted' blocks """ just_blocks_list = [] for _blk in self.block_list.list: if _blk.type == 'block': just_blocks_list.append(_blk) return just_blocks_list def _width_and_height(self, blk): """ What are the width and height of a stack? """ minx = 10000 miny = 10000 maxx = -10000 maxy = -10000 for gblk in find_group(blk): (x, y) = gblk.spr.get_xy() w, h = gblk.spr.get_dimensions() if x < minx: minx = x if y < miny: miny = y if x + w > maxx: maxx = x + w if y + h > maxy: maxy = y + h return(maxx - minx, maxy - miny) # Utilities related to putting a image 'skin' on a block def _calc_image_offset(self, name, spr, iw=0, ih=0): """ Calculate the postion for placing an image onto a sprite. """ _l, _t = spr.label_left_top() if name == '': return _l, _t _w = spr.label_safe_width() _h = spr.label_safe_height() if iw == 0: iw = self.media_shapes[name].get_width() ih = self.media_shapes[name].get_height() return int(_l + (_w - iw)/2), int(_t + (_h - ih)/2) def _calc_w_h(self, name, spr): """ Calculate new image size """ target_w = spr.label_safe_width() target_h = spr.label_safe_height() if name == '': return target_w, target_h image_w = self.media_shapes[name].get_width() image_h = self.media_shapes[name].get_height() scale_factor = float(target_w)/image_w new_w = target_w new_h = image_h*scale_factor if new_h > target_h: scale_factor = float(target_h)/new_h new_h = target_h new_w = target_w*scale_factor return int(new_w), int(new_h) def _proto_skin(self, name, n, i): """ Utility for calculating proto skin images """ x, y = self._calc_image_offset(name, self.palettes[n][i].spr) self.palettes[n][i].spr.set_image(self.media_shapes[name], 1, x, y) def _block_skin(self, name, blk): """ Some blocks get a skin """ x, y = self._calc_image_offset(name, blk.spr) blk.set_image(self.media_shapes[name], x, y) self._resize_skin(blk) def _resize_skin(self, blk): """ Resize the 'skin' when block scale changes. """ if blk.name in PYTHON_SKIN: w, h = self._calc_w_h('pythonoff', blk.spr) x, y = self._calc_image_offset('pythonoff', blk.spr, w, h) elif blk.name == 'journal': if len(blk.values) == 1 and blk.values[0] is not None: w, h = self._calc_w_h('', blk.spr) x, y = self._calc_image_offset('journaloff', blk.spr, w, h) else: w, h = self._calc_w_h('journaloff', blk.spr) x, y = self._calc_image_offset('journaloff', blk.spr, w, h) else: w, h = self._calc_w_h('descriptionoff', blk.spr) x, y = self._calc_image_offset('descriptionoff', blk.spr, w, h) blk.scale_image(x, y, w, h)