diff options
author | Walter Bender <walter@sugarlabs.org> | 2014-02-10 23:09:38 (GMT) |
---|---|---|
committer | Walter Bender <walter@sugarlabs.org> | 2014-02-10 23:09:38 (GMT) |
commit | 1bf93b67e874cb5b89b53f577ab476377390485b (patch) | |
tree | 903ea636a0da87cbd7128980ab8e4f26a73fa0c1 | |
parent | 6592663e2e956c5d6557d3eb006b119b5d59ebfe (diff) |
v199
-rw-r--r-- | TurtleArt/sprites.py | 18 | ||||
-rw-r--r-- | TurtleArt/tablock.py | 10 | ||||
-rw-r--r-- | TurtleArt/taconstants.py | 23 | ||||
-rw-r--r-- | TurtleArt/tagplay.py | 46 | ||||
-rw-r--r-- | TurtleArt/talogo.py | 63 | ||||
-rw-r--r-- | TurtleArt/tapaletteview.py | 409 | ||||
-rw-r--r-- | TurtleArt/taselector.py | 108 | ||||
-rwxr-xr-x | TurtleArt/tasprite_factory.py | 13 | ||||
-rw-r--r-- | TurtleArt/taturtle.py | 7 | ||||
-rw-r--r-- | TurtleArt/tatype.py | 3 | ||||
-rw-r--r-- | TurtleArt/tautils.py | 43 | ||||
-rw-r--r-- | TurtleArt/tawindow.py | 540 | ||||
-rwxr-xr-x | turtleblocks.py | 15 |
13 files changed, 778 insertions, 520 deletions
diff --git a/TurtleArt/sprites.py b/TurtleArt/sprites.py index 3f5d7df..c8a0f6c 100644 --- a/TurtleArt/sprites.py +++ b/TurtleArt/sprites.py @@ -167,8 +167,8 @@ class Sprite: self.rect = gtk.gdk.Rectangle(int(x), int(y), 0, 0) self._scale = [12] self._rescale = [True] - self._horiz_align = ["center"] - self._vert_align = ["middle"] + self._horiz_align = ['center'] + self._vert_align = ['middle'] self._x_pos = [None] self._y_pos = [None] self._fd = None @@ -269,7 +269,7 @@ class Sprite: self._extend_labels_array(i) if isinstance(new_label, (str, unicode)): # pango doesn't like nulls - self.labels[i] = new_label.replace("\0", " ") + self.labels[i] = new_label.replace('\0', ' ') else: self.labels[i] = str(new_label) self.inval() @@ -285,7 +285,7 @@ class Sprite: if self._color is None: self._color = (0., 0., 0.) while len(self.labels) < i + 1: - self.labels.append(" ") + self.labels.append(' ') self._scale.append(self._scale[0]) self._rescale.append(self._rescale[0]) self._horiz_align.append(self._horiz_align[0]) @@ -312,8 +312,8 @@ class Sprite: int('0x' + rgb[5:7], 16) / 256.) return - def set_label_attributes(self, scale, rescale=True, horiz_align="center", - vert_align="middle", x_pos=None, y_pos=None, i=0): + def set_label_attributes(self, scale, rescale=True, horiz_align='center', + vert_align='middle', x_pos=None, y_pos=None, i=0): ''' Set the various label attributes ''' self._extend_labels_array(i) self._scale[i] = scale @@ -407,7 +407,7 @@ class Sprite: w = pl.get_size()[0] / pango.SCALE if self._x_pos[i] is not None: x = int(self.rect.x + self._x_pos[i]) - elif self._horiz_align[i] == "center": + elif self._horiz_align[i] == 'center': x = int(self.rect.x + self._margins[0] + (my_width - w) / 2) elif self._horiz_align[i] == 'left': x = int(self.rect.x + self._margins[0]) @@ -416,9 +416,9 @@ class Sprite: h = pl.get_size()[1] / pango.SCALE if self._y_pos[i] is not None: y = int(self.rect.y + self._y_pos[i]) - elif self._vert_align[i] == "middle": + elif self._vert_align[i] == 'middle': y = int(self.rect.y + self._margins[1] + (my_height - h) / 2) - elif self._vert_align[i] == "top": + elif self._vert_align[i] == 'top': y = int(self.rect.y + self._margins[1]) else: # bottom y = int(self.rect.y + self.rect.height - h - self._margins[3]) diff --git a/TurtleArt/tablock.py b/TurtleArt/tablock.py index 5f3315a..c257a14 100644 --- a/TurtleArt/tablock.py +++ b/TurtleArt/tablock.py @@ -43,19 +43,21 @@ class Media(object): ALL_TYPES = ('media', 'audio', 'video', 'descr', 'camera', 'camera1') - def __init__(self, type_, value=None): + def __init__(self, media_type, value=None): """ - type_ --- a string that indicates the kind of media: + media_type --- a string that indicates the kind of media: media --- image audio --- audio file video --- video descr --- Journal description camera, camera1 --- camera snapshot value --- a file path or a reference to a Sugar datastore object """ - if type_ not in Media.ALL_TYPES: + if media_type == 'image': + media_type = 'media' + if media_type not in Media.ALL_TYPES: raise ValueError("Media.type must be one of " + repr(Media.ALL_TYPES)) - self.type = type_ + self.type = media_type self.value = value def __str__(self): diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py index a8fc046..0a3cc23 100644 --- a/TurtleArt/taconstants.py +++ b/TurtleArt/taconstants.py @@ -151,6 +151,26 @@ REVERSE_KEY_DICT = { } +class ColorObj(object): + def __init__(self, color): + self.color = color + + def __int__(self): + if self.color.color is None: + return int(self.color.shade) + else: + return int(self.color.color) + + def __float__(self): + return float(int(self)) + + def __str__(self): + return str(self.color.name) + + def __repr__(self): + return str(self.color.name) + + class Color(object): """ A color used in block programs (e.g., as pen color). """ @@ -176,6 +196,9 @@ class Color(object): def get_number_string(self): return str(int(self)) + def get_number_name(self): + return str(self.name) + def __str__(self): return str(self.name) diff --git a/TurtleArt/tagplay.py b/TurtleArt/tagplay.py index 9e9f821..fc3ee9e 100644 --- a/TurtleArt/tagplay.py +++ b/TurtleArt/tagplay.py @@ -22,8 +22,6 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA - -import logging import os import pygtk @@ -36,6 +34,8 @@ import gst import gst.interfaces import gtk +from tautils import error_output, debug_output + def play_audio_from_file(lc, file_path): """ Called from Show block of audio media """ @@ -102,7 +102,7 @@ class Gplay(): UPDATE_INTERVAL = 500 def __init__(self, lc, x, y, w, h): - + self.running_sugar = lc.tw.running_sugar self.player = None self.uri = None self.playlist = [] @@ -118,7 +118,7 @@ class Gplay(): self.bin.add(self.videowidget) self.bin.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NORMAL) self.bin.set_decorated(False) - if lc.tw.running_sugar: + if self.running_sugar: self.bin.set_transient_for(lc.tw.activity) self.bin.move(x, y) @@ -128,14 +128,15 @@ class Gplay(): self._want_document = True def _player_eos_cb(self, widget): - logging.debug('end of stream') + debug_output('end of stream', self.running_sugar) # Make sure player is stopped after EOS self.player.stop() def _player_error_cb(self, widget, message, detail): self.player.stop() self.player.set_uri(None) - logging.debug('Error: %s - %s' % (message, detail)) + error_output('Error: %s - %s' % (message, detail), + self.running_sugar) def _player_stream_info_cb(self, widget, stream_info): if not len(stream_info) or self.got_stream_info: @@ -157,22 +158,23 @@ class Gplay(): return False self.playlist.append('file://' + os.path.abspath(file_path)) if not self.player: - # lazy init the player so that videowidget is realized - # and has a valid widget allocation - self.player = GstPlayer(self.videowidget) + # Lazy init the player so that videowidget is realized + # and has a valid widget allocation. + self.player = GstPlayer(self.videowidget, self.running_sugar) self.player.connect('eos', self._player_eos_cb) self.player.connect('error', self._player_error_cb) self.player.connect('stream-info', self._player_stream_info_cb) try: if not self.currentplaying: - logging.info('Playing: %s' % (self.playlist[0])) + debug_output('Playing: %s' % (self.playlist[0]), + self.running_sugar) self.player.set_uri(self.playlist[0]) self.currentplaying = 0 self.play_toggled() - self.show_all() - except: - logging.error('Error playing %s' % (self.playlist[0])) + except Exception, e: + error_output('Error playing %s: %s' % (self.playlist[0], e), + self.running_sugar) return False def play_toggled(self): @@ -191,9 +193,10 @@ class GstPlayer(gobject.GObject): 'eos': (gobject.SIGNAL_RUN_FIRST, None, []), 'stream-info': (gobject.SIGNAL_RUN_FIRST, None, [object])} - def __init__(self, videowidget): + def __init__(self, videowidget, running_sugar): gobject.GObject.__init__(self) + self.running_sugar = running_sugar self.playing = False self.error = False @@ -224,7 +227,8 @@ class GstPlayer(gobject.GObject): t = message.type if t == gst.MESSAGE_ERROR: err, debug = message.parse_error() - logging.debug('Error: %s - %s' % (err, debug)) + error_output('Error: %s - %s' % (err, debug), + self.running_sugar) self.error = True self.emit('eos') self.playing = False @@ -234,11 +238,10 @@ class GstPlayer(gobject.GObject): self.playing = False elif t == gst.MESSAGE_STATE_CHANGED: old, new, pen = message.parse_state_changed() - if old == gst.STATE_READY and new == gst.STATE_PAUSED: + if old == gst.STATE_READY and new == gst.STATE_PAUSED and \ + hasattr(self.player.props, 'stream_info_value_array'): self.emit('stream-info', self.player.props.stream_info_value_array) - # else: - # logging.debug(message.type) def _init_video_sink(self): self.bin = gst.Bin() @@ -275,19 +278,18 @@ class GstPlayer(gobject.GObject): def pause(self): self.player.set_state(gst.STATE_PAUSED) self.playing = False - logging.debug('pausing player') + # debug_output('pausing player', self.running_sugar) def play(self): self.player.set_state(gst.STATE_PLAYING) self.playing = True self.error = False - logging.debug('playing player') + # debug_output('playing player', self.running_sugar) def stop(self): self.player.set_state(gst.STATE_NULL) self.playing = False - logging.debug('stopped player') - # return False + # debug_output('stopped player', self.running_sugar) def get_state(self, timeout=1): return self.player.get_state(timeout=timeout) diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py index 7307b99..b3435e3 100644 --- a/TurtleArt/talogo.py +++ b/TurtleArt/talogo.py @@ -41,11 +41,12 @@ except ImportError: import traceback from tablock import (Block, Media, media_blocks_dictionary) -from taconstants import (TAB_LAYER, DEFAULT_SCALE, ICON_SIZE) +from taconstants import (TAB_LAYER, DEFAULT_SCALE, ICON_SIZE, Color) from tajail import (myfunc, myfunc_import) from tapalette import (block_names, value_blocks) from tatype import (TATypeError, TYPES_NUMERIC) from tautils import (get_pixbuf_from_journal, data_from_file, get_stack_name, + movie_media_type, audio_media_type, image_media_type, text_media_type, round_int, debug_output, find_group, get_path, image_to_base64, data_to_string, data_to_file, get_load_name, chooser_dialog) @@ -1081,6 +1082,7 @@ class LogoCode: """ Get contents of URL as text or tempfile to image """ if "://" not in url: # no protocol url = "http://" + url # assume HTTP + try: req = urllib2.urlopen(url) except urllib2.HTTPError, e: @@ -1097,12 +1099,12 @@ class LogoCode: self.tw.running_sugar) raise logoerror('#noconnection') - if req.info().getheader("Content-Type")[0:5] == "image": - # it can't be deleted immediately, or else we won't ever access it + mediatype = req.info().getheader('Content-Type') + if mediatype[0:5] in ['image', 'audio', 'video']: tmp = tempfile.NamedTemporaryFile(delete=False) - tmp.write(req.read()) # prepare for writing - tmp.flush() # actually write it - obj = Media('media', value=tmp.name) + tmp.write(req.read()) + tmp.flush() + obj = Media(mediatype[0:5], value=tmp.name) return obj else: return req.read() @@ -1120,19 +1122,29 @@ class LogoCode: def show(self, obj, center=False): """ Show is the general-purpose media-rendering block. """ - # media + mediatype = None + if isinstance(obj, Media) and obj.value: self.filepath = None self.pixbuf = None # Camera writes directly to pixbuf self.dsobject = None - # camera snapshot if obj.value.lower() in media_blocks_dictionary: media_blocks_dictionary[obj.value.lower()]() - # file path + mediatype = 'image' # camera snapshot elif os_path_exists(obj.value): self.filepath = obj.value - # datastore object + mediatype = obj.type + # If for some reason the obj.type is not set, try guessing. + if mediatype is None and self.filepath is not None: + if movie_media_type(self.filepath): + mediatype = 'video' + elif audio_media_type(self.filepath): + mediatype = 'audio' + elif image_media_type(self.filepath): + mediatype = 'image' + elif text_media_type(self.filepath): + mediatype = 'text' elif self.tw.running_sugar: from sugar.datastore import datastore try: @@ -1140,8 +1152,19 @@ class LogoCode: except: debug_output("Couldn't find dsobject %s" % (obj.value), self.tw.running_sugar) + if self.dsobject is not None: self.filepath = self.dsobject.file_path + if 'mime_type' in self.dsobject.metadata: + mimetype = self.dsobject.metadata['mime_type'] + if mimetype[0:5] == 'video': + mediatype = 'video' + elif mimetype[0:5] == 'audio': + mediatype = 'audio' + elif mimetype[0:5] == 'image': + mediatype = 'image' + else: + mediatype = 'text' if self.pixbuf is not None: self.insert_image(center=center, pixbuf=True) @@ -1152,36 +1175,40 @@ class LogoCode: self.dsobject.metadata['title']) else: self.tw.showlabel('nojournal', obj.value) + debug_output("Couldn't open %s" % (obj.value), self.tw.running_sugar) - elif obj.type == 'media': + elif obj.type == 'media' or mediatype == 'image': self.insert_image(center=center) - elif obj.type == 'descr': + elif mediatype == 'audio': + self.play_sound() + elif mediatype == 'video': + self.play_video() + elif obj.type == 'descr' or mediatype == 'text': mimetype = None if self.dsobject is not None and \ 'mime_type' in self.dsobject.metadata: mimetype = self.dsobject.metadata['mime_type'] + description = None if self.dsobject is not None and \ 'description' in self.dsobject.metadata: description = self.dsobject.metadata[ 'description'] + self.insert_desc(mimetype, description) - elif obj.type == 'audio': - self.play_sound() - elif obj.type == 'video': - self.play_video() if self.dsobject is not None: self.dsobject.destroy() - # text or number - elif isinstance(obj, (basestring, float, int)): + elif isinstance(obj, (basestring, float, int)): # text or number if isinstance(obj, (float, int)): obj = round_int(obj) + x, y = self.x2tx(), self.y2ty() if center: y -= self.tw.canvas.textsize + self.tw.turtles.get_active_turtle().draw_text( obj, x, y, int(self.tw.canvas.textsize * self.scale / 100.), diff --git a/TurtleArt/tapaletteview.py b/TurtleArt/tapaletteview.py new file mode 100644 index 0000000..945be0f --- /dev/null +++ b/TurtleArt/tapaletteview.py @@ -0,0 +1,409 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2014, Walter Bender + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#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. + +_SKIN_IMAGE = 1 +_MARGIN = 5 +_BUTTON_SIZE = 32 + +from tautils import find_group, debug_output, get_stack_width_and_height +from tablock import Block +from tapalette import (palette_names, palette_blocks, hidden_proto_blocks, + block_styles) +from taconstants import (PALETTE_SCALE, ICON_SIZE, PYTHON_SKIN, XO1, + HORIZONTAL_PALETTE, PALETTE_WIDTH, PALETTE_HEIGHT, + CATEGORY_LAYER, TOP_LAYER, PROTO_LAYER) +from tasprite_factory import SVG, svg_str_to_pixbuf +from sprites import Sprite + + +class PaletteView(): + ''' Palette View class abstraction ''' + + def __init__(self, turtle_window, n): + ''' + This class handles the display of block palettes + ''' + self.blocks = [] + self.backgrounds = [None, None] + self.visible = False + self.populated = False + + self._turtle_window = turtle_window + self._palette_index = n + + if not n < len(palette_names): + # Shouldn't happen, but hey... + debug_output('palette index %d is out of range' % n, + self._turtle_window.running_sugar) + self._name = 'undefined' + else: + self._name = palette_names[n] + + def create(self, regenerate=True, show=False): + if not self._name == 'undefined': + # Create proto blocks for each palette entry + self._create_proto_blocks() + + save_selected = self._turtle_window.selected_palette + self.layout(regenerate=regenerate, + show=(show or + save_selected == self._palette_index)) + + def show(self): + ''' Show palette background and proto blocks. If needed, display + shift button. ''' + orientation = self._turtle_window.orientation + if self.backgrounds[orientation] is not None: + self.backgrounds[orientation].set_layer(CATEGORY_LAYER) + + for blk in self.blocks: + if blk.get_visibility(): + blk.spr.set_layer(PROTO_LAYER) + else: + blk.spr.hide() + + self.display_palette_shift_buttons() + + self.visible = True + + def hide(self): + ''' Hide the palette. ''' + for background in self.backgrounds: + if background is not None: + background.hide() + + for blk in self.blocks: + blk.spr.hide() + + self._hide_palette_shift_buttons() + + if self._trash_palette(): + for blk in self._turtle_window.trash_stack: + for gblk in find_group(blk): + gblk.spr.hide() + + self.visible = False + + def move(self, x, y): + ''' Move the palette. ''' + buttons = self._turtle_window.palette_button + + for blk in self.blocks: + blk.spr.move((x + blk.spr.save_xy[0], y + blk.spr.save_xy[1])) + + for button in buttons: + button.move((x + button.save_xy[0], y + button.save_xy[1])) + + for spr in self.backgrounds: + if spr is not None: + spr.move((x + spr.save_xy[0], y + spr.save_xy[1])) + + if self._trash_palette(): + for blk in self._turtle_window.trash_stack: + for gblk in find_group(blk): + gblk.spr.move((x + gblk.spr.save_xy[0], + y + gblk.spr.save_xy[1])) + + def shift(self): + ''' Shift blocks on the palette. ''' + buttons = self._turtle_window.palette_button + orientation = self._turtle_window.orientation + + x, y = self.backgrounds[orientation].get_xy() + w, h = self.backgrounds[orientation].get_dimensions() + bx, by = self.blocks[0].spr.get_xy() + if orientation == 0: + width = self._turtle_window.width + + if bx != _BUTTON_SIZE: + dx = w - width + else: + dx = width - w + dy = 0 + else: + height = self._turtle_window.height + offset = self._turtle_window.toolbar_offset + + dx = 0 + if by != offset + _BUTTON_SIZE + _MARGIN: + dy = h - height + ICON_SIZE + else: + dy = height - h - ICON_SIZE + + for blk in self.blocks: + if blk.get_visibility(): + blk.spr.move_relative((dx, dy)) + + buttons[orientation].set_layer(TOP_LAYER) + if dx < 0 or dy < 0: + buttons[orientation + 5].set_layer(TOP_LAYER) + buttons[orientation + 3].hide() + else: + buttons[orientation + 5].hide() + buttons[orientation + 3].set_layer(TOP_LAYER) + + def _create_proto_blocks(self): + ''' + Create the proto blocks that will populate this palette. + Reload the palette, but reuse the existing blocks. + If a block doesn't exist, add it. + ''' + for blk in self.blocks: + blk.spr.hide() + + preexisting_blocks = self.blocks[:] + self.blocks = [] + for name in palette_blocks[self._palette_index]: + # Did we already create this block? + preexisting_block = False + for blk in preexisting_blocks: + if blk.name == name: + self.blocks.append(blk) + preexisting_block = True + break + + # If not, create it now. + if not preexisting_block: + self.blocks.append(Block(self._turtle_window.block_list, + self._turtle_window.sprite_list, + name, 0, 0, 'proto', [], + PALETTE_SCALE)) + if name in hidden_proto_blocks: + self.blocks[-1].set_visibility(False) + else: + self.blocks[-1].spr.set_layer(PROTO_LAYER) + self.blocks[-1].unhighlight() + self.blocks[-1].resize() + + # Some proto blocks get a skin. + if name in block_styles['box-style-media']: + self._proto_skin(name + 'small', self.blocks[-1].spr) + elif name in PYTHON_SKIN: + self._proto_skin('pythonsmall', self.blocks[-1].spr) + elif len(self.blocks[-1].spr.labels) > 0: + self.blocks[-1].refresh() + + self.populated = True + + def _proto_skin(self, name, spr): + ''' Utility for creating proto block skins ''' + x, y = self._turtle_window.calc_image_offset(name, spr) + spr.set_image(self._turtle_window.media_shapes[name], _SKIN_IMAGE, + x, y) + + def _float_palette(self, spr): + ''' We sometimes let the palette move with the canvas. ''' + if self._turtle_window.running_sugar and \ + not self._turtle_window.hw in [XO1]: + spr.move_relative( + (self._turtle_window.activity.hadj_value, + self._turtle_window.activity.vadj_value)) + + def _trash_palette(self): + return 'trash' in palette_names and \ + self._palette_index == palette_names.index('trash') + + def layout(self, regenerate=False, show=True): + ''' Layout prototypes in a palette. ''' + + offset = self._turtle_window.toolbar_offset + buttons = self._turtle_window.palette_button + orientation = self._turtle_window.orientation + w = PALETTE_WIDTH + h = PALETTE_HEIGHT + + if orientation == HORIZONTAL_PALETTE: + x, y, max_w = self._horizontal_layout( + _BUTTON_SIZE, offset + _MARGIN, self.blocks) + if self._trash_palette(): + blocks = [] # self.blocks[:] + for blk in self._turtle_window.trash_stack: + blocks.append(blk) + x, y, max_w = self._horizontal_layout(x + max_w, y, blocks) + w = x + max_w + _BUTTON_SIZE + _MARGIN + if show: + buttons[2].move((w - _BUTTON_SIZE, offset)) + buttons[4].move((_BUTTON_SIZE, offset)) + buttons[6].move((_BUTTON_SIZE, offset)) + else: + x, y, max_h = self._vertical_layout( + _MARGIN, offset + _BUTTON_SIZE + _MARGIN, self.blocks) + if self._trash_palette(): + blocks = [] # self.blocks[:] + for blk in self._turtle_window.trash_stack: + blocks.append(blk) + x, y, max_h = self._vertical_layout(x, y + max_h, blocks) + h = y + max_h + _BUTTON_SIZE + _MARGIN - offset + if show: + buttons[2].move((PALETTE_WIDTH - _BUTTON_SIZE, offset)) + buttons[3].move((0, offset + _BUTTON_SIZE)) + buttons[5].move((0, offset + _BUTTON_SIZE)) + + self._make_background(0, offset, w, h, regenerate) + + if show: + for blk in self.blocks: + if blk.get_visibility(): + blk.spr.set_layer(PROTO_LAYER) + else: + blk.spr.hide() + + buttons[2].save_xy = buttons[2].get_xy() + self._float_palette(buttons[2]) + self.backgrounds[orientation].set_layer(CATEGORY_LAYER) + self.display_palette_shift_buttons() + + if self._trash_palette(): + for blk in self._turtle_window.trash_stack: + for gblk in find_group(blk): + gblk.spr.set_layer(PROTO_LAYER) + + svg = SVG() + self.backgrounds[orientation].set_shape( + svg_str_to_pixbuf(svg.palette(w, h))) + + def _make_background(self, x, y, w, h, regenerate=False): + ''' Make the background sprite for the palette. ''' + orientation = self._turtle_window.orientation + + if regenerate and not self.backgrounds[orientation] is None: + self.backgrounds[orientation].hide() + self.backgrounds[orientation] = None + + if self.backgrounds[orientation] is None: + svg = SVG() + self.backgrounds[orientation] = \ + Sprite(self._turtle_window.sprite_list, x, y, + svg_str_to_pixbuf(svg.palette(w, h))) + self.backgrounds[orientation].save_xy = (x, y) + + self._float_palette(self.backgrounds[orientation]) + + if orientation == 0 and w > self._turtle_window.width: + self.backgrounds[orientation].type = \ + 'category-shift-horizontal' + elif orientation == 1 and \ + h > self._turtle_window.height - ICON_SIZE: + self.backgrounds[orientation].type = \ + 'category-shift-vertical' + else: + self.backgrounds[orientation].type = 'category' + + ''' + if self._trash_palette(): + svg = SVG() + self.backgrounds[orientation].set_shape( + svg_str_to_pixbuf(svg.palette(w, h))) + ''' + + def _horizontal_layout(self, x, y, blocks): + ''' Position prototypes in a horizontal palette. ''' + offset = self._turtle_window.toolbar_offset + max_w = 0 + + for blk in blocks: + if not blk.get_visibility(): + continue + + w, h = get_stack_width_and_height(blk) + if y + h > PALETTE_HEIGHT + offset: + x += int(max_w + 3) + y = 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))) + g.spr.save_xy = g.spr.get_xy() + self._float_palette(g.spr) + 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 blk in blocks: + if not blk.get_visibility(): + continue + + w, h = get_stack_width_and_height(blk) + 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)) + g.spr.save_xy = (g.spr.save_xy[0] + dx, + g.spr.save_xy[1]) + row = [] + row_w = 0 + x = 4 + y += int(max_h + 3) + max_h = 0 + + row.append(blk) + row_w += (4 + w) + + (bx, by) = blk.spr.get_xy() + dx = int(x - bx) + dy = int(y - by) + for g in find_group(blk): + g.spr.move_relative((dx, dy)) + g.spr.save_xy = g.spr.get_xy() + self._float_palette(g.spr) + + 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)) + g.spr.save_xy = (g.spr.save_xy[0] + dx, g.spr.save_xy[1]) + + return x, y, max_h + + def _hide_palette_shift_buttons(self): + buttons = self._turtle_window.palette_button + for i in range(4): + buttons[i + 3].hide() + + def display_palette_shift_buttons(self): + ''' Palettes too wide (or tall) for the screen get a shift button. ''' + self._hide_palette_shift_buttons() + + buttons = self._turtle_window.palette_button + orientation = self._turtle_window.orientation + + if self.backgrounds[orientation].type == 'category-shift-horizontal': + buttons[3].set_layer(CATEGORY_LAYER) + elif self.backgrounds[orientation].type == 'category-shift-vertical': + buttons[4].set_layer(CATEGORY_LAYER) diff --git a/TurtleArt/taselector.py b/TurtleArt/taselector.py new file mode 100644 index 0000000..5e680f4 --- /dev/null +++ b/TurtleArt/taselector.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2014, Walter Bender + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#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 os + +from tautils import debug_output, error_output +from tapalette import palette_names +from taconstants import ICON_SIZE, CATEGORY_LAYER, TAB_LAYER +from tasprite_factory import SVG, svg_from_file, svg_str_to_pixbuf +from sprites import Sprite + + +def create_toolbar_background(sprite_list, width): + # Create the toolbar background for the selectors + spr = Sprite(sprite_list, 0, 0, + svg_str_to_pixbuf(SVG().toolbar(2 * width, ICON_SIZE))) + spr.type = 'toolbar' + spr.set_layer(CATEGORY_LAYER) + return spr + + +class Selector(): + ''' Selector class abstraction ''' + + def __init__(self, turtle_window, n): + '''This class handles the display of palette selectors (Only relevant + to GNOME version and very old versions of Sugar). + ''' + + self.shapes = [] + self.spr = None + self._turtle_window = turtle_window + self._index = n + + if not n < len(palette_names): + # Shouldn't happen, but hey... + debug_output('palette index %d is out of range' % n, + self._turtle_window.running_sugar) + self._name = 'extras' + else: + self._name = palette_names[n] + + icon_pathname = None + for path in self._turtle_window.icon_paths: + if os.path.exists(os.path.join(path, '%soff.svg' % (self._name))): + icon_pathname = os.path.join(path, '%soff.svg' % (self._name)) + break + + if icon_pathname is not None: + off_shape = svg_str_to_pixbuf(svg_from_file(icon_pathname)) + else: + off_shape = svg_str_to_pixbuf(svg_from_file(os.path.join( + self._turtle_window.icon_paths[0], 'extrasoff.svg'))) + error_output('Unable to open %soff.svg' % (self._name), + self._turtle_window.running_sugar) + + icon_pathname = None + for path in self._turtle_window.icon_paths: + if os.path.exists(os.path.join(path, '%son.svg' % (self._name))): + icon_pathname = os.path.join(path, '%son.svg' % (self._name)) + break + + if icon_pathname is not None: + on_shape = svg_str_to_pixbuf(svg_from_file(icon_pathname)) + else: + on_shape = svg_str_to_pixbuf(svg_from_file(os.path.join( + self._turtle_window.icon_paths[0], 'extrason.svg'))) + error_output('Unable to open %son.svg' % (self._name), + self._turtle_window.running_sugar) + + self.shapes.append(off_shape) + self.shapes.append(on_shape) + + x = int(ICON_SIZE * self._index) + self.spr = Sprite(self._turtle_window.sprite_list, x, 0, off_shape) + self.spr.type = 'selector' + self.spr.name = self._name + self.set_layer() + + def set_shape(self, i): + if self.spr is not None and i in [0, 1]: + self.spr.set_shape(self.shapes[i]) + + def set_layer(self, layer=TAB_LAYER): + if self.spr is not None: + self.spr.set_layer(layer) + + def hide(self): + if self.spr is not None: + self.spr.hide() diff --git a/TurtleArt/tasprite_factory.py b/TurtleArt/tasprite_factory.py index e83ac30..21777df 100755 --- a/TurtleArt/tasprite_factory.py +++ b/TurtleArt/tasprite_factory.py @@ -1376,7 +1376,6 @@ def generator(datapath): svg_str = svg.basic_block() f.write(svg_str) close_file(f) - ''' svg = SVG() f = open_file(datapath, "basic1arg.svg") @@ -1386,10 +1385,7 @@ def generator(datapath): svg_str = svg.basic_block() f.write(svg_str) close_file(f) - print 'basic1arg.svg' - print svg.docks - ''' svg = SVG() f = open_file(datapath, "basic2arg.svg") svg.set_scale(2) @@ -1408,7 +1404,6 @@ def generator(datapath): f.write(svg_str) close_file(f) - ''' svg = SVG() f = open_file(datapath, "box.svg") svg.set_scale(2) @@ -1417,10 +1412,6 @@ def generator(datapath): f.write(svg_str) close_file(f) - print 'number.svg' - print svg.docks - ''' - svg = SVG() f = open_file(datapath, "media.svg") svg.set_scale(2) @@ -1513,6 +1504,7 @@ def generator(datapath): svg_str = svg.clamp() f.write(svg_str) close_file(f) + ''' svg = SVG() f = open_file(datapath, "clampn.svg") @@ -1526,6 +1518,7 @@ def generator(datapath): f.write(svg_str) close_file(f) + ''' svg = SVG() f = open_file(datapath, "clampe.svg") svg.set_scale(2) @@ -1537,6 +1530,7 @@ def generator(datapath): svg_str = svg.clamp() f.write(svg_str) close_file(f) + ''' svg = SVG() f = open_file(datapath, "clampb.svg") @@ -1561,7 +1555,6 @@ def generator(datapath): svg_str = svg.clamp_until() f.write(svg_str) close_file(f) - ''' def main(): diff --git a/TurtleArt/taturtle.py b/TurtleArt/taturtle.py index c3c7831..9845b07 100644 --- a/TurtleArt/taturtle.py +++ b/TurtleArt/taturtle.py @@ -28,7 +28,7 @@ import cairo from random import uniform from math import sin, cos, pi, sqrt from taconstants import (TURTLE_LAYER, DEFAULT_TURTLE_COLORS, DEFAULT_TURTLE, - Color) + CONSTANTS, Color, ColorObj) from tasprite_factory import SVG, svg_str_to_pixbuf from tacanvas import wrap100, COLOR_TABLE from sprites import Sprite @@ -368,9 +368,12 @@ class Turtle: def set_color(self, color=None, share=True): ''' Set the pen color for this turtle. ''' + if isinstance(color, ColorObj): + # See comment in tatype.py TYPE_BOX -> TYPE_COLOR + color = color.color if color is None: color = self._pen_color - # Special case for color blocks + # Special case for color blocks from CONSTANTS elif isinstance(color, Color): self.set_shade(color.shade, share) self.set_gray(color.gray, share) diff --git a/TurtleArt/tatype.py b/TurtleArt/tatype.py index 0fcfc3c..709a200 100644 --- a/TurtleArt/tatype.py +++ b/TurtleArt/tatype.py @@ -23,7 +23,7 @@ import ast from tablock import Media -from taconstants import (Color, CONSTANTS) +from taconstants import (Color, ColorObj, CONSTANTS) class Type(object): @@ -189,6 +189,7 @@ TYPE_CONVERTERS = { # converting A -> C must yield the same result as converting A -> B -> C. # TYPE_OBJECT is the supertype of everything. TYPE_BOX: { + TYPE_COLOR: ColorObj, # FIXME: should be Color.name TYPE_FLOAT: float, TYPE_INT: int, TYPE_NUMBER: float, diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py index 8e37f95..4c075ae 100644 --- a/TurtleArt/tautils.py +++ b/TurtleArt/tautils.py @@ -33,6 +33,7 @@ import pickle import subprocess import os import string +import mimetypes from gettext import gettext as _ try: @@ -483,24 +484,34 @@ def base64_to_image(data, path_name): def movie_media_type(name): ''' Is it movie media? ''' - return name.lower().endswith(('.ogv', '.vob', '.mp4', '.wmv', '.mov', - '.mpeg', '.ogg', '.webm')) + guess = mimetypes.guess_type(name) + if guess[0] is None: + return False + return guess[0][0:5] == 'video' def audio_media_type(name): ''' Is it audio media? ''' - return name.lower().endswith(('.oga', '.m4a')) + guess = mimetypes.guess_type(name) + if guess[0] is None: + return False + return guess[0][0:5] == 'audio' def image_media_type(name): ''' Is it image media? ''' - return name.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.tiff', - '.tif', '.svg')) + guess = mimetypes.guess_type(name) + if guess[0] is None: + return False + return guess[0][0:5] == 'image' def text_media_type(name): ''' Is it text media? ''' - return name.lower().endswith(('.txt', '.py', '.lg', '.rtf')) + guess = mimetypes.guess_type(name) + if guess[0] is None: + return False + return guess[0][0:4] == 'text' def round_int(num): @@ -803,6 +814,26 @@ def find_blk_below(blk, namelist): return None +def get_stack_width_and_height(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) + + def get_stack_name(blk): ''' Return the name of the action stack that the given block belongs to. If the top block of this stack is not a stack-defining block, return diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py index 09b9b30..d371e77 100644 --- a/TurtleArt/tawindow.py +++ b/TurtleArt/tawindow.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- #Copyright (c) 2007, Playful Invention Company -#Copyright (c) 2008-13, Walter Bender +#Copyright (c) 2008-14, Walter Bender #Copyright (c) 2009-11 Raúl Gutiérrez Segalés #Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/> @@ -52,20 +52,19 @@ from taconstants import (HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE, MEDIA_SHAPES, STATUS_SHAPES, OVERLAY_SHAPES, TOOLBAR_SHAPES, TAB_LAYER, RETURN, OVERLAY_LAYER, CATEGORY_LAYER, BLOCKS_WITH_SKIN, ICON_SIZE, - PALETTE_SCALE, PALETTE_WIDTH, SKIN_PATHS, MACROS, + PALETTE_WIDTH, SKIN_PATHS, MACROS, Color, KEY_DICT, TOP_LAYER, BLOCK_LAYER, OLD_NAMES, DEFAULT_TURTLE, TURTLE_LAYER, EXPANDABLE, NO_IMPORT, TEMPLATES, PYTHON_SKIN, PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK, EXPANDABLE_ARGS, XO1, XO15, XO175, XO30, XO4, TITLEXY, CONTENT_ARGS, CONSTANTS, EXPAND_SKIN, PROTO_LAYER, - EXPANDABLE_FLOW, SUFFIX, TMP_SVG_PATH, TMP_ODP_PATH, - Color, KEY_DICT) + EXPANDABLE_FLOW, SUFFIX, TMP_SVG_PATH, TMP_ODP_PATH) from tapalette import (palette_names, palette_blocks, expandable_blocks, block_names, content_blocks, default_values, special_names, block_styles, help_strings, - hidden_proto_blocks, string_or_number_args, - make_palette, palette_name_to_index, - palette_init_on_start, palette_i18n_names) + string_or_number_args, make_palette, + palette_name_to_index, palette_init_on_start, + palette_i18n_names) from talogo import (LogoCode, logoerror) from tacanvas import TurtleGraphics from tablock import (Blocks, Block, Media, media_blocks_dictionary) @@ -81,8 +80,10 @@ from tautils import (magnitude, get_load_name, get_save_name, data_from_file, error_output, find_hat, find_bot_block, restore_clamp, collapse_clamp, data_from_string, increment_name, get_screen_dpi) -from tasprite_factory import (SVG, svg_str_to_pixbuf, svg_from_file) +from tasprite_factory import (svg_str_to_pixbuf, svg_from_file) from tapalette import block_primitives +from tapaletteview import PaletteView +from taselector import (Selector, create_toolbar_background) from sprites import (Sprites, Sprite) if _GST_AVAILABLE: @@ -143,7 +144,7 @@ class TurtleArtWindow(): # Make sure macros_path is somewhere writable self.macros_path = os.path.join( os.path.expanduser('~'), 'Activities', - 'TurtleArt.activity', _MACROS_SUBPATH) + os.path.basename(path), _MACROS_SUBPATH) self._setup_events() else: self.interactive_mode = False @@ -243,16 +244,13 @@ class TurtleArtWindow(): self.status_spr = None self.status_shapes = {} self.toolbar_spr = None - self.palette_sprs = [] - self.palettes = [] self.palette_button = [] + self.palette_views = [] 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._highlighted_blk = None self.selected_blk = None self.selected_spr = None @@ -295,7 +293,7 @@ class TurtleArtWindow(): self._configure_cb(None) - self._icon_paths = [os.path.join(self.path, 'icons')] + self.icon_paths = [os.path.join(self.path, 'icons')] self.lc = LogoCode(self) @@ -370,25 +368,30 @@ class TurtleArtWindow(): else: return None - def _get_plugins_from_plugins_dir(self, path): + def _get_plugins_from_plugins_dir(self, paths): ''' Look for plugin files in plugin dir. ''' plugin_files = [] - if path is not None: + for path in paths: candidates = os.listdir(path) candidates.sort() for dirname in candidates: pname = os.path.join(path, dirname, dirname + '.py') if os.path.exists(pname): - plugin_files.append(dirname) + plugin_files.append({'dirname': dirname, 'path': path}) return plugin_files def _init_plugins(self): ''' Try importing plugin files from the plugin dir. ''' - plist = self._get_plugins_from_plugins_dir(self._get_plugin_home()) - for plugin_dir in plist: - self.init_plugin(plugin_dir) - - def init_plugin(self, plugin_dir): + homepath = os.path.join(os.path.expanduser('~'), 'Activities', + os.path.basename(self.path), _PLUGIN_SUBPATH) + paths = [self._get_plugin_home()] + if paths[0] != homepath and os.path.exists(homepath): + paths.append(homepath) + plist = self._get_plugins_from_plugins_dir(paths) + for plugin in plist: + self.init_plugin(plugin['dirname'], plugin['path']) + + def init_plugin(self, plugin_dir, plugin_path): ''' Initialize plugin in plugin_dir ''' plugin_class = plugin_dir.capitalize() f = 'def f(self): from plugins.%s.%s import %s; return %s(self)' \ @@ -401,8 +404,7 @@ class TurtleArtWindow(): debug_output('Successfully importing %s' % (plugin_class), self.running_sugar) # Add the icon dir to the icon_theme search path - self._add_plugin_icon_dir(os.path.join(self._get_plugin_home(), - plugin_dir)) + self._add_plugin_icon_dir(os.path.join(plugin_path, plugin_dir)) # Add the plugin to the list of global objects global_objects[plugin_class] = self.turtleart_plugins[-1] except Exception as e: @@ -415,7 +417,7 @@ class TurtleArtWindow(): icon_path = os.path.join(dirname, 'icons') if os.path.exists(icon_path): icon_theme.append_search_path(icon_path) - self._icon_paths.append(icon_path) + self.icon_paths.append(icon_path) def _get_plugin_instance(self, plugin_name): ''' Returns the plugin 'plugin_name' instance ''' @@ -770,7 +772,7 @@ class TurtleArtWindow(): ''' Change icon for user-defined blocks after loading Python code. ''' if blk is not None: if blk.name in PYTHON_SKIN: - x, y = self._calc_image_offset('pythonon', blk.spr) + x, y = self.calc_image_offset('pythonon', blk.spr) blk.set_image(self.media_shapes['pythonon'], x, y) self._resize_skin(blk) @@ -903,7 +905,7 @@ class TurtleArtWindow(): self.show_toolbar_palette(n) self.palette_button[self.orientation].set_layer(TAB_LAYER) self.palette_button[2].set_layer(TAB_LAYER) - self._display_palette_shift_button(n) + self.palette_views[n].display_palette_shift_buttons() if not self.running_sugar or not self.activity.has_toolbarbox: self.toolbar_spr.set_layer(CATEGORY_LAYER) self.palette = True @@ -920,26 +922,12 @@ class TurtleArtWindow(): def move_palettes(self, x, y): ''' Move the palettes. ''' - for p in self.palettes: - for blk in p: - blk.spr.move((x + blk.spr.save_xy[0], y + blk.spr.save_xy[1])) - for spr in self.palette_button: - spr.move((x + spr.save_xy[0], y + spr.save_xy[1])) - for p in self.palette_sprs: - if p[0] is not None: - p[0].move((x + p[0].save_xy[0], y + p[0].save_xy[1])) - if p[1] is not None: - p[1].move((x + p[1].save_xy[0], y + p[1].save_xy[1])) + for palette in self.palette_views: + palette.move(x, y) self.status_spr.move((x + self.status_spr.save_xy[0], y + self.status_spr.save_xy[1])) - # To do: set save_xy for blocks in Trash - for blk in self.trash_stack: - for gblk in find_group(blk): - gblk.spr.move((x + gblk.spr.save_xy[0], - y + gblk.spr.save_xy[1])) - def hideblocks(self): ''' Callback from 'hide blocks' block ''' if not self.interactive_mode: @@ -981,33 +969,8 @@ class TurtleArtWindow(): int(blocks[0].font_size[0] * pango.SCALE * self.entry_scale)) self._text_entry.modify_font(font_desc) - def _shift_toolbar_palette(self, n): - ''' Shift blocks on specified palette ''' - x, y = self.palette_sprs[n][self.orientation].get_xy() - w, h = self.palette_sprs[n][self.orientation].get_dimensions() - bx, by = self.palettes[n][0].spr.get_xy() - if self.orientation == 0: - if bx != _BUTTON_SIZE: - dx = w - self.width - else: - dx = self.width - w - dy = 0 - else: - dx = 0 - if by != self.toolbar_offset + _BUTTON_SIZE + _MARGIN: - dy = h - self.height + ICON_SIZE - else: - dy = self.height - h - ICON_SIZE - for blk in self.palettes[n]: - if blk.get_visibility(): - blk.spr.move_relative((dx, dy)) - self.palette_button[self.orientation].set_layer(TOP_LAYER) - if dx < 0 or dy < 0: - self.palette_button[self.orientation + 5].set_layer(TOP_LAYER) - self.palette_button[self.orientation + 3].hide() - else: - self.palette_button[self.orientation + 5].hide() - self.palette_button[self.orientation + 3].set_layer(TOP_LAYER) + def _has_selectors(self): + return not (self.running_sugar and self.activity.has_toolbarbox) def show_toolbar_palette(self, n, init_only=False, regenerate=False, show=True): @@ -1020,11 +983,10 @@ class TurtleArtWindow(): self._create_the_selectors() # Create the empty palettes that we'll then populate with prototypes. - if self.palette_sprs == []: + if self.palette_views == []: self._create_the_empty_palettes() - # At initialization of the program, we don't actually populate - # the palettes. + # At initialization of the program, we don't populate the palettes. if init_only: return @@ -1038,44 +1000,20 @@ class TurtleArtWindow(): self.selected_palette = n self.previous_palette = self.selected_palette - # Make sure all of the selectors are visible. (We don't need to do - # this for 0.86+ toolbars since the selectors are toolbar buttons.) - if show and \ - (not self.running_sugar or not self.activity.has_toolbarbox): - self.selected_selector = self.selectors[n] - self.selectors[n].set_shape(self.selector_shapes[n][1]) + # Make sure all of the selectors are visible. + if show and self._has_selectors(): + self.selected_selector = n + self.selectors[n].set_shape(1) for i in range(len(palette_blocks)): - self.selectors[i].set_layer(TAB_LAYER) + self.selectors[i].set_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( + if self.palette_views[n].backgrounds[self.orientation] is not None: + self.palette_views[n].backgrounds[self.orientation].set_layer( CATEGORY_LAYER) - self._display_palette_shift_button(n) - - # Create 'proto' blocks for each palette entry - self._create_proto_blocks(n) + self.palette_views[n].display_palette_shift_buttons() - if show or save_selected == n: - self._layout_palette(n, regenerate=regenerate) - else: - self._layout_palette(n, regenerate=regenerate, show=False) - for blk in self.palettes[n]: - if blk.get_visibility(): - if hasattr(blk.spr, 'set_layer'): - blk.spr.set_layer(PROTO_LAYER) - else: - debug_output('WARNING: block sprite is None' % (blk.name), - self.running_sugar) - else: - blk.spr.hide() - if 'trash' in palette_names and \ - n == palette_names.index('trash'): - for blk in self.trash_stack: - # Deprecated - for gblk in find_group(blk): - if gblk.status != 'collapsed': - gblk.spr.set_layer(TAB_LAYER) + self.palette_views[n].create(regenerate=regenerate, show=show) if not show: if not save_selected == n: @@ -1085,10 +1023,9 @@ class TurtleArtWindow(): def regenerate_palette(self, n): ''' Regenerate palette (used by some plugins) ''' - if (not self.running_sugar or not self.activity.has_toolbarbox) and \ - self.selectors == []: + if self._has_selectors() and self.selectors == []: return - if self.palette_sprs == []: + if self.palette_views == []: return save_selected = self.selected_palette @@ -1096,98 +1033,29 @@ class TurtleArtWindow(): self.selected_palette = n self.previous_palette = self.selected_palette - if save_selected == n: - self._layout_palette(n, regenerate=True) - else: - self._layout_palette(n, regenerate=True, show=False) - - for blk in self.palettes[n]: - if blk.get_visibility(): - if hasattr(blk.spr, 'set_layer'): - blk.spr.set_layer(PROTO_LAYER) - else: - debug_output('WARNING: block sprite is None' % (blk.name), - self.running_sugar) - else: - blk.spr.hide() + self.palette_views[n].layout(regenerate=True, + show=(save_selected == n)) if not save_selected == n: self._hide_previous_palette(palette=n) self.selected_palette = save_selected self.previous_palette = save_previous - def _display_palette_shift_button(self, n): - ''' Palettes too wide (or tall) for the screen get a shift button ''' - for i in range(4): - self.palette_button[i + 3].hide() - if self.palette_sprs[n][self.orientation].type == \ - 'category-shift-horizontal': - self.palette_button[3].set_layer(CATEGORY_LAYER) - elif self.palette_sprs[n][self.orientation].type == \ - 'category-shift-vertical': - self.palette_button[4].set_layer(CATEGORY_LAYER) - def _create_the_selectors(self): ''' Create the palette selector buttons: only when running old-style Sugar toolbars or from GNOME ''' - svg = SVG() - if self.running_sugar: - x, y = 50, 0 # positioned at the left, top - else: - x, y = 0, 0 - for i, name in enumerate(palette_names): - for path in self._icon_paths: - if os.path.exists(os.path.join(path, '%soff.svg' % (name))): - icon_pathname = os.path.join(path, '%soff.svg' % (name)) - break - if icon_pathname is not None: - off_shape = svg_str_to_pixbuf(svg_from_file(icon_pathname)) - else: - off_shape = svg_str_to_pixbuf( - svg_from_file( - os.path.join( - self._icon_paths[0], 'extrasoff.svg'))) - error_output('Unable to open %soff.svg' % (name), - self.running_sugar) - for path in self._icon_paths: - if os.path.exists(os.path.join(path, '%son.svg' % (name))): - icon_pathname = os.path.join(path, '%son.svg' % (name)) - break - if icon_pathname is not None: - on_shape = svg_str_to_pixbuf(svg_from_file(icon_pathname)) - else: - on_shape = svg_str_to_pixbuf( - svg_from_file( - os.path.join( - self._icon_paths[0], 'extrason.svg'))) - error_output('Unable to open %son.svg' % (name), - self.running_sugar) - - self.selector_shapes.append([off_shape, on_shape]) - self.selectors.append(Sprite(self.sprite_list, x, y, off_shape)) - 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) # running from left to right + for i in range(len(palette_names)): + self.selectors.append(Selector(self, i)) # Create the toolbar background for the selectors self.toolbar_offset = ICON_SIZE - self.toolbar_spr = Sprite(self.sprite_list, 0, 0, - svg_str_to_pixbuf(svg.toolbar(2 * self.width, - ICON_SIZE))) - self.toolbar_spr.type = 'toolbar' - self.toolbar_spr.set_layer(CATEGORY_LAYER) + self.toolbar_spr = create_toolbar_background(self.sprite_list, + self.width) def _create_the_empty_palettes(self): ''' Create the empty palettes to be populated by prototype blocks. ''' - if len(self.palettes) == 0: - for i in range(len(palette_blocks)): - self.palettes.append([]) - - # Create empty palette backgrounds - for i in palette_names: - self.palette_sprs.append([None, None]) + for i in range(len(palette_names)): + self.palette_views.append(PaletteView(self, i)) # Create the palette orientation button self.palette_button.append( @@ -1264,56 +1132,6 @@ class TurtleArtWindow(): self.palette_button[3 + i].type = 'palette' self.palette_button[3 + i].hide() - def _create_proto_blocks(self, n): - ''' Create the protoblocks that will populate a palette. ''' - # Reload the palette, but reuse the existing blocks - # If a block doesn't exist, add it - - if not n < len(self.palettes): - debug_output( - '_create_proto_blocks: palette index %d is out of range' % - (n), self.running_sugar) - return - - for blk in self.palettes[n]: - blk.spr.hide() - old_blocks = self.palettes[n][:] - self.palettes[n] = [] - for name in palette_blocks[n]: - found_block = False - for oblk in old_blocks: - if oblk.name == name: - self.palettes[n].append(oblk) - found_block = True - break - if not found_block: - self.palettes[n].append( - Block(self.block_list, self.sprite_list, name, 0, 0, - 'proto', [], PALETTE_SCALE)) - if name in hidden_proto_blocks: - self.palettes[n][-1].set_visibility(False) - else: - if hasattr(self.palettes[n][-1].spr, 'set_layer'): - self.palettes[n][-1].spr.set_layer(PROTO_LAYER) - self.palettes[n][-1].unhighlight() - else: - debug_output('WARNING: block sprite is None' % - (self.palettes[n][-1].name), - self.running_sugar) - - # Some proto blocks get a skin. - if name in block_styles['box-style-media']: - self._proto_skin(name + 'small', n, -1) - elif name[:8] == 'template': # Deprecated - self._proto_skin(name[8:], n, -1) - elif name[:7] == 'picture': # Deprecated - self._proto_skin(name[7:], n, -1) - elif name in PYTHON_SKIN: - self._proto_skin('pythonsmall', n, -1) - if len(self.palettes[n][-1].spr.labels) > 0: - self.palettes[n][-1].refresh() - return - def _hide_toolbar_palette(self): ''' Hide the toolbar palettes ''' self._hide_previous_palette() @@ -1332,171 +1150,13 @@ class TurtleArtWindow(): palette = self.previous_palette # Hide previously selected palette if palette is not None: - if not palette < len(self.palettes): - debug_output( - '_hide_previous_palette: index %d is out of range' % - (palette), self.running_sugar) - return - for proto in self.palettes[palette]: - proto.spr.hide() - if self.palette_sprs[palette][self.orientation] is not None: - self.palette_sprs[palette][self.orientation].hide() - if not self.running_sugar or not self.activity.has_toolbarbox: - self.selectors[palette].set_shape( - self.selector_shapes[palette][0]) + self.palette_views[palette].hide() + if self._has_selectors(): + self.selectors[palette].set_shape(0) elif palette is not None and palette != self.selected_palette \ and not self.activity.has_toolbarbox: self.activity.palette_buttons[palette].set_icon( palette_names[palette] + 'off') - if 'trash' in palette_names and \ - palette == palette_names.index('trash'): - 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: - if not blk.get_visibility(): - continue - w, h = self._width_and_height(blk) - if y + h > PALETTE_HEIGHT + self.toolbar_offset: - x += int(max_w + 3) - 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))) - g.spr.save_xy = g.spr.get_xy() - if self.running_sugar and not self.hw in [XO1]: - g.spr.move_relative((self.activity.hadj_value, - self.activity.vadj_value)) - 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 blk in blocks: - if not blk.get_visibility(): - continue - w, h = self._width_and_height(blk) - if x + w > PALETTE_WIDTH: - # Recenter row. - dx = int((PALETTE_WIDTH - row_w) / 2) - for r in row: - for g in find_group(r): - g.spr.move_relative((dx, 0)) - g.spr.save_xy = (g.spr.save_xy[0] + dx, - g.spr.save_xy[1]) - row = [] - row_w = 0 - x = 4 - y += int(max_h + 3) - max_h = 0 - row.append(blk) - row_w += (4 + w) - (bx, by) = blk.spr.get_xy() - dx = int(x - bx) - dy = int(y - by) - for g in find_group(blk): - g.spr.move_relative((dx, dy)) - g.spr.save_xy = g.spr.get_xy() - if self.running_sugar and not self.hw in [XO1]: - g.spr.move_relative((self.activity.hadj_value, - self.activity.vadj_value)) - 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)) - g.spr.save_xy = (g.spr.save_xy[0] + dx, g.spr.save_xy[1]) - return x, y, max_h - - def _layout_palette(self, n, regenerate=False, show=True): - ''' Layout prototypes in a palette. ''' - if n is not None: - if self.orientation == HORIZONTAL_PALETTE: - x, y = _BUTTON_SIZE, self.toolbar_offset + _MARGIN - x, y, max_w = self._horizontal_layout(x, y, self.palettes[n]) - if 'trash' in palette_names and \ - n == palette_names.index('trash'): - x, y, max_w = self._horizontal_layout(x + max_w, y, - self.trash_stack) - w = x + max_w + _BUTTON_SIZE + _MARGIN - self._make_palette_spr(n, 0, self.toolbar_offset, - w, PALETTE_HEIGHT, regenerate) - if show: - self.palette_button[2].move( - (w - _BUTTON_SIZE, self.toolbar_offset)) - self.palette_button[4].move( - (_BUTTON_SIZE, self.toolbar_offset)) - self.palette_button[6].move( - (_BUTTON_SIZE, self.toolbar_offset)) - else: - x, y = _MARGIN, self.toolbar_offset + _BUTTON_SIZE + _MARGIN - x, y, max_h = self._vertical_layout(x, y, self.palettes[n]) - if 'trash' in palette_names and \ - n == palette_names.index('trash'): - x, y, max_h = self._vertical_layout(x, y + max_h, - self.trash_stack) - h = y + max_h + _BUTTON_SIZE + _MARGIN - self.toolbar_offset - self._make_palette_spr(n, 0, self.toolbar_offset, - PALETTE_WIDTH, h, regenerate) - if show: - self.palette_button[2].move((PALETTE_WIDTH - _BUTTON_SIZE, - self.toolbar_offset)) - self.palette_button[3].move( - (0, self.toolbar_offset + _BUTTON_SIZE)) - self.palette_button[5].move( - (0, self.toolbar_offset + _BUTTON_SIZE)) - if show: - self.palette_button[2].save_xy = \ - self.palette_button[2].get_xy() - if self.running_sugar and not self.hw in [XO1]: - self.palette_button[2].move_relative( - (self.activity.hadj_value, self.activity.vadj_value)) - self.palette_sprs[n][self.orientation].set_layer( - CATEGORY_LAYER) - self._display_palette_shift_button(n) - - def _make_palette_spr(self, n, x, y, w, h, regenerate=False): - ''' Make the background for the palette. ''' - if regenerate and not self.palette_sprs[n][self.orientation] is None: - self.palette_sprs[n][self.orientation].hide() - self.palette_sprs[n][self.orientation] = None - if self.palette_sprs[n][self.orientation] is None: - svg = SVG() - self.palette_sprs[n][self.orientation] = \ - Sprite(self.sprite_list, x, y, svg_str_to_pixbuf( - svg.palette(w, h))) - self.palette_sprs[n][self.orientation].save_xy = (x, y) - if self.running_sugar and not self.hw in [XO1]: - self.palette_sprs[n][self.orientation].move_relative( - (self.activity.hadj_value, self.activity.vadj_value)) - if self.orientation == 0 and w > self.width: - self.palette_sprs[n][self.orientation].type = \ - 'category-shift-horizontal' - elif self.orientation == 1 and h > self.height - ICON_SIZE: - self.palette_sprs[n][self.orientation].type = \ - 'category-shift-vertical' - else: - self.palette_sprs[n][self.orientation].type = 'category' - if 'trash' in palette_names and \ - n == palette_names.index('trash'): - svg = SVG() - self.palette_sprs[n][self.orientation].set_shape( - svg_str_to_pixbuf(svg.palette(w, h))) def _buttonpress_cb(self, win, event): ''' Button press ''' @@ -1543,8 +1203,9 @@ class TurtleArtWindow(): elif self.interactive_mode: self.toolbar_shapes['stopiton'].set_layer(TAB_LAYER) self.showlabel('status', - label=_('Please hit the Stop Button \ -before making changes to your program')) + label=_('Please hit the Stop Button ' + 'before making changes to your ' + 'program')) self._autohide_shape = True return True @@ -1859,10 +1520,10 @@ before making changes to your program')) return i = palette_names.index('myblocks') palette_blocks[i].remove(blk.name) - for pblk in self.palettes[i]: + for pblk in self.palette_views[i].blocks: if pblk.name == blk.name: pblk.spr.hide() - self.palettes[i].remove(pblk) + self.palette_views[i].blocks.remove(pblk) break self.show_toolbar_palette(i, regenerate=True) @@ -1896,7 +1557,7 @@ before making changes to your program')) i = 0 if not self.running_sugar or \ not self.activity.has_toolbarbox: - self._select_category(self.selectors[i]) + self._select_category(self.selectors[i].spr) else: if self.selected_palette is not None and \ not self.activity.has_toolbarbox: @@ -1912,7 +1573,7 @@ before making changes to your program')) palette_names[i] + 'on') self.show_palette(i) elif spr.name == _('shift'): - self._shift_toolbar_palette(self.selected_palette) + self.palette_views[self.selected_palette].shift() else: self.set_orientation(1 - self.orientation) elif spr.type == 'toolbar': @@ -1923,10 +1584,11 @@ before making changes to your program')) self.orientation = orientation self.palette_button[self.orientation].set_layer(TAB_LAYER) self.palette_button[1 - self.orientation].hide() - spr = self.palette_sprs[self.selected_palette][1 - self.orientation] + o = 1 - self.orientation + spr = self.palette_views[self.selected_palette].backgrounds[o] if spr is not None: spr.hide() - self._layout_palette(self.selected_palette) + self.palette_views[self.selected_palette].layout() self.show_palette(self.selected_palette) def _update_action_names(self, name): @@ -2017,7 +1679,7 @@ before making changes to your program')) block_names[new] = name i = palette_name_to_index(palette) - for blk in self.palettes[i]: + for blk in self.palette_views[i].blocks: if blk.name == old: blk.name = new blk.spr.labels[label] = name @@ -2069,16 +1731,15 @@ before making changes to your program')) def _select_category(self, spr): ''' Select a category from the toolbar ''' - i = self.selectors.index(spr) - spr.set_shape(self.selector_shapes[i][1]) - if self.selected_selector is not None: - 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) + for i, selector in enumerate(self.selectors): + if selector.spr == spr: + break + self.selectors[i].set_shape(1) + + if i != self.selected_selector: + self.selectors[self.selected_selector].set_shape(0) + self.selected_selector = i + self.show_palette(i) def _select_toolbar_button(self, spr): ''' Select a toolbar button (Used when not running Sugar). ''' @@ -2169,10 +1830,10 @@ before making changes to your program')) i = palette_name_to_index('blocks') if name in palette_blocks[i]: palette_blocks[i].remove(name) - for blk in self.palettes[i]: + for blk in self.palette_views[i].blocks: if blk.name == name: blk.spr.hide() - self.palettes[i].remove(blk) + self.palette_views[i].blocks.remove(blk) self.show_toolbar_palette(i, regenerate=True) if name in block_styles[style]: block_styles[style].remove(name) @@ -2249,10 +1910,11 @@ before making changes to your program')) def _in_the_trash(self, x, y): ''' Is x, y over a palette? ''' - if self.selected_palette is not None and \ - self.palette_sprs[self.selected_palette][self.orientation]\ - .hit((x, y)): - return True + n = self.selected_palette + if n is not None: + spr = self.palette_views[n].backgrounds[self.orientation] + if spr.hit((x, y)): + return True return False def _block_pressed(self, x, y, blk): @@ -3687,7 +3349,7 @@ before making changes to your program')) else: blk.name = 'description' if pixbuf is not None: - x, y = self._calc_image_offset('', blk.spr) + x, y = self.calc_image_offset('', blk.spr) blk.set_image(pixbuf, x, y) self._resize_skin(blk) @@ -3725,7 +3387,7 @@ before making changes to your program')) print 'selected palette is None' return True else: - p = self.palettes[self.selected_palette] + p = self.palette_views[self.selected_palette].blocks i = 0 if self._highlighted_blk is not None: self._highlighted_blk.unhighlight() @@ -4320,7 +3982,7 @@ before making changes to your program')) 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) + x, y = self.calc_image_offset('', blk.spr) blk.set_image(pixbuf, x, y) else: self._block_skin('journalon', blk) @@ -4330,7 +3992,7 @@ before making changes to your program')) 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) + x, y = self.calc_image_offset('', blk.spr) blk.set_image(pixbuf, x, y) except: debug_output('Could not open dsobject (%s)' % @@ -4342,7 +4004,7 @@ before making changes to your program')) 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) + x, y = self.calc_image_offset('', blk.spr) blk.set_image(pixbuf, x, y) except: self._block_skin('journaloff', blk) @@ -4720,7 +4382,7 @@ before making changes to your program')) if '.odp' not in name: name = name + '.odp' if name is not None: - res = subprocess.check_output( + subprocess.check_output( ['cp', TMP_ODP_PATH, os.path.join(datapath, name)]) @@ -4916,7 +4578,7 @@ before making changes to your program')) # Utilities related to putting a image 'skin' on a block - def _calc_image_offset(self, name, spr, iw=0, ih=0): + 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 == '': @@ -4947,12 +4609,14 @@ before making changes to your program')) 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) + x, y = self.calc_image_offset(name, + self.palette_views[n].blocks[i].spr) + self.palette_views[n].blocks[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) + x, y = self.calc_image_offset(name, blk.spr) blk.set_image(self.media_shapes[name], x, y) self._resize_skin(blk) @@ -4960,19 +4624,19 @@ before making changes to your program')) ''' 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) + 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) + 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) + x, y = self.calc_image_offset('journaloff', blk.spr, w, h) else: # w, h = self._calc_w_h('descriptionoff', blk.spr) w, h = self._calc_w_h('', blk.spr) - # x, y = self._calc_image_offset('descriptionoff', blk.spr, w, h) - x, y = self._calc_image_offset('', blk.spr, w, h) + # x, y = self.calc_image_offset('descriptionoff', blk.spr, w, h) + x, y = self.calc_image_offset('', blk.spr, w, h) blk.scale_image(x, y, w, h) def _find_proto_name(self, name, label, palette='blocks'): @@ -4984,7 +4648,7 @@ before making changes to your program')) if isinstance(label, unicode): label = label.encode('utf-8') i = palette_name_to_index(palette) - for blk in self.palettes[i]: + for blk in self.palette_views[i].blocks: blk_label = blk.spr.labels[0] if isinstance(blk.name, unicode): blk.name = blk.name.encode('utf-8') diff --git a/turtleblocks.py b/turtleblocks.py index 3483169..fa324ce 100755 --- a/turtleblocks.py +++ b/turtleblocks.py @@ -243,8 +243,7 @@ return %s(self)" % (p, P, P) if hasattr(self, 'client'): if self.client.get_int(self._HOVER_HELP) == 1: - self.hover.set_active(False) - self._do_hover_help_off_cb(None) + self._do_hover_help_off_cb() if not self.client.get_int(self._COORDINATE_SCALE) in [0, 1]: self.tw.coord_scale = 1 else: @@ -748,22 +747,18 @@ Would you like to save before quitting?')) ''' Toggle hover help on/off ''' self.tw.no_help = not self.tw.no_help if self.tw.no_help: - self._do_hover_help_off_cb(None) + self._do_hover_help_off_cb() else: - self._do_hover_help_on_cb(None) + self._do_hover_help_on_cb() - def _do_hover_help_on_cb(self, button): + def _do_hover_help_on_cb(self): ''' Turn hover help on ''' - self.tw.no_help = False self.hover.set_active(True) if hasattr(self, 'client'): self.client.set_int(self._HOVER_HELP, 0) - def _do_hover_help_off_cb(self, button): + def _do_hover_help_off_cb(self): ''' Turn hover help off ''' - if self.tw.no_help: # Debounce - return - self.tw.no_help = True self.tw.last_label = None if self.tw.status_spr is not None: self.tw.status_spr.hide() |