diff options
Diffstat (limited to 'tawindow.py')
-rw-r--r-- | tawindow.py | 2983 |
1 files changed, 2132 insertions, 851 deletions
diff --git a/tawindow.py b/tawindow.py index 7b25335..57224f4 100644 --- a/tawindow.py +++ b/tawindow.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- #Copyright (c) 2007, Playful Invention Company -#Copyright (c) 2008-9, Walter Bender -#Copyright (c) 2009, Raúl Gutiérrez Segalés +#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 @@ -24,895 +24,2176 @@ import pygtk pygtk.require('2.0') import gtk -import pango import gobject import os import os.path -import time - -# Import from Journal for these blocks -importblocks = ['audiooff', 'descriptionoff','journal'] - -class taWindow: pass - from math import atan2, pi DEGTOR = 2*pi/360 +from gettext import gettext as _ -from tasetup import * -from tasprites import * -from talogo import * -from taturtle import * -from taproject import * try: from sugar.graphics.objectchooser import ObjectChooser -except: + from sugar.datastore import datastore + from sugar import profile +except ImportError: pass -from tahoverhelp import * -from gettext import gettext as _ +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 -# dead key dictionaries -dead_grave = {'A':192,'E':200,'I':204,'O':210,'U':217,'a':224,'e':232,'i':236,\ - 'o':242,'u':249} -dead_acute = {'A':193,'E':201,'I':205,'O':211,'U':218,'a':225,'e':233,'i':237,\ - 'o':243,'u':250} -dead_circumflex = {'A':194,'E':202,'I':206,'O':212,'U':219,'a':226,'e':234,\ - 'i':238,'o':244,'u':251} -dead_tilde = {'A':195,'O':211,'N':209,'U':360,'a':227,'o':245,'n':241,'u':361} -dead_diaeresis = {'A':196,'E':203,'I':207,'O':211,'U':218,'a':228,'e':235,\ - 'i':239,'o':245,'u':252} -dead_abovering = {'A':197,'a':229} - -# Time out for triggering help -timeout_tag = [0] - - -# -# Setup -# - -def twNew(win, path, lang, parent=None): - tw = taWindow() - tw.window = win - tw.path = os.path.join(path,'images') - tw.path_lang = os.path.join(path,'images',lang) - tw.path_en = os.path.join(path,'images/en') # en as fallback - tw.load_save_folder = os.path.join(path,'samples') - tw.save_folder = None - tw.save_file_name = None - win.set_flags(gtk.CAN_FOCUS) - tw.width = gtk.gdk.screen_width() - tw.height = gtk.gdk.screen_height() - # starting from command line - if parent is None: - win.show_all() - # starting from Sugar - else: - parent.show_all() - win.add_events(gtk.gdk.BUTTON_PRESS_MASK) - win.add_events(gtk.gdk.BUTTON_RELEASE_MASK) - win.add_events(gtk.gdk.POINTER_MOTION_MASK) - win.add_events(gtk.gdk.KEY_PRESS_MASK) - win.connect("expose-event", expose_cb, tw) - win.connect("button-press-event", buttonpress_cb, tw) - win.connect("button-release-event", buttonrelease_cb, tw) - win.connect("motion-notify-event", move_cb, tw) - win.connect("key_press_event", keypress_cb, tw) - tw.keypress = "" - tw.keyvalue = 0 - tw.dead_key = "" - tw.area = win.window - tw.gc = tw.area.new_gc() - # on an OLPC-XO-1, there is a scaling factor - if os.path.exists('/etc/olpc-release') or \ - os.path.exists('/sys/power/olpc-pm'): - tw.lead = 1.6 - tw.scale = 1.0 - else: - tw.lead = 1.0 - tw.scale = 1.6 - tw.cm = tw.gc.get_colormap() - tw.rgb = [255,0,0] - tw.bgcolor = tw.cm.alloc_color('#fff8de') - tw.msgcolor = tw.cm.alloc_color('black') - tw.fgcolor = tw.cm.alloc_color('red') - tw.textcolor = tw.cm.alloc_color('blue') - tw.textsize = 32 - tw.sprites = [] - tw.selected_block = None - tw.draggroup = None - prep_selectors(tw) - tw.myblock = None - tw.nop = 'nop' - tw.loaded = 0 - for s in selectors: - setup_selectors(tw,s) - setup_misc(tw) - tw.step_time = 0 - tw.hide = False - tw.palette = True - select_category(tw, tw.selbuttons[0]) - tw.coord_scale = 1 - tw.turtle = tNew(tw,tw.width,tw.height) - tw.lc = lcNew(tw) - tw.buddies = [] - tw.dx = 0 - tw.dy = 0 - tw.cartesian = False - tw.polar = False - tw.spr = None # "currently selected spr" - return tw - -# -# Button Press -# - -def buttonpress_cb(win, event, tw): - win.grab_focus() - x, y = xy(event) - button_press(tw, event.get_state()>k.gdk.CONTROL_MASK, x, y) - # if sharing, send button press - if hasattr(tw, 'activity') and \ - hasattr(tw.activity, 'chattube') and tw.activity.chattube is not None: - # print "sending button pressed" - if event.get_state()>k.gdk.CONTROL_MASK is True: - tw.activity._send_event("p:"+str(x)+":"+str(y)+":"+'T') - else: - tw.activity._send_event("p:"+str(x)+":"+str(y)+":"+'F') - return True - -def button_press(tw, mask, x, y, verbose=False): - if verbose: - print "processing remote button press: " + str(x) + " " + str(y) - tw.block_operation = 'click' - if tw.selected_block!=None: - unselect(tw) - else: - setlayer(tw.status_spr,400) - spr = findsprite(tw,(x,y)) - tw.x, tw.y = x,y - tw.dx = 0 - tw.dy = 0 - if spr is None: - return True - if spr.type == "canvas": + def _expose_cb(self, win, event): + """ Repaint """ + self.sprite_list.redraw_sprites() + # self.canvas.cr_expose(event) return True - elif spr.type == 'selbutton': - select_category(tw,spr) - elif spr.type == 'category': - block_selector_pressed(tw,x,y) - elif spr.type == 'block': - block_pressed(tw,mask,x,y,spr) - elif spr.type == 'turtle': - turtle_pressed(tw,x,y) - tw.spr = spr - -def block_selector_pressed(tw,x,y): - proto = get_proto_from_category(tw,x,y) - if proto==None: + + 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 - if proto is not 'hide': - new_block_from_category(tw,proto,x,y) - else: - hideshow_palette(tw,False) - -def hideshow_palette(tw,state): - if state is False: - tw.palette == False - if hasattr(tw,'activity'): - # Use new toolbar design - tw.activity.do_hidepalette() - hide_palette(tw) - else: - tw.palette == True - if hasattr(tw,'activity'): - # Use new toolbar design - tw.activity.do_showpalette() - show_palette(tw) - -def show_palette(tw): - for i in tw.selbuttons: setlayer(i,800) - select_category(tw,tw.selbuttons[0]) - tw.palette = True - -def hide_palette(tw): - for i in tw.selbuttons: hide(i) - setshape(tw.category_spr, tw.hidden_palette_icon) - tw.palette = False - -def get_proto_from_category(tw,x,y): - dx,dy = x-tw.category_spr.x, y-tw.category_spr.y, - pixel = getpixel(tw.current_category.mask,dx,dy) - index = ((pixel%256)>>3)-1 - if index==0: - return 'hide' - index-=1 - if index>len(tw.current_category.blockprotos): - return None - return tw.current_category.blockprotos[index] - -def select_category(tw, spr): - if hasattr(tw, 'current_category'): - setshape(tw.current_category, tw.current_category.offshape) - setshape(spr, spr.onshape) - tw.current_category = spr - setshape(tw.category_spr,spr.group) - -def new_block_from_category(tw,proto,x,y): - if proto is None: + + 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 - # load alternative image of nop block if python code is loaded - if proto.name == 'nop' and tw.nop == 'pythonloaded': - newspr = sprNew(tw,x-20,y-20,tw.media_shapes['pythonloaded']) - else: - newspr = sprNew(tw,x-20,y-20,proto.image) - setlayer(newspr,2000) - tw.dragpos = 20,20 - newspr.type = 'block' - newspr.proto = proto - if tw.defdict.has_key(newspr.proto.name): - newspr.label=tw.defdict[newspr.proto.name] - newspr.connections = [None]*len(proto.docks) - for i in range(len(proto.defaults)): - dock = proto.docks[i+1] - argproto = tw.protodict[tw.valdict[dock[0]]] - argdock = argproto.docks[0] - nx,ny = newspr.x+dock[2]-argdock[2],newspr.y+dock[3]-argdock[3] - argspr = sprNew(tw,nx,ny,argproto.image) - argspr.type = 'block' - argspr.proto = argproto - argspr.label = str(proto.defaults[i]) - setlayer(argspr,2000) - argspr.connections = [newspr,None] - newspr.connections[i+1] = argspr - tw.draggroup = findgroup(newspr) - tw.block_operation = 'new' - -def block_pressed(tw,mask,x,y,spr): - if spr is not None: - tw.draggroup = findgroup(spr) - for b in tw.draggroup: setlayer(b,2000) - if spr.connections[0] != None and spr.proto.name == 'lock': - b = find_top_block(spr) - tw.dragpos = x-b.x,y-b.y - else: - tw.dragpos = x-spr.x,y-spr.y - disconnect(spr) - -def turtle_pressed(tw,x,y): - dx,dy = x-tw.turtle.spr.x-30,y-tw.turtle.spr.y-30 - if dx*dx+dy*dy > 200: - tw.dragpos = ('turn', \ - tw.turtle.heading-atan2(dy,dx)/DEGTOR,0) - else: - tw.dragpos = ('move', x-tw.turtle.spr.x,y-tw.turtle.spr.y) - tw.draggroup = [tw.turtle.spr] - -# -# Mouse move -# - -def move_cb(win, event, tw): - x,y = xy(event) - mouse_move(tw, x, y) - return True - -def mouse_move(tw, x, y, verbose=False, mdx=0, mdy=0): - if verbose: - print "processing remote mouse move: " + str(x) + " " + str(y) - if tw.draggroup is None: - # popup help from RGS - spr = findsprite(tw,(x,y)) - if spr and spr.type == 'category': - proto = get_proto_from_category(tw,x,y) - if proto and proto!='hide': - if timeout_tag[0] == 0: - timeout_tag[0] = showPopup(proto.name,tw) - tw.spr = spr - return - else: - if timeout_tag[0] > 0: - try: - gobject.source_remove(timeout_tag[0]) - timeout_tag[0] = 0 - except: - timeout_tag[0] = 0 - elif spr and spr.type == 'selbutton': - if timeout_tag[0] == 0: - timeout_tag[0] = showPopup(spr.name,tw) - tw.spr = spr + + 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: - if timeout_tag[0] > 0: + 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(timeout_tag[0]) - timeout_tag[0] = 0 + gobject.source_remove(self.timeout_tag[0]) + self.timeout_tag[0] = 0 except: - timeout_tag[0] = 0 - elif spr and spr.type == 'block': - if timeout_tag[0] == 0: - timeout_tag[0] = showPopup(spr.proto.name,tw) - tw.spr = spr + 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 timeout_tag[0] > 0: + if self.timeout_tag[0] > 0: try: - gobject.source_remove(timeout_tag[0]) - timeout_tag[0] = 0 + gobject.source_remove(self.timeout_tag[0]) + self.timeout_tag[0] = 0 except: - timeout_tag[0] = 0 + self.timeout_tag[0] = 0 else: - if timeout_tag[0] > 0: + if self.timeout_tag[0] > 0: try: - gobject.source_remove(timeout_tag[0]) - timeout_tag[0] = 0 + gobject.source_remove(self.timeout_tag[0]) + self.timeout_tag[0] = 0 except: - timeout_tag[0] = 0 - return - tw.block_operation = 'move' - spr = tw.draggroup[0] - if spr.type == 'block': - tw.spr = spr - dragx, dragy = tw.dragpos - if mdx != 0 or mdy != 0: - dx,dy = mdx,mdy + 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: - dx,dy = x-dragx-spr.x,y-dragy-spr.y - # skip if there was a move of 0,0 - if dx == 0 and dy == 0: - return - # drag entire stack if moving lock block - if spr.proto.name == 'lock': - tw.draggroup = findgroup(find_top_block(spr)) + 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: - tw.draggroup = findgroup(spr) - # check to see if any block ends up with a negative x - for b in tw.draggroup: - if b.x+dx < 0: - dx += -(b.x+dx) - # move the stack - for b in tw.draggroup: - move(b,(b.x+dx, b.y+dy)) - elif spr.type=='turtle': - type,dragx,dragy = tw.dragpos - if type == 'move': - if mdx != 0 or mdy != 0: - dx,dy = mdx,mdy + 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: - dx,dy = x-dragx-spr.x,y-dragy-spr.y - move(spr, (spr.x+dx, spr.y+dy)) + 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: - if mdx != 0 or mdy != 0: - dx,dy = mdx,mdy - else: - dx,dy = x-spr.x-30,y-spr.y-30 - seth(tw.turtle, int(dragx+atan2(dy,dx)/DEGTOR+5)/10*10) - if mdx != 0 or mdy != 0: - dx,dy = 0,0 - else: - tw.dx += dx - tw.dy += dy - -# -# Button release -# - -def buttonrelease_cb(win, event, tw): - x,y = xy(event) - button_release(tw, x, y) - if hasattr(tw, 'activity') and \ - hasattr(tw.activity, 'chattube') and tw.activity.chattube is not None: - # print "sending release button" - tw.activity._send_event("r:"+str(x)+":"+str(y)) - return True - -def button_release(tw, x, y, verbose=False): - if tw.dx != 0 or tw.dy != 0: - if hasattr(tw, 'activity') and \ - hasattr(tw.activity, 'chattube') and \ - tw.activity.chattube is not None: - if verbose: - print "processing move: " + str(tw.dx) + " " + str(tw.dy) - tw.activity._send_event("m:"+str(tw.dx)+":"+str(tw.dy)) - tw.dx = 0 - tw.dy = 0 - if verbose: - print "processing remote button release: " + str(x) + " " + str(y) - if tw.draggroup == None: - return - spr = tw.draggroup[0] - if spr.type == 'turtle': - tw.turtle.xcor = tw.turtle.spr.x-tw.turtle.canvas.x- \ - tw.turtle.canvas.width/2+30 - tw.turtle.ycor = tw.turtle.canvas.height/2-tw.turtle.spr.y+ \ - tw.turtle.canvas.y-30 - move_turtle(tw.turtle) - display_coordinates(tw) - tw.draggroup = None - return - if tw.block_operation=='move' and hit(tw.category_spr, (x,y)): - for b in tw.draggroup: hide(b) - tw.draggroup = None - return - if tw.block_operation=='new': - for b in tw.draggroup: - move(b, (b.x+200, b.y)) - snap_to_dock(tw) - for b in tw.draggroup: setlayer(b,650) - tw.draggroup = None - if tw.block_operation=='click': - if tw.spr.proto.name=='number': - tw.selected_block = spr - move(tw.select_mask, (spr.x-5,spr.y-5)) - setlayer(tw.select_mask, 660) - tw.firstkey = True - elif tw.defdict.has_key(spr.proto.name): - tw.selected_block = spr - if tw.spr.proto.name=='string': - move(tw.select_mask_string, (spr.x-5,spr.y-5)) - setlayer(tw.select_mask_string, 660) - tw.firstkey = True - elif tw.spr.proto.name in importblocks: - import_from_journal(tw, spr) - elif tw.spr.proto.name=='nop' and tw.myblock==None: - tw.activity.import_py() - else: run_stack(tw, spr) - -def import_from_journal(tw, spr): - if hasattr(tw,"activity"): - 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() - # change block graphic to indicate that object is "loaded" - if spr.proto.name == 'journal': - load_image(tw, dsobject, spr) - elif spr.proto.name == 'audiooff': - setimage(spr,tw.media_shapes['audioon']) + 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: - setimage(spr, tw.media_shapes['decson']) - spr.ds_id = dsobject.object_id - dsobject.destroy() - finally: - chooser.destroy() - del chooser - else: - print "Journal Object Chooser unavailable from outside of Sugar" - -# Replace Journal block graphic with preview image -def load_image(tw, picture, spr): - from talogo import get_pixbuf_from_journal - pixbuf = get_pixbuf_from_journal(picture,spr.width,spr.height) - if pixbuf is not None: - setimage(spr, pixbuf) - else: - setimage(spr, tw.media_shapes['texton']) - -# change the icon for user-defined blocks after Python code is loaded -def set_userdefined(tw): - list = tw.sprites[:] - for spr in list: - if hasattr(spr,'proto') and spr.proto.name == 'nop': - setimage(spr,tw.media_shapes['pythonloaded']) - tw.nop = 'pythonloaded' - -def snap_to_dock(tw): - d=200 - me = tw.draggroup[0] - for mydockn in range(len(me.proto.docks)): - for you in blocks(tw): - if you in tw.draggroup: - continue - for yourdockn in range(len(you.proto.docks)): - thisxy = dock_dx_dy(you,yourdockn,me,mydockn) - if magnitude(thisxy)>d: + 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 - d=magnitude(thisxy) - bestxy=thisxy - bestyou=you - bestyourdockn=yourdockn - bestmydockn=mydockn - if d<200: - for b in tw.draggroup: - move(b,(b.x+bestxy[0],b.y+bestxy[1])) - blockindock=bestyou.connections[bestyourdockn] - if blockindock!=None: - for b in findgroup(blockindock): - hide(b) - bestyou.connections[bestyourdockn]=me - me.connections[bestmydockn]=bestyou - -def dock_dx_dy(block1,dock1n,block2,dock2n): - dock1 = block1.proto.docks[dock1n] - dock2 = block2.proto.docks[dock2n] - d1type,d1dir,d1x,d1y=dock1[0:4] - d2type,d2dir,d2x,d2y=dock2[0:4] - if (d2type!='num') or (dock2n!=0): - if block1.connections[dock1n] != None: - return (100,100) - if block2.connections[dock2n] != None: - return (100,100) - if block1==block2: return (100,100) - if d1type!=d2type: - # some blocks can take strings or nums - if block1.proto.name in ('write', 'plus2', 'equal', 'less', 'greater', \ - 'template1', 'template2', 'template3', \ - 'template4', 'template6', 'template7', 'nop', \ - 'print', 'stack'): - if block1.proto.name == 'write' and d1type == 'string': - if d2type == 'num' or d2type == 'string': - pass - else: - if d2type == 'num' or d2type == 'string': - pass - # some blocks can take strings, nums, or Journal - elif block1.proto.name in ('show', 'push', 'storein', 'storeinbox1', \ - 'storeinbox2'): - if d2type == 'num' or d2type == 'string' or d2type == 'journal': - pass - # some blocks can take media, audio, movies, of descriptions - elif block1.proto.name in ('containter'): - if d1type == 'audiooff' or d1type == 'journal': - pass + # 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: - return (100,100) - if d1dir==d2dir: - return (100,100) - return (block1.x+d1x)-(block2.x+d2x),(block1.y+d1y)-(block2.y+d2y) - -def magnitude(pos): - x,y = pos - return x*x+y*y - -# -# Repaint -# - -def expose_cb(win, event, tw): - redrawsprites(tw) - return True - -# -# Keyboard -# - -def keypress_cb(area, event, tw): - keyname = gtk.gdk.keyval_name(event.keyval) -# keyunicode = unichr(gtk.gdk.keyval_to_unicode(event.keyval)).replace("\x00","") - keyunicode = gtk.gdk.keyval_to_unicode(event.keyval) -# print keyname -# if keyunicode > 0: -# print unichr(keyunicode) - - if event.get_state()>k.gdk.MOD1_MASK: - alt_mask = True - else: - alt_mask = False - results = key_press(tw, alt_mask, keyname, keyunicode) - if keyname is not None and hasattr(tw,"activity") and \ - hasattr(tw.activity, 'chattube') and tw.activity.chattube is not None: - # print "key press" - if alt_mask: - tw.activity._send_event("k:"+'T'+":"+keyname+":"+str(keyunicode)) - else: - tw.activity._send_event("k:"+'F'+":"+keyname+":"+str(keyunicode)) - return keyname -''' - if len(keyname)>1: - # print "(" + keyunicode.encode("utf-8") + ")" + alt_mask = False + alt_flag = 'F' + self._key_press(alt_mask, keyname, keyunicode) return keyname - else: - # print "[" + keyunicode.encode("utf-8") + "]" - return keyunicode.encode("utf-8") -''' -def key_press(tw, alt_mask, keyname, keyunicode, verbose=False): - if keyname is None: - return False - if verbose: - print "processing remote key press: " + keyname - tw.keypress = keyname - if alt_mask is True and tw.selected_block==None: - if keyname=="i" and hasattr(tw, 'activity'): - tw.activity.waiting_for_blocks = True - tw.activity._send_event("i") # request sync for sharing - elif keyname=="p": - hideshow_button(tw) - elif keyname=='q': - exit() - return True - if tw.selected_block is not None and \ - tw.selected_block.proto.name == 'number': - if keyname in ['minus', 'period']: - keyname = {'minus': '-', 'period': '.'}[keyname] - oldnum = tw.selected_block.label - selblock=tw.selected_block.proto - if keyname == 'BackSpace': - if len(oldnum) > 1: + + 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 = '' - setlabel(tw.selected_block, selblock.check(newnum,oldnum)) - if len(newnum) > 0: - tw.firstkey = False + 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: - tw.firstkey = True - if len(keyname)>1: + 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) + if self.running_sugar: + self.activity.metadata['title'] = os.path.split(_file_name)[1] + + def _found_a_turtle(self, blk): + """ Either [-1, 'turtle', ...] or [-1, ['turtle', key], ...] """ + if blk[1] == 'turtle': + self.load_turtle(blk) return True - else: # gtk.keysyms.Left ... - if keyname in ['Escape', 'Return', 'KP_Page_Up', - 'Up', 'Down', 'Left', 'Right', 'KP_Home', 'KP_End', - 'KP_Up', 'KP_Down', 'KP_Left', 'KP_Right', - 'KP_Page_Down']: - # move blocks (except number and text blocks only with arrows) - # or click with Return - if keyname == 'KP_End': - run_button(tw, 0) - elif tw.spr is not None: - if tw.spr.type == 'turtle': # jog turtle with arrow keys - if keyname == 'KP_Up' or keyname == 'Up': - jog_turtle(tw,0,10) - elif keyname == 'KP_Down' or keyname == 'Down': - jog_turtle(tw,0,-10) - elif keyname == 'KP_Left' or keyname == 'Left': - jog_turtle(tw,-10,0) - elif keyname == 'KP_Right' or keyname == 'Right': - jog_turtle(tw,10,0) - elif keyname == 'KP_Home': - jog_turtle(tw,-1,-1) - elif tw.spr.type == 'block': - if keyname == 'Return' or keyname == 'KP_Page_Up': - click_block(tw) - elif keyname == 'KP_Up' or keyname == 'Up': - jog_block(tw,0,10) - elif keyname == 'KP_Down' or keyname == 'Down': - jog_block(tw,0,-10) - elif keyname == 'KP_Left' or keyname == 'Left': - jog_block(tw,-10,0) - elif keyname == 'KP_Right' or keyname == 'Right': - jog_block(tw,10,0) - elif keyname == 'KP_Page_Down': - if tw.draggroup == None: - tw.draggroup = findgroup(tw.spr) - for b in tw.draggroup: hide(b) - tw.draggroup = None - elif tw.spr.type == 'selbutton': - if keyname == 'Return' or keyname == 'KP_Page_Up': - select_category(tw,tw.spr) - elif tw.spr.type == 'category': - if keyname == 'Return' or keyname == 'KP_Page_Up': - (x,y) = tw.window.get_pointer() - block_selector_pressed(tw,x,y) - for b in tw.draggroup: - move(b, (b.x+200, b.y)) - tw.draggroup = None + elif type(blk[1]) == list and blk[1][0] == 'turtle': + self.load_turtle(blk, blk[1][1]) return True - if tw.selected_block is None: + elif type(blk[1]) == tuple: + _btype, _key = blk[1] + if _btype == 'turtle': + self.load_turtle(blk, _key) + return True return False - 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 + dead keys - if keyname[0:5] == 'dead_': - tw.dead_key = keyname - keyname = '' - keyunicode = 0 - if keyname == 'Tab': - keyunicode = 32 # substitute a space for a tab - oldnum = tw.selected_block.label - selblock=tw.selected_block.proto - if keyname == 'BackSpace': - if len(oldnum) > 1: - newnum = oldnum[:len(oldnum)-1] - else: - newnum = '' - setlabel(tw.selected_block, selblock.check(newnum,oldnum)) - if len(newnum) > 0: - tw.firstkey = False - else: - tw.firstkey = True - elif keyname is not '': - # Hack until I sort out input and unicode + dead keys - if tw.dead_key == 'dead_grave': - keyunicode = dead_grave[keyname] - elif tw.dead_key == 'dead_acute': - keyunicode = dead_acute[keyname] - elif tw.dead_key == 'dead_circumflex': - keyunicode = dead_circumflex[keyname] - elif tw.dead_key == 'dead_tilde': - keyunicode = dead_tilde[keyname] - elif tw.dead_key == 'dead_diaeresis': - keyunicode = dead_diaeresis[keyname] - elif tw.dead_key == 'dead_abovering': - keyunicode = dead_abovering[keyname] - tw.dead_key = "" - if tw.firstkey: - newnum = selblock.check(unichr(keyunicode), \ - tw.defdict[selblock.name]) - elif keyunicode > 0: - if unichr(keyunicode) is not '\x00': - newnum = oldnum+unichr(keyunicode) + + 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: - newnum = oldnum + values = [value] else: - newnum = "" - setlabel(tw.selected_block, selblock.check(newnum,oldnum)) - tw.firstkey = False - return True - -def unselect(tw): - if tw.selected_block.label in ['-', '.', '-.']: - setlabel(tw.selected_block,'0') - - # put an upper and lower bound on numbers to prevent OverflowError - if tw.selected_block.proto.name == 'number' and \ - tw.selected_block.label is not None: - try: - i = float(tw.selected_block.label) - if i > 1000000: - setlabel(tw.selected_block,'1') - showlabel(tw.lc,"#overflowerror") - elif i < -1000000: - setlabel(tw.selected_block,'-1') - showlabel(tw.lc,"#overflowerror") - except ValueError: - pass - - hide(tw.select_mask) - hide(tw.select_mask_string) - tw.selected_block = None - -def jog_turtle(tw,dx,dy): - if dx == -1 and dy == -1: - tw.turtle.xcor = 0 - tw.turtle.ycor = 0 - else: - tw.turtle.xcor += dx - tw.turtle.ycor += dy - move_turtle(tw.turtle) - display_coordinates(tw) - tw.draggroup = None - -def jog_block(tw,dx,dy): - # drag entire stack if moving lock block - if tw.spr.proto.name == 'lock': - tw.draggroup = findgroup(find_top_block(tw.spr)) - else: - tw.draggroup = findgroup(tw.spr) - # check to see if any block ends up with a negative x - for b in tw.draggroup: - if b.x+dx < 0: - dx += -(b.x+dx) - # move the stack - for b in tw.draggroup: - move(b,(b.x+dx, b.y-dy)) - snap_to_dock(tw) - tw.draggroup = None - -def click_block(tw): - if tw.spr.proto.name=='number': - tw.selected_block = tw.spr - move(tw.select_mask, (tw.spr.x-5,tw.spr.y-5)) - setlayer(tw.select_mask, 660) - tw.firstkey = True - elif tw.defdict.has_key(tw.spr.proto.name): - tw.selected_block = tw.spr - if tw.spr.proto.name=='string': - move(tw.select_mask_string, (tw.spr.x-5,tw.spr.y-5)) - setlayer(tw.select_mask_string, 660) - tw.firstkey = True - elif tw.spr.proto.name in importblocks: - import_from_journal(tw, tw.spr) - elif tw.spr.proto.name=='nop' and tw.myblock==None: - tw.activity.import_py() - else: run_stack(tw, tw.spr) - -# -# Block utilities -# - -def disconnect(b): - if b.connections[0]==None: - return - b2=b.connections[0] - b2.connections[b2.connections.index(b)] = None - b.connections[0] = None - -def run_stack(tw,spr): - tw.lc.ag = None - top = find_top_block(spr) - run_blocks(tw.lc, top, blocks(tw), True) - gobject.idle_add(doevalstep, tw.lc) - -def findgroup(b): - group=[b] - for b2 in b.connections[1:]: - if b2!=None: group.extend(findgroup(b2)) - return group - -def find_top_block(spr): - b = spr - while b.connections[0]!=None: - b=b.connections[0] - return b - -def runtool(tw, spr, cmd, *args): - cmd(*(args)) - -def eraser_button(tw): - # hide status block - setlayer(tw.status_spr,400) - clear(tw.lc) - display_coordinates(tw) - -def stop_button(tw): - stop_logo(tw) - -def run_button(tw, time): - print "you better run, turtle, run!!" - # look for the start block - for b in blocks(tw): - if find_start_stack(tw, b): - tw.step_time = time - if hasattr(tw,'activity'): - tw.activity.recenter() - run_stack(tw, b) + 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) + if not self.running_sugar: + self.save_folder = self.load_save_folder + + def assemble_data_to_save(self, save_turtle=True, save_project=True): + """ Pack the project (or stack) into a data stream to be serialized """ + _data = [] + _blks = [] + + if save_project: + _blks = self.just_blocks() + else: + _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 - # no start block, so run a stack that isn't a hat - for b in blocks(tw): - if find_block_to_run(tw, b): - print "running " + b.proto.name - tw.step_time = time - run_stack(tw, b) - return - -def hideshow_button(tw): - if tw.hide is False: - for b in blocks(tw): setlayer(b,100) - hide_palette(tw) - hide(tw.select_mask) - hide(tw.select_mask_string) - tw.hide = True - else: - for b in blocks(tw): setlayer(b,650) - show_palette(tw) - tw.hide = False - inval(tw.turtle.canvas) - -# find start stack -def find_start_stack(tw, spr): - top = find_top_block(spr) - if spr.proto.name == 'start': - return True - else: - return False -# find a stack to run (any stack without a hat) -def find_block_to_run(tw, spr): - top = find_top_block(spr) - if spr == top and spr.proto.name[0:3] != 'hat': - return True - else: - return False + 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) -def blocks(tw): - return [spr for spr in tw.sprites if spr.type == 'block'] - -def xy(event): - return map(int, event.get_coords()) - -def showPopup(block_name,tw): - if blocks_dict.has_key(block_name): - block_name_s = _(blocks_dict[block_name]) - else: - block_name_s = _(block_name) - if hover_dict.has_key(block_name): - label = block_name_s + ": " + hover_dict[block_name] - else: - label = block_name_s - if hasattr(tw, "activity"): - tw.activity.hover_help_label.set_text(label) - tw.activity.hover_help_label.show() - elif hasattr(tw, "win"): - tw.win.set_title(_("Turtle Art") + " — " + label) - return 0 + # 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) |