def create_item(self, component, coords=(), item=None, canvas=None, silently=False, **kwargs): canvas = canvas or self.canvas if item is None: opts = dict(**component.defaults) opts.update(kwargs) item = component(canvas, *coords, **opts) # generate a unique id item.name = self.generator.generate(component, self._ids) canvas._cv_items.append(item) item._prev_index = canvas._cv_items.index(item) node = canvas._cv_tree.add_as_node(item=item) item.bind("<ButtonRelease-1>", lambda e: self._handle_select(item, e), True) item.bind("<ButtonRelease-1>", lambda e: self._handle_end(item, e), True) item.bind("<Motion>", lambda e: self._handle_move(item, e), True) MenuUtils.bind_context(item, self._show_item_menu(item)) MenuUtils.bind_all_context(node, self._show_item_menu(item)) if not silently: self.studio.new_action( Action(lambda _: self.remove_items([item], silently=True), lambda _: self.restore_items([item]))) return item
def on_layout_change(self): prev_data = {item: item._coord_restore for item in self.selected_items} data = {item: item.coords() for item in self.selected_items} for item in self.selected_items: item._coord_restore = item.coords() self.studio.new_action( Action(lambda _: self.restore_layouts(prev_data), lambda _: self.restore_layouts(data)))
def paste_items(self, _clipboard=None): _clipboard = self._clipboard if _clipboard is None else _clipboard if _clipboard: items = [] for item_data in _clipboard: item = CanvasItem.from_data(self.canvas, item_data, self._latch_pos) self.create_item(item.__class__, item=item, silently=True) items.append(item) # slightly displace latch position for next paste self._latch_pos = tuple(map(lambda x: x + 5, self._latch_pos)) self.studio.new_action( Action(lambda _: self.remove_items(items, silently=True), lambda _: self.restore_items(items)))
def remove_items(self, items, silently=False): if not items: return # ideally all items will have the same canvas canvas = items[0].canvas items = sorted(items, key=canvas._cv_items.index) self.deselect_items(items) for item in items: item.hide() canvas._cv_items.remove(item) item.node.remove() if not silently: self.studio.new_action( Action(lambda _: self.restore_items(items), lambda _: self.remove_items(items, silently=True)))
def create_restore(self, widget): prev_restore_point = widget.recent_layout_info cur_restore_point = widget.layout.get_restore(widget) if prev_restore_point == cur_restore_point: return prev_container = prev_restore_point["container"] container = widget.layout def undo(_): container.remove_widget(widget) prev_container.restore_widget(widget, prev_restore_point) def redo(_): prev_container.remove_widget(widget) container.restore_widget(widget, cur_restore_point) self.studio.new_action(Action(undo, redo))
def add(self, obj_class: PseudoWidget.__class__, x, y, **kwargs): if obj_class.group != Groups.container and self.root_obj is None: # We only need a container as the root widget self._show_root_widget_warning() return silent = kwargs.get("silently", False) name = self._get_unique(obj_class) obj = obj_class(self, name) if hasattr(obj, 'initial_dimensions'): width, height = obj.initial_dimensions else: width = kwargs.get( "width", self._base_font.measure(name) + self.WIDGET_INIT_PADDING) height = kwargs.get("height", self.WIDGET_INIT_HEIGHT) obj.layout = kwargs.get("intended_layout", None) self._attach(obj) # apply extra bindings required layout = kwargs.get("layout") # If the object has a layout which actually the layout at the point of creation prepare and pass it # to the layout if isinstance(layout, Container): bounds = (x, y, x + width, y + height) bounds = geometry.resolve_bounds(bounds, self) layout.add_widget(obj, bounds) self.studio.add(obj, layout) restore_point = layout.get_restore(obj) # Create an undo redo point if add is not silent if not silent: self.studio.new_action( Action( # Delete silently to prevent adding the event to the undo/redo stack lambda _: self.delete(obj, True), lambda _: self.restore(obj, restore_point, obj.layout)) ) elif obj.layout is None: # This only happens when adding the main layout. We dont need to add this action to the undo/redo stack # This main layout is attached directly to the designer obj.layout = self self.layout_strategy.add_widget(obj, x=x, y=y, width=width, height=height) self.studio.add(obj, None) return obj
def paste(self, node, silently=False): if not self.current_obj: return layout = self.current_obj if isinstance(self.current_obj, Container) else self.current_obj.layout width = int(BaseConverter.get_attr(node, "width", "layout") or 0) height = int(BaseConverter.get_attr(node, "height", "layout") or 0) x, y = self._last_click_pos or (self.winfo_rootx() + 50, self.winfo_rooty() + 50) self._last_click_pos = x + 5, y + 5 # slightly displace click position so multiple pastes are still visible bounds = geometry.resolve_bounds((x, y, x + width, y + height), self) obj = self.xml.load_section(node, layout, bounds) restore_point = layout.get_restore(obj) # Create an undo redo point if add is not silent if not silently: self.studio.new_action(Action( # Delete silently to prevent adding the event to the undo/redo stack lambda: self.delete(obj, True), lambda: self.restore(obj, restore_point, obj.layout) )) return obj
def delete(self, widget, silently=False): if not widget: return if not silently: restore_point = widget.layout.get_restore(widget) self.studio.new_action(Action( lambda: self.restore(widget, restore_point, widget.layout), lambda: self.studio.delete(widget) )) else: self.studio.delete(widget, self) widget.layout.remove_widget(widget) if widget == self.root_obj: # try finding another toplevel widget that can be a root obj otherwise leave it as none self.root_obj = None for w in self.layout_strategy.children: if isinstance(w, Container): self.root_obj = w break self._uproot_widget(widget)
def _update_stacking(self, canvas, data=None, silently=False): if data: canvas._cv_tree.reorder(data) else: data = {} canvas._cv_items.sort( key=lambda x: canvas._cv_tree.nodes.index(x.node)) prev_data = {} for index, item in enumerate(canvas._cv_items): if item._prev_index != index: # old data prev_data[item] = item._prev_index # new data data[item] = index item._prev_index = index if index > 0: item.lift(canvas._cv_items[index - 1]._id) if not silently and prev_data != data: self.studio.new_action( Action( lambda _: self._update_stacking(canvas, prev_data, True), lambda _: self._update_stacking(canvas, data, True)))
def apply(self, prop, value, widget=None, silent=False): is_external = widget is not None widget = self.widget if widget is None else widget if widget is None: return try: prev_val = self._get_prop(prop, widget) data = self._get_action_data(widget, prop) self._set_prop(prop, value, widget) new_data = self._get_action_data(widget, prop) self.style_pane.widget_modified(widget) if is_external: if widget == self.widget: self.items[prop].set_silently(value) if silent: return key = self._get_key(widget, prop) action = self.style_pane.last_action() if action is None or action.key != key: self.style_pane.new_action( Action( lambda _: self._apply_action(prop, prev_val, widget, data), lambda _: self._apply_action(prop, value, widget, new_data), key=key, )) else: action.update_redo(lambda _: self._apply_action( prop, value, widget, new_data)) except Exception as e: # Empty string values are too common to be useful in logger debug if value != '': logging.error(e) logging.error( f"Could not set {self.__class__.__name__} {prop} as {value}", )
def create_restore(self, widget): restore_point = widget.layout.get_restore() self.studio.new_action(Action( lambda: self.restore(widget, restore_point, widget.layout), lambda: self.studio.delete(widget) ))