Skip to content

Commit

Permalink
Merge 19975bc into 4bbb273
Browse files Browse the repository at this point in the history
  • Loading branch information
corranwebster committed Mar 23, 2015
2 parents 4bbb273 + 19975bc commit a7c43ce
Show file tree
Hide file tree
Showing 21 changed files with 1,616 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
Expand Up @@ -10,6 +10,7 @@ Enhancements
* Added a base class for drag and drop tools, example and test support.
* PR #160: Basic testing for kiva backends.
* PR #168: Simple push-button and checkbox tool.
* PR #167: Added tools that support Apptools Undo/Redo.


Enable 4.4.0 (May 1, 2014)
Expand Down
1 change: 1 addition & 0 deletions dev_requirements.txt
Expand Up @@ -10,4 +10,5 @@ reportlab<=3.1
-e git+http://github.com/enthought/traits.git#egg=traits
-e git+http://github.com/enthought/pyface.git#egg=pyface
-e git+http://github.com/enthought/traitsui.git#egg=traitsui
-e git+http://github.com/enthought/apptools.git#egg=apptools
-e git+http://github.com/nucleic/kiwi.git#egg=kiwisolver
199 changes: 199 additions & 0 deletions docs/source/enable_apptools_integration.rst
@@ -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.
1 change: 1 addition & 0 deletions docs/source/index.rst
Expand Up @@ -9,6 +9,7 @@ Enable Documentation
enable_key_events.rst
enable_basic_tools.rst
enable_drag_and_drop.rst
enable_apptools_integration.rst

kiva.rst

Expand Down
75 changes: 75 additions & 0 deletions enable/example_application.py
@@ -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)
3 changes: 3 additions & 0 deletions enable/testing.py
Expand Up @@ -21,6 +21,7 @@ def _redraw(self, coordinates=None):
def set_drag_result(self, result):
self._drag_result = result


class EnableTestAssistant(KivaTestAssistant):
""" Mixin helper for enable/chaco components.
Expand Down Expand Up @@ -93,6 +94,7 @@ def create_mock_window(self):
window._redraw = Mock()
window.control = Mock()
window.control.set_pointer = Mock()
window.get_pointer_position = Mock()
return window

def create_key_press(self, key, window=None, alt_down=False,
Expand Down Expand Up @@ -254,6 +256,7 @@ def mouse_move(self, interactor, x, y, window=None,
alt_down=alt_down,
control_down=control_down,
shift_down=shift_down)
window.get_pointer_position.return_value = (x, y)
self._mouse_event_dispatch(interactor, event, 'mouse_move')
return event

Expand Down
Empty file added enable/tests/tools/__init__.py
Empty file.
Empty file.

0 comments on commit a7c43ce

Please sign in to comment.