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