# -*- 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)