-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #167 from enthought/feature/apptools-undo-tools
Enable integration with Apptools Undo/Redo
- Loading branch information
Showing
21 changed files
with
1,616 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
Enable Apptools Integration | ||
=========================== | ||
|
||
Apptools (https://github.com/enthought/apptools) is a library of useful code | ||
for building GUI applications. It includes code for features like preferences, | ||
undo/redo support, and selection management. | ||
|
||
Enable doesn't require Apptools, but developers working within the apptools | ||
ecosystem may want to integrate Enable interactions with undo/redo and | ||
selections. | ||
|
||
Undo/Redo Support | ||
----------------- | ||
|
||
The `enable.tools.apptools` package has a number of modules that provide | ||
classes for working with Apptool's Undo/Redo stack. This permits Enable | ||
tools to add Commands to the Undo/Redo stack, and provides variants of the | ||
MoveTool and ResizeTool that are undoable. | ||
|
||
In addition, a tool is provided which binds keystrokes to send undo and | ||
redo requests to the apptools UndoManager. | ||
|
||
High-Level Tools | ||
~~~~~~~~~~~~~~~~ | ||
|
||
There are three tools that provide convenient facilities and reference | ||
implementations of interacting with the undo/redo stack. | ||
|
||
``UndoTool`` | ||
|
||
The ``UndoTool`` binds keystrokes to undo and redo operations. The | ||
``undo_keys`` and ``redo_keys`` attributes each take a list of ``KeySpec`` | ||
objects which should trigger the relevant operations. The default | ||
values bind undo to 'Ctrl+Z' and redo to 'Ctrl+Shift+Z'. | ||
|
||
The ``UndoTool`` must be provided with an ``IUndoManager`` that will | ||
actually perform the undo and redo operations. | ||
|
||
For example, to bind undo to 'Ctrl+Left arrow', and redo to 'Ctrl+Right | ||
arrow':: | ||
|
||
undo_tool = UndoTool( | ||
my_component, | ||
undo_manager=my_undo_manager, | ||
undo_keys=[KeySpec('Left', 'control')], | ||
redo_keys=[KeySpec('Right', 'control')] | ||
) | ||
my_component.tools.append(undo_tool) | ||
|
||
``MoveCommandTool`` | ||
|
||
The ``MoveCommandTool`` is a subclass of ``MoveTool`` that by default | ||
issues a ``MoveCommand`` at the end of every successful drag move. | ||
A ``MoveCommand`` stores the new and previous position of the | ||
component so that it can undo and redo the move. The ``MoveCommandTool`` | ||
needs to be provided with an ``ICommandStack`` instance that it will | ||
push commands to, but is otherwise identical to the usual ``MoveTool``. | ||
|
||
The command tool has a ``mergeable`` attribute which indicates whether | ||
subsequent move operations with the same component immediately following | ||
this one can be merged into one single move operation. | ||
|
||
Typical usage would be something like this:: | ||
|
||
move_tool = MoveCommandTool(my_component, command_stack=my_command_stack) | ||
my_component.tools.append(move_tool) | ||
|
||
Users of the tool can provide a different factory to create appropriate | ||
``Command`` instances by setting the ``command`` trait to a callable | ||
that should expect keyword arguments ``component``, ``data`` (the new | ||
position), ``previous_position``, and ``mergeable``. | ||
|
||
``ResizeCommandTool`` | ||
|
||
The ``ResizeCommandTool`` is a subclass of ``ResizeTool`` that issues | ||
``ResizeCommand`` s at the end of every successful drag move. | ||
A ``ResizeCommand`` stores the new and previous position and bounds of the | ||
component so that it can undo and redo the resize. The | ||
``ResizeCommandTool`` needs to be provided with an ``ICommandStack`` | ||
instance that it will push commands to, but is otherwise identical to the | ||
usual ``ResizeTool``. | ||
|
||
The command tool has a ``mergeable`` attribute which indicates whether | ||
subsequent resize operations with the same component immediately following | ||
this one can be merged into one single resize operation. | ||
|
||
Typical usage would be something like this:: | ||
|
||
move_tool = ResizeTool(my_component, command_stack=my_command_stack) | ||
my_component.tools.append(move_tool) | ||
|
||
Users of the tool can provide a different factory to create appropriate | ||
``Command`` instances by setting the ``command`` trait to a callable | ||
that should expect keyword arguments ``component``, ``data`` (the new | ||
rectangle as a tuple ``(x, y, width, height)``), ``previous_rectangle``, | ||
and ``mergeable``. | ||
|
||
Command Classes | ||
~~~~~~~~~~~~~~~ | ||
|
||
The library provides some useful ``Command`` subclasses that users may want | ||
to create specialized instances or subclass to customize the behaviour | ||
of their applications. They may also be of use to ``CommandAction`` subclasses | ||
outside of the Enable framework (such as menu items or toolbar buttons) which | ||
want to interact with Enable components. | ||
|
||
``ResizeCommand`` | ||
|
||
This command handles changing the size of a component. The constructor | ||
expects arguments ``component``, ``new_rectangle`` and (optionally) | ||
``previous_rectangle``, plus optional additional traits. If | ||
``previous_rectangle`` is not provided, then the component's current | ||
rectangle is used. | ||
|
||
Instances hold references to the ``Component`` being resized in the | ||
``component`` attribute, the new and previous rectangles of the component | ||
as tuples ``(x, y, width, height)`` in the ``data`` and | ||
``previous_rectangle`` attributes, and whether or not subsequent resize | ||
operations on the same component should be merged together. | ||
|
||
The tool handles the logic of changing the position and bounds of the | ||
component appropriately, as well as invalidating layout and requesting | ||
redraws. | ||
|
||
It also provides a default ``name`` attribute of ``Resize `` plus the | ||
``component_name`` (which in turn defaults to a more human-readable | ||
variant of the component's class). Instances can improve this by | ||
either supplying a full replacement for the ``name`` attribute, or | ||
for the ``component_name``. | ||
|
||
Finally, there is a ``move_command`` class method that creates a | ||
``ResizeCommand`` that just performs a move and is suitable as the | ||
command factory of a ``MoveCommandTool``, which allows easy merging | ||
between resize and move operations, if required for the application. | ||
|
||
``MoveCommand`` | ||
|
||
This command handles changing the position of a component. The constructor | ||
expects arguments ``component``, ``previous_position`` and (optionally) | ||
``new_position``, plus optional additional traits. If ``new_position`` | ||
is not provided, then the component's current position is used. | ||
|
||
Instances hold references to the ``Component`` being moved in the | ||
``component`` attribute, the new and previous positions of the component as | ||
tuples ``(x, y)`` in the ``data`` and ``previous_position`` attributes, and | ||
whether or not subsequent move operations on the same component should | ||
be merged together. | ||
|
||
The tool handles the logic of changing the position of the component | ||
appropriately, as well as invalidating layout and requesting | ||
redraws. | ||
|
||
It also provides a default ``name`` attribute of ``Move `` plus the | ||
``component_name`` (which in turn defaults to a more human-readable | ||
variant of the component's class). Instances can improve this by | ||
either supplying a full replacement for the ``name`` attribute, or | ||
for the ``component_name``. | ||
|
||
|
||
Base Classes | ||
~~~~~~~~~~~~ | ||
|
||
There are two simple base classes of tools that are potentially of use to | ||
authors of new tools. | ||
|
||
``BaseUndoTool`` | ||
|
||
Tools which need to be able to trigger undo and redo actions, or otherwise | ||
interact with an undo manager (for example, to set the current command | ||
stack or clear the command history) can inherit from this class. | ||
|
||
It has an ``undo_manager`` attribute which holds a reference to an | ||
``IUndoManager`` and provides convenience methods for ``undo`` and ``redo`` | ||
using the undo manager. | ||
|
||
``BaseCommandTool`` | ||
|
||
Tools which need to perform undoable actions may want to inherit from this | ||
class. It provides a standard ``command_stack`` attribute which | ||
holds a reference to an ``ICommandStack``. It also has a ``command`` | ||
callable trait that can be overriden by subclasses to create an | ||
appropriate command when demanded by the UI. | ||
|
||
In addition to these simple base tools, authors of Tools or Actions that | ||
perform undoable operations on Enable or Chaco components may want to make use | ||
of the following ``Command`` subclass: | ||
|
||
``ComponentCommand`` | ||
|
||
This class is an abstract base class for commands which act on Enable | ||
``Components``. It provides a ``component`` attribute which holds a | ||
reference to the component that the command should be performed on, and | ||
a ``component_name`` attribute that can be used to help build the ``name`` | ||
of the ``Command`` to be used in textual representations of the command | ||
(eg. in menu item labels). | ||
|
||
The default ``component_name`` is just a more human-friendly version of | ||
the component's class name, with camel-case converted to words. Users | ||
are encouraged to override with something even more user-friendly. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# | ||
# (C) Copyright 2015 Enthought, Inc., Austin, TX | ||
# All right reserved. | ||
# | ||
# This file is open source software distributed according to the terms in | ||
# LICENSE.txt | ||
# | ||
""" | ||
Example Application Support | ||
=========================== | ||
This module provides a simple Pyface application that can be used by examples | ||
in places where a DemoFrame is insufficient. | ||
""" | ||
|
||
from __future__ import (division, absolute_import, print_function, | ||
unicode_literals) | ||
|
||
from pyface.api import ApplicationWindow, GUI | ||
|
||
class DemoApplication(ApplicationWindow): | ||
""" Simple Pyface application displaying a component. | ||
This application has the same interface as the DemoFrames from the | ||
example_support module, but the window is embedded in a full Pyface | ||
application. This means that subclasses have the opportunity of | ||
adding Menus, Toolbars, and other similar features to the demo, where | ||
needed. | ||
""" | ||
|
||
def _create_contents(self, parent): | ||
self.enable_win = self._create_window() | ||
return self.enable_win.control | ||
|
||
def _create_window(self): | ||
"Subclasses should override this method and return an enable.Window" | ||
raise NotImplementedError() | ||
|
||
@classmethod | ||
def demo_main(cls, **traits): | ||
""" Create the demo application and start the mainloop, if needed | ||
This should be called with appropriate arguments which will be passed to | ||
the class' constructor. | ||
""" | ||
# get the Pyface GUI | ||
gui = GUI() | ||
|
||
# create the application's main window | ||
window = cls(**traits) | ||
window.open() | ||
|
||
# start the application | ||
# if there isn't already a running mainloop, this will block | ||
gui.start_event_loop() | ||
|
||
# if there is already a running mainloop (eg. in an IPython session), | ||
# return a reference to the window so that our caller can hold on to it | ||
return window | ||
|
||
|
||
def demo_main(cls, **traits): | ||
""" Create the demo application and start the mainloop, if needed. | ||
This is a simple wrapper around `cls.demo_main` for compatibility with the | ||
`DemoFrame` implementation. | ||
This should be called with appropriate arguments which will be passed to | ||
the class' constructor. | ||
""" | ||
cls.demo_main(**traits) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
Oops, something went wrong.