State management
================

A special word should be mentioned about state management. Managing state is
the first step in creating an undo system.

The state system consists of two parts:

 1. A basic observer (the @observed decorator)
 2. A reverter

Observer
--------

The observer simply dispatches the function called (as <function ..>, not as
<unbound method..>!) to each handler registered in an observers list.

    >>> from gaphas import state
    >>> state.observers.clear()
    >>> state.subscribers.clear()

    >>> from gaphas.tree import Tree
    >>> tree = Tree()

For this demonstration let's use the Tree class (which contains an add/remove
method pair). It's methods are not dispatched by default though (because they're
only used in the Canvas class. So first dispatching should be enabled:

    >>> state.enable_dispatching(Tree.add)
    >>> state.enable_dispatching(Tree.remove)

It works:

    >>> def handler(event):
    ...     print 'event handled', event
    >>> state.observers.add(handler)
    >>> tree.add(1)                                     # doctest: +ELLIPSIS
    event handled (<function add at ...>, (<gaphas.tree.Tree object at 0x...>, 1, None), {})
    >>> tree.add(2, parent=1)                           # doctest: +ELLIPSIS
    event handled (<function add at ...>, (<gaphas.tree.Tree object at 0x...>, 2, 1), {})
    >>> tree.nodes
    [1, 2]

Remember that this observer is just a simple method call notifier and knows
nothing about the internals of the tree class (in this case the remove() method
recursively calls remove() for each of it's children):

    >>> tree.remove(1)                                  # doctest: +ELLIPSIS
    event handled (<function remove at ...>, (<gaphas.tree.Tree object at 0x...>, 1), {})
    event handled (<function remove at ...>, (<gaphas.tree.Tree object at 0x...>, 2), {})
    >>> tree.nodes
    []

The @observed decorator can also be applied to properties, as is done in
gaphas/item.py's Handle class:

    >>> from gaphas.solver import Variable
    >>> var = Variable()
    >>> var.value = 10                                  # doctest: +ELLIPSIS
    event handled (<function set_value at 0x...>, (Variable(0, 20), 10), {})

(this is simply done by observing the setter method).

Off course handlers can be removed as well (only the default revert handler
is present now):

    >>> state.observers.remove(handler)
    >>> state.observers                                 # doctest: +ELLIPSIS
    set([])

What should you know:

 1. The observer always generates events based on 'function' calls. Even for
    class method invokations. This is because, when calling a method (say
    Tree.add) it's the im_func field is executed, which is a function type
    object.

 2. It's important to know if an event came from invoking a method or a simple
    function. With methods, the first argument always is an instance. This can
    be handy when writing an undo management systems in case multiple calls
    from the same instance do not have to be registered (e.g. if a method
    set_point() is called with exact coordinates (in stead of deltas), only the
    first call to set_point needs to be remembered.


Reverser
--------

The reverser requires some registration.

 1. Property setters should be declared with reversible_property()
 2. Method (or function) pairs that implement each others reverse operation
    (e.g. add and remove) should be registered as reversible_pair()'s in the
    reverser engine.
    The reverser will construct a tuple (callable, arguments) which are send
    to every handler registered in the subscribers list. Arguments is a dict().

First thing to do is to actually enable the revert_handler:

    >>> state.observers.add(state.revert_handler)

This handler is not enabled by default because:
 1. it generates quite a bit of overhead if it isn't used anyway
 2. you might want to add some additional filtering.

Point 2 may require some explanation. First of all observers have been added
to almost every method that involves a state change. As a result multiple
(conflicting) revert actions may be generated (e.g. Canvas.add calls Tree.add,
both of which are observed).

Handlers for the reverse events should be registered on the subscribers list:

    >>> events = []
    >>> def handler(event):
    ...     events.append(event)
    ...     print 'event handler', event
    >>> state.subscribers.add(handler)

After that, signals can be received of undoable (reverse-)events:

    >>> tree.add(3)                                     # doctest: +ELLIPSIS
    event handler (<function remove at ...>, {'node': 3, 'self': <gaphas.tree.Tree object at 0x...>})
    >>> tree.nodes
    [3]

As you can see this event is constructed of only two parameters: the function
that does the inverse operation of add() and the arguments that should be
applied to that function.

The inverse operation is easiest performed by the function saveapply(). Of course
an inverse operation is emitting a change event too:

    >>> state.saveapply(*events.pop())                  # doctest: +ELLIPSIS
    event handler (<function add at 0x...>, {'node': 3, 'self': <gaphas.tree.Tree object at 0x...>, 'parent': None})
    >>> tree.nodes
    []

Just handling method pairs is one thing. handling properties (descriptors) in
a simple fashion is another matter. First of all the original value should
be retrieved before the new value is applied (this is different from applying
the same arguments to another method in order to reverse an operation).

For this a reversible_property has been introduced. It works just like a
property (in fact it creates a plain old property descriptor), but also
registers the property as being reversible.

    >>> var = Variable()
    >>> var.value = 10                                  # doctest: +ELLIPSIS
    event handler (<function set_value at 0x...>, {'self': Variable(0, 20), 'value': 0.0})
    
Handlers can be simply removed:

    >>> state.subscribers.remove(handler)
    >>> state.observers.remove(state.revert_handler)

TODO
----

Function wrappers should have an extra property indicating if a function needs
to be dispatched or not. If it needs to be dispatched an extra flag should be
set. This will prevent the system from slowing down due to emitting signals that
are filtered out later on.

What is Observed 
----------------

As far as Gaphas is concerned, only properties and methods related to the
model (e.g. Canvas, Items) emit state changes. Some extra effort has been taken
to monitor the Matrix class (which is from Cairo).

 canvas.py:
   Canvas:
     add() and remove()

 item.py:
   Handle:
     x, y, connectable, movable, visible, connected_to and disconnect properties
   Item:
     canvas and matrix properties
   Element:
     min_height and min_width properties
   Line:
     line_width, fuzziness, orthogonal and horizontal properties;
     split_segment() and merge_segment()

 solver.py:
   Variable:
     strength and value properties
   Solver:
     add_constraint() and remove_constraint() 

 tree.py:
   Tree:
     add() and remove()

 matrix.py:
   Matrix:
    invert, translate, rotate and scale

Testcases are described in undo.txt.

(disable dispatching again, not frustrating other tests)

    >>> state.disable_dispatching(Tree.add)
    >>> state.disable_dispatching(Tree.remove)

