Source code for enable.tools.pyface.commands

# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
"""
Enable Commands
===============

This module provides :py:class:`pyface.undo.abstract_command.AbstractCommand`
subclasses for common component manipulations, such as moving, resizing and
setting attribute values.

"""

from pyface.undo.api import AbstractCommand
from traits.api import Bool, Instance, Tuple, Unicode
from traits.util.camel_case import camel_case_to_words

from enable.component import Component


[docs]class ComponentCommand(AbstractCommand): """ Abstract command which operates on a Component """ #: The component the command is being performed on. component = Instance(Component) #: An appropriate name for the component that can be used by the command. #: The default is the class name, split into words. component_name = Unicode def __init__(self, component, **traits): super().__init__(component=component, **traits) # ------------------------------------------------------------------------- # traits handlers # ------------------------------------------------------------------------- def _component_name_default(self): if self.component is not None: return camel_case_to_words(self.component.__class__.__name__) return ""
[docs]class ResizeCommand(ComponentCommand): """ Command for resizing a component This handles the logic of moving a component and merging successive moves. This class provides ``_change_rectangle`` and ``_merge_data`` methods that subclasses can override to change the reszie behaviour in a uniform way for undo and redo operations. Parameters ---------- component : Component instance The component being moved. new_rectangle : tuple of (x, y, w, h) The rectangle representing the new position and bounds. previous_rectangle : tuple of (x, y, w, h) The rectangle representing the previous position and bounds. **traits : Any other trait values that need to be passed in at creation time. The ``new_rectangle`` argument is the same as the ``data`` trait on the class. If both are provided, the ``new_rectangle`` value is used. """ #: The new rectangle of the component as a tuple (x, y, width, height). data = Tuple #: The old rectangle of the component as a tuple (x, y, width, height). previous_rectangle = Tuple #: whether additional resizes can be merged or if the resize is finished. mergeable = Bool def __init__(self, component, new_rectangle=None, previous_rectangle=None, **traits): if previous_rectangle is None: previous_rectangle = tuple(component.position) + tuple( component.bounds ) if new_rectangle is None: if "data" in traits: data = traits.pop("data") else: raise TypeError( "ResizeCommand __init__ method requires " "'new_rectangle' argument." ) else: data = new_rectangle super().__init__( component=component, data=data, previous_rectangle=previous_rectangle, **traits, )
[docs] @classmethod def move_command(cls, component, new_position, previous_position=None, **traits): """ Factory that creates a ResizeCommand implementing a move operation This allows a MoveTool to create move commands that can be easily merged with resize commands. """ bounds = tuple(component.bounds) new_rectangle = new_position + bounds if previous_position is not None: previous_rectangle = previous_position + bounds else: previous_rectangle = None return cls( component=component, new_rectangle=new_rectangle, previous_rectangle=previous_rectangle, **traits, )
# ------------------------------------------------------------------------- # AbstractCommand interface # -------------------------------------------------------------------------
[docs] def merge(self, other): if (self.mergeable and isinstance(other, self.__class__) and other.component == self.component): return self._merge_data(other) return super().merge(other)
[docs] def do(self): if self.previous_rectangle == (): self.previous_rectangle = tuple( self.component.position + self.component.bounds ) self.redo()
[docs] def redo(self): self._change_rectangle(self.data)
[docs] def undo(self): self._change_rectangle(self.previous_rectangle)
# ------------------------------------------------------------------------- # Private interface # ------------------------------------------------------------------------- def _change_rectangle(self, rectangle): x, y, w, h = rectangle self.component.position = [x, y] self.component.bounds = [w, h] self.component._layout_needed = True self.component.request_redraw() def _merge_data(self, other): self.data = other.data self.mergeable = other.mergeable return True # ------------------------------------------------------------------------- # traits handlers # ------------------------------------------------------------------------- def _name_default(self): return "Resize " + self.component_name
[docs]class MoveCommand(ComponentCommand): """ A command that moves a component This handles the logic of moving a component and merging successive moves. This class provides ``_change_position`` and ``_merge_data`` methods that subclasses can override to change the move behaviour in a uniform way for undo and redo operations. Parameters ---------- component : Component instance The component being moved. new_position : tuple of (x, y) The tuple representing the new position. previous_position : tuple of (x, y) The tuple representing the previous position. **traits : Any other trait values that need to be passed in at creation time. The ``new_position`` argument is the same as the ``data`` trait on the class. If both are provided, the ``new_position`` value is used. """ #: The new position of the component as a tuple (x, y). data = Tuple #: The old position of the component as a tuple (x, y). previous_position = Tuple #: whether additional moves can be merged or if the move is finished. mergeable = Bool def __init__(self, component, new_position=None, previous_position=None, **traits): if previous_position is None: previous_position = component.position if new_position is None: if "data" in traits: data = traits.pop("data") else: raise TypeError( "MoveCommand __init__ method requires " "'new_position' argument." ) else: data = new_position super().__init__( component=component, data=data, previous_position=previous_position, **traits, ) # ------------------------------------------------------------------------- # AbstractCommand interface # -------------------------------------------------------------------------
[docs] def do(self): self.redo()
[docs] def redo(self): self._change_position(self.data)
[docs] def undo(self): self._change_position(self.previous_position)
[docs] def merge(self, other): if (self.mergeable and isinstance(other, self.__class__) and other.component == self.component): return self._merge_data(other) return super().merge(other)
# ------------------------------------------------------------------------- # Private interface # ------------------------------------------------------------------------- def _change_position(self, position): self.component.position = list(position) self.component._layout_needed = True self.component.request_redraw() def _merge_data(self, other): self.data = other.data self.mergeable = other.mergeable return True # ------------------------------------------------------------------------- # traits handlers # ------------------------------------------------------------------------- def _name_default(self): return "Move " + self.component_name