Prepare documentation for Gaphas 3
This commit is contained in:
parent
ece885056e
commit
1d5e07caec
@ -1,28 +0,0 @@
|
||||
|
||||
Gaphas vs jHotDraw
|
||||
------------------
|
||||
|
||||
Gaphas || jHotDraw
|
||||
Item | Figure
|
||||
Canvas | Drawing
|
||||
Tool | Tool
|
||||
Painter | Painter
|
||||
View | DrawingView
|
||||
|
||||
jHotDraw let's you enable one tool at a time. In Gaphas everything should be
|
||||
handled from within one tool chain. Tool chains can be switched, e.g. for
|
||||
specific actions such as item placement. Everything else (item, handle
|
||||
manupilation, zooming, selection) is handled without the need to select
|
||||
specific tools.
|
||||
|
||||
Items (Figures in jHotDraw) keep track of their own child items. In Gaphas, the
|
||||
Canvas object maintains the hierarchical order of items. Advantage is that
|
||||
addition of items is never unnoticed. Also iterating the nodes in the tree
|
||||
structure is a bit faster.
|
||||
|
||||
In jHotDraw, connections of items (figures) are maintained within the special
|
||||
connection figure. Gaphas maintains connections between items as constraints
|
||||
and uses a constraint solver (one per canvas) to ensure the constraints remain
|
||||
true.
|
||||
|
||||
|
@ -30,9 +30,5 @@ Tools
|
||||
|
||||
Several tools_ are used to make the overall user experience complete.
|
||||
|
||||
.. image:: tools.png
|
||||
:width: 532
|
||||
|
||||
|
||||
.. _quadtree: quadtree.html
|
||||
.. _tools: tools.html
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 94 KiB |
File diff suppressed because it is too large
Load Diff
4
docs/guide.rst
Normal file
4
docs/guide.rst
Normal file
@ -0,0 +1,4 @@
|
||||
Guides
|
||||
######
|
||||
|
||||
Guides are a tool to align elements with one another.
|
@ -1,30 +1,30 @@
|
||||
Gaphas Documentation
|
||||
Gaphas 3 Documentation
|
||||
====================
|
||||
|
||||
.. important:: This documentation is in the process of being updated for Gaphas 3.0.
|
||||
|
||||
|
||||
Gaphas is the diagramming widget library for Python.
|
||||
|
||||
Gaphas has been built with some extensibility in mind. It can be used for many
|
||||
drawing purposes, including vector drawing applications, diagram drawing tools
|
||||
and we even have a geographical map demo in our repository.
|
||||
Gaphas has been built with extensibility in mind. It can be used for many
|
||||
drawing purposes, including vector drawing applications, and diagram drawing tools.
|
||||
|
||||
|
||||
The basic idea is:
|
||||
|
||||
- Items (canvas items) can be added to a Canvas.
|
||||
- A Canvas can be visualized by one or more Views.
|
||||
- The canvas maintains the tree structure (parent-child relationships between
|
||||
items).
|
||||
- Gaphas has a Model-View-Controller_ design.
|
||||
- A model is presented as a protocol in Gaphas. This means that it's very easy to define a class that acts as a model.
|
||||
- A model can be visualized by one or more Views.
|
||||
- A constraint solver is used to maintain item constraints and inter-item
|
||||
constraints.
|
||||
- The item (and user) should not be bothered with things like bounding-box
|
||||
calculations.
|
||||
- Very modular: e.g. handle support could be swapped in and swapped out.
|
||||
- Very modular: The view contains the basic features. Painters and tools can be swapped out as needed.
|
||||
- Rendering using Cairo_. This implies the diagrams can be exported in a number
|
||||
of formats, including PNG and SVG.
|
||||
|
||||
Gaphas is released under the terms of the Apache Software License, version 2.0.
|
||||
|
||||
Gaphas has been build using `setuptools` and can be installed as Python Egg.
|
||||
|
||||
* Git repository: https://github.com/gaphor/gaphas
|
||||
* Python Package index (PyPI): https://pypi.org/project/gaphas
|
||||
|
||||
@ -32,20 +32,39 @@ Table of Contents
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:caption: The basics
|
||||
:maxdepth: 2
|
||||
|
||||
diagram
|
||||
tools
|
||||
ports
|
||||
state
|
||||
undo
|
||||
quadtree
|
||||
connectors
|
||||
solver
|
||||
state
|
||||
|
||||
.. toctree::
|
||||
:caption: Advanced
|
||||
:maxdepth: 2
|
||||
|
||||
guide
|
||||
segment
|
||||
|
||||
.. toctree::
|
||||
:caption: API
|
||||
:maxdepth: 2
|
||||
|
||||
api
|
||||
|
||||
.. toctree::
|
||||
:caption: Internals
|
||||
:maxdepth: 1
|
||||
|
||||
quadtree
|
||||
table
|
||||
tree
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
.. _Cairo: https://cairographics.org
|
||||
.. _Model-View-Controller: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
|
||||
|
@ -1,5 +1,5 @@
|
||||
Quadtree implementation
|
||||
=======================
|
||||
Quadtree
|
||||
########
|
||||
|
||||
In order to find items and handles fast on a 2D surface, a geometric structure is required.
|
||||
|
||||
@ -63,17 +63,11 @@ The screen is divided into four equal quadrants. The first quadrant has many ite
|
||||
References
|
||||
~~~~~~~~~~
|
||||
|
||||
(Roids)
|
||||
http://roids.slowchop.com/roids/browser/trunk/src/quadtree.py
|
||||
|
||||
(???)
|
||||
http://mu.arete.cc/pcr/syntax/quadtree/1/quadtree.py
|
||||
|
||||
(!PyGame)
|
||||
http://www.pygame.org/wiki/QuadTree?parent=CookBook
|
||||
|
||||
(PythonCAD)
|
||||
http://www.koders.com/python/fidB93B4D02B1C4E9F90F351736873DF6BE4D8D5783.aspx
|
||||
https://sourceforge.net/p/pythoncad/code/ci/master/tree/PythonCAD/Generic/quadtree.py
|
||||
|
||||
.. _Quadtrees: http://en.wikipedia.org/wiki/Quadtree
|
||||
.. _R-trees: http://en.wikipedia.org/wiki/R-tree
|
||||
|
6
docs/segment.rst
Normal file
6
docs/segment.rst
Normal file
@ -0,0 +1,6 @@
|
||||
Line Segments
|
||||
#############
|
||||
|
||||
The line segment functionality is an add-on, that will allow the user to add line segments to a line, and merge them.
|
||||
|
||||
To use this behavior, import the ``gaphas.segment`` module and add ``LineSegmentPainter`` to the list of painters for the view.
|
418
docs/state.rst
418
docs/state.rst
@ -1,6 +1,8 @@
|
||||
State management
|
||||
================
|
||||
|
||||
.. important:: This functionality will be removed.
|
||||
|
||||
A special word should be mentioned about state management. Managing state is
|
||||
the first step in creating an undo system.
|
||||
|
||||
@ -193,3 +195,419 @@ matrix.py:
|
||||
``Matrix``: ``invert()``, ``translate()``, ``rotate()`` and ``scale()``
|
||||
|
||||
Test cases are described in undo.txt.
|
||||
|
||||
Undo example
|
||||
------------
|
||||
|
||||
This document describes a basic undo system and tests Gaphas' classes with this
|
||||
system.
|
||||
|
||||
This document contains a set of test cases that is used to prove that it really
|
||||
works.
|
||||
|
||||
For this to work, some boilerplate has to be configured:
|
||||
|
||||
>>> from gaphas import state
|
||||
>>> state.observers.clear()
|
||||
>>> state.subscribers.clear()
|
||||
|
||||
>>> undo_list = []
|
||||
>>> redo_list = []
|
||||
>>> def undo_handler(event):
|
||||
... undo_list.append(event)
|
||||
>>> state.observers.add(state.revert_handler)
|
||||
>>> state.subscribers.add(undo_handler)
|
||||
|
||||
This simple undo function will revert all states collected in the undo_list:
|
||||
|
||||
>>> def undo():
|
||||
... apply_me = list(undo_list)
|
||||
... del undo_list[:]
|
||||
... apply_me.reverse()
|
||||
... for e in apply_me:
|
||||
... state.saveapply(*e)
|
||||
... redo_list[:] = undo_list[:]
|
||||
... del undo_list[:]
|
||||
|
||||
Undo functionality tests
|
||||
========================
|
||||
|
||||
The following sections contain most of the basis unit tests for undo
|
||||
management.
|
||||
|
||||
tree.py: Tree
|
||||
-------------
|
||||
Tree has no observed methods.
|
||||
|
||||
matrix.py: Matrix
|
||||
-----------------
|
||||
Matrix is used by Item classes.
|
||||
|
||||
>>> from gaphas.matrix import Matrix
|
||||
>>> m = Matrix()
|
||||
>>> m
|
||||
Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
|
||||
translate(tx, ty):
|
||||
|
||||
>>> m.translate(12, 16)
|
||||
>>> m
|
||||
Matrix(1.0, 0.0, 0.0, 1.0, 12.0, 16.0)
|
||||
>>> undo()
|
||||
>>> m
|
||||
Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
|
||||
scale(sx, sy):
|
||||
|
||||
>>> m.scale(1.5, 1.5)
|
||||
>>> m
|
||||
Matrix(1.5, 0.0, 0.0, 1.5, 0.0, 0.0)
|
||||
>>> undo()
|
||||
>>> m
|
||||
Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
|
||||
rotate(radians):
|
||||
|
||||
>>> def matrix_approx(m):
|
||||
... a = []
|
||||
... for i in tuple(m):
|
||||
... if -1e-10 < i < 1e-10: i=0
|
||||
... a.append(i)
|
||||
... return tuple(a)
|
||||
|
||||
>>> m.rotate(0.5)
|
||||
>>> m
|
||||
Matrix(0.8775825618903728, 0.479425538604203, -0.479425538604203, 0.8775825618903728, 0.0, 0.0)
|
||||
>>> undo()
|
||||
>>> matrix_approx(m)
|
||||
(1.0, 0, 0, 1.0, 0, 0)
|
||||
|
||||
Okay, nearly, close enough IMHO...
|
||||
|
||||
>>> m = Matrix()
|
||||
>>> m.translate(12, 10)
|
||||
>>> m.scale(1.5, 1.5)
|
||||
>>> m.rotate(0.5)
|
||||
>>> m
|
||||
Matrix(1.3163738428355591, 0.7191383079063045, -0.7191383079063045, 1.3163738428355591, 12.0, 10.0)
|
||||
>>> m.invert()
|
||||
>>> m
|
||||
Matrix(0.5850550412602484, -0.3196170257361353, 0.3196170257361353, 0.5850550412602484, -10.216830752484334, -2.0151461037688607)
|
||||
>>> undo()
|
||||
>>> matrix_approx(m)
|
||||
(1.0, 0, 0, 1.0, 0, 0)
|
||||
|
||||
Again, rotate does not result in an exact match, but it's close enough.
|
||||
|
||||
>>> undo_list
|
||||
[]
|
||||
|
||||
canvas.py: Canvas
|
||||
-----------------
|
||||
|
||||
>>> from gaphas import Canvas
|
||||
>>> from examples.exampleitems import Circle
|
||||
>>> canvas = Canvas()
|
||||
>>> list(canvas.get_all_items())
|
||||
[]
|
||||
>>> item = Circle()
|
||||
>>> canvas.add(item)
|
||||
|
||||
The ``request_update()`` method is observed:
|
||||
|
||||
>>> len(undo_list)
|
||||
2
|
||||
>>> canvas.request_update(item)
|
||||
>>> len(undo_list)
|
||||
3
|
||||
|
||||
On the canvas only ``add()`` and ``remove()`` are monitored:
|
||||
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>]
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items())
|
||||
[]
|
||||
>>> canvas.add(item)
|
||||
>>> del undo_list[:]
|
||||
>>> canvas.remove(item)
|
||||
>>> list(canvas.get_all_items())
|
||||
[]
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>]
|
||||
>>> undo_list
|
||||
[]
|
||||
|
||||
Parent-child relationships are restored as well:
|
||||
|
||||
TODO!
|
||||
|
||||
|
||||
>>> child = Circle()
|
||||
>>> canvas.add(child, parent=item)
|
||||
>>> canvas.get_parent(child) is item
|
||||
True
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>]
|
||||
>>> child in canvas.get_all_items()
|
||||
False
|
||||
|
||||
Now redo the previous undo action:
|
||||
|
||||
>>> undo_list[:] = redo_list[:]
|
||||
>>> undo()
|
||||
>>> canvas.get_parent(child) is item
|
||||
True
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||
|
||||
Remove also works when items are removed recursively (an item and it's
|
||||
children):
|
||||
|
||||
>>> child = Circle()
|
||||
>>> canvas.add(child, parent=item)
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||
>>> del undo_list[:]
|
||||
>>> canvas.remove(item)
|
||||
>>> list(canvas.get_all_items())
|
||||
[]
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||
>>> canvas.get_children(item) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>]
|
||||
|
||||
As well as the reparent() method:
|
||||
|
||||
>>> canvas = Canvas()
|
||||
>>> class NameItem:
|
||||
... def __init__(self, name):
|
||||
... super(NameItem, self).__init__()
|
||||
... self.name = name
|
||||
... def handles(self): return []
|
||||
... def ports(self): return []
|
||||
... def point(self, x, y): return 0
|
||||
... def __repr__(self):
|
||||
... return '<%s>' % self.name
|
||||
>>> ni1 = NameItem('a')
|
||||
>>> canvas.add(ni1)
|
||||
>>> ni2 = NameItem('b')
|
||||
>>> canvas.add(ni2)
|
||||
>>> ni3 = NameItem('c')
|
||||
>>> canvas.add(ni3, parent=ni1)
|
||||
>>> ni4 = NameItem('d')
|
||||
>>> canvas.add(ni4, parent=ni3)
|
||||
>>> list(canvas.get_all_items())
|
||||
[<a>, <c>, <d>, <b>]
|
||||
>>> del undo_list[:]
|
||||
>>> canvas.reparent(ni3, parent=ni2)
|
||||
>>> list(canvas.get_all_items())
|
||||
[<a>, <b>, <c>, <d>]
|
||||
>>> len(undo_list)
|
||||
1
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items())
|
||||
[<a>, <c>, <d>, <b>]
|
||||
|
||||
Redo should work too:
|
||||
|
||||
>>> undo_list[:] = redo_list[:]
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items())
|
||||
[<a>, <b>, <c>, <d>]
|
||||
|
||||
|
||||
Undo/redo a connection: see gaphas/tests/test_undo.py
|
||||
|
||||
|
||||
connector.py: Handle
|
||||
--------------------
|
||||
Changing the Handle's position is reversible:
|
||||
|
||||
>>> from gaphas import Handle
|
||||
>>> handle = Handle()
|
||||
>>> handle.pos = 10, 12
|
||||
>>> handle.pos
|
||||
<Position object on (Variable(10, 20), Variable(12, 20))>
|
||||
>>> undo()
|
||||
>>> handle.pos
|
||||
<Position object on (Variable(0, 20), Variable(0, 20))>
|
||||
|
||||
As are all other properties:
|
||||
|
||||
>>> handle.connectable, handle.movable, handle.visible
|
||||
(False, True, True)
|
||||
>>> handle.connectable = True
|
||||
>>> handle.movable = False
|
||||
>>> handle.visible = False
|
||||
>>> handle.connectable, handle.movable, handle.visible
|
||||
(True, False, False)
|
||||
|
||||
And now undo the whole lot at once:
|
||||
|
||||
>>> undo()
|
||||
>>> handle.connectable, handle.movable, handle.visible
|
||||
(False, True, True)
|
||||
|
||||
item.py: Item
|
||||
-------------
|
||||
|
||||
The basic Item properties are canvas and matrix. Canvas has been tested before,
|
||||
while testing the Canvas class.
|
||||
|
||||
The Matrix has been tested in section matrix.py: Matrix.
|
||||
|
||||
item.py: Element
|
||||
----------------
|
||||
|
||||
An element has ``min_height`` and ``min_width`` properties.
|
||||
|
||||
>>> from gaphas import Element
|
||||
>>> from gaphas.connections import Connections
|
||||
>>> e = Element(Connections())
|
||||
>>> e.min_height, e.min_width
|
||||
(Variable(10, 100), Variable(10, 100))
|
||||
>>> e.min_height, e.min_width = 30, 40
|
||||
>>> e.min_height, e.min_width
|
||||
(Variable(30, 100), Variable(40, 100))
|
||||
|
||||
>>> undo()
|
||||
>>> e.min_height, e.min_width
|
||||
(Variable(0, 100), Variable(0, 100))
|
||||
|
||||
>>> canvas = Canvas()
|
||||
>>> canvas.add(e)
|
||||
>>> undo()
|
||||
|
||||
item.py: Line
|
||||
-------------
|
||||
|
||||
A line has the following properties: ``line_width``, ``fuzziness``,
|
||||
``orthogonal`` and ``horizontal``. Each one of then is observed for changes:
|
||||
|
||||
>>> from gaphas import Line
|
||||
>>> from gaphas.segment import Segment
|
||||
>>> l = Line(Connections())
|
||||
|
||||
Let's first add a segment to the line, to test orthogonal lines as well.
|
||||
|
||||
>>> segment = Segment(l, canvas)
|
||||
>>> _ = segment.split_segment(0)
|
||||
|
||||
>>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
|
||||
(2, 0, False, False)
|
||||
|
||||
Now change the properties:
|
||||
|
||||
>>> l.line_width = 4
|
||||
>>> l.fuzziness = 2
|
||||
>>> l.orthogonal = True
|
||||
>>> l.horizontal = True
|
||||
>>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
|
||||
(4, 2, True, True)
|
||||
|
||||
And undo the changes:
|
||||
|
||||
>>> undo()
|
||||
>>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
|
||||
(2, 0, False, False)
|
||||
|
||||
In addition to those properties, line segments can be split and merged.
|
||||
|
||||
>>> l.handles()[1].pos = 10, 10
|
||||
>>> l.handles()
|
||||
[<Handle object on (Variable(0, 20), Variable(0, 20))>, <Handle object on (Variable(10, 20), Variable(10, 20))>]
|
||||
|
||||
This is our basis for further testing.
|
||||
|
||||
>>> del undo_list[:]
|
||||
|
||||
>>> Segment(l, canvas).split_segment(0) # doctest: +ELLIPSIS
|
||||
([<Handle object on (Variable(5, 10), Variable(5, 10))>], [<gaphas.connector.LinePort object at 0x...>])
|
||||
>>> l.handles()
|
||||
[<Handle object on (Variable(0, 20), Variable(0, 20))>, <Handle object on (Variable(5, 10), Variable(5, 10))>, <Handle object on (Variable(10, 20), Variable(10, 20))>]
|
||||
|
||||
The opposite operation is performed with the merge_segment() method:
|
||||
|
||||
>>> undo()
|
||||
>>> l.handles()
|
||||
[<Handle object on (Variable(0, 20), Variable(0, 20))>, <Handle object on (Variable(10, 20), Variable(10, 20))>]
|
||||
|
||||
Also creation and removal of connected lines is recorded and can be undone:
|
||||
|
||||
>>> canvas = Canvas()
|
||||
>>> def real_connect(hitem, handle, item):
|
||||
... def real_disconnect():
|
||||
... pass
|
||||
... canvas.connections.connect_item(hitem, handle, item, port=None, constraint=None, callback=real_disconnect)
|
||||
>>> b0 = Circle()
|
||||
>>> canvas.add(b0)
|
||||
>>> b1 = Circle()
|
||||
>>> canvas.add(b1)
|
||||
>>> l = Line(Connections())
|
||||
>>> canvas.add(l)
|
||||
>>> real_connect(l, l.handles()[0], b0)
|
||||
>>> real_connect(l, l.handles()[1], b1)
|
||||
>>> canvas.connections.get_connection(l.handles()[0]) # doctest: +ELLIPSIS
|
||||
Connection(item=<gaphas.item.Line object at 0x...>)
|
||||
>>> canvas.connections.get_connection(l.handles()[1]) # doctest: +ELLIPSIS
|
||||
Connection(item=<gaphas.item.Line object at 0x...>)
|
||||
|
||||
Clear already collected undo data:
|
||||
|
||||
>>> del undo_list[:]
|
||||
|
||||
Now remove the line from the canvas:
|
||||
|
||||
>>> canvas.remove(l)
|
||||
|
||||
The handles are disconnected:
|
||||
|
||||
>>> canvas.connections.get_connection(l.handles()[0])
|
||||
>>> canvas.connections.get_connection(l.handles()[1])
|
||||
|
||||
Undoing the remove() action should put everything back in place again:
|
||||
|
||||
>>> undo()
|
||||
|
||||
>>> canvas.connections.get_connection(l.handles()[0]) # doctest: +ELLIPSIS
|
||||
Connection(item=<gaphas.item.Line object at 0x...>)
|
||||
>>> canvas.connections.get_connection(l.handles()[1]) # doctest: +ELLIPSIS
|
||||
Connection(item=<gaphas.item.Line object at 0x...>)
|
||||
|
||||
|
||||
solver.py: Variable
|
||||
-------------------
|
||||
|
||||
Variable's strength and value properties are observed:
|
||||
|
||||
>>> from gaphas.solver import Variable
|
||||
>>> v = Variable()
|
||||
>>> v.value = 10
|
||||
>>> v.strength = 100
|
||||
>>> v
|
||||
Variable(10, 100)
|
||||
>>> undo()
|
||||
>>> v
|
||||
Variable(0, 20)
|
||||
|
||||
solver.py: Solver
|
||||
-----------------
|
||||
|
||||
Solvers ``add_constraint()`` and ``remove_constraint()`` are observed.
|
||||
|
||||
>>> from gaphas.solver import Solver
|
||||
>>> from gaphas.constraint import EquationConstraint
|
||||
>>> s = Solver()
|
||||
>>> a, b = Variable(1.0), Variable(2.0)
|
||||
>>> s.add_constraint(EquationConstraint(lambda a,b: a+b, a=a, b=b))
|
||||
EquationConstraint(<lambda>, a=Variable(1, 20), b=Variable(2, 20))
|
||||
>>> undo()
|
||||
|
||||
>>> undo_list[:] = redo_list[:]
|
||||
>>> undo()
|
8
docs/table.rst
Normal file
8
docs/table.rst
Normal file
@ -0,0 +1,8 @@
|
||||
Table
|
||||
#####
|
||||
|
||||
Table is an internal structure. It can be best compared to a table in a database. On the table, indexes can be defined.
|
||||
|
||||
Tables are used when data should be made available in different forms.
|
||||
|
||||
Source code: https://github.com/gaphor/gaphas/blob/master/gaphas/table.py.
|
BIN
docs/tools.png
BIN
docs/tools.png
Binary file not shown.
Before Width: | Height: | Size: 76 KiB |
@ -8,6 +8,7 @@ Tools are registered on the ``View``. They have some internal state (e.g. when a
|
||||
where a mouse button was pressed). Therefore tools can not be reused by
|
||||
different views [#]_.
|
||||
|
||||
Tools are simply `Gtk.EventController` instances.
|
||||
For a certain action to happen multiple user events are used. For example a
|
||||
click is a combination of a button press and button release event (only talking
|
||||
mouse clicks now). In most cases also some movement is done. A sequence of a
|
||||
@ -37,36 +38,26 @@ To organize the event sequences and keep some order in what the user is doing To
|
||||
|
||||
Gaphas contains a set of default tools. Each tool is meant to deal with a special part of the view. A list of responsibilities is also defined here:
|
||||
|
||||
:HoverTool:
|
||||
:hover tool:
|
||||
First thing a user wants to know is if the mouse cursor is over an item. The ``HoverTool`` makes that explicit.
|
||||
- Find a handle or item, if found, mark it as the ``hovered_item``
|
||||
:HandleTool and ConnectHandleTool:
|
||||
Handles are an important means to interact with items. They are used to
|
||||
resize element, move lines and (in case of ``ConnectHandleTool``) establish
|
||||
connections between items. Handles are rendered on top of items so it makes
|
||||
sense to deal with them before you deal with items.
|
||||
|
||||
- On click: find a handle, if found become the grabbed tool and set focus on the selected item. Deselected current selection based on modifier keys.
|
||||
- On motion: move the handle
|
||||
- On release: release grab and release the handle
|
||||
|
||||
:ItemTool:
|
||||
:item tool:
|
||||
Items are the elements that are actually providing any (visual) meaning to the diagram. ItemTool deals with moving them around. The tool makes sure the right subset of selected elements are moved (e.g. you don't want to move a nested item if its parent item is already moved, this gives funny visual effects)
|
||||
|
||||
- On click: find an item, if found become the grabbed tool and set the item as focused. Some extra behaviour regarding multiple select is also done here.
|
||||
- On click: find an item, if found become the grabbed tool and set the item as focused. If a used clicked on a handle position that is taken into account
|
||||
- On motion: move the selected items (only the ones that have no selected parent items)
|
||||
- On release: release grab and release item
|
||||
|
||||
:RubberBandTool:
|
||||
If no handle or item is selected a rubberband selection is started.
|
||||
:PanTool and ZoomTool:
|
||||
Handy tools for moving the canvas around and zooming in and out. Convenience functionality, basically.
|
||||
The item tool invokes the `Move` aspect, or the `HandleMove` aspect in case a handle is being grabbed.
|
||||
|
||||
All tools mentioned above are linked in a ``ToolChain``. Only one tool can deal with a use event.
|
||||
:rubberband tool:
|
||||
If no handle or item is selected a rubberband selection is started.
|
||||
:scroll and zoom tool:
|
||||
Handy tools for moving the canvas around and zooming in and out. Convenience functionality, basically.
|
||||
|
||||
There is one more tool, that has not been mentioned yet:
|
||||
|
||||
:PlacementTool:
|
||||
:placement tool:
|
||||
A special tool to use for placing new items on the screen.
|
||||
|
||||
As said, tools define *what* has to happen, they don't say how. Take for example finding a handle: on a normal element (a box or something) that would mean find the handle in one of the corners. On a line, however, that may also mean a not-yet existing handle in the middle of a line segment (there is a functionality that splits the line segment).
|
||||
@ -88,9 +79,6 @@ The advantage is that more complex behaviour can be composed. Since the
|
||||
decision on what should happen is done in the tool, the aspect which is then
|
||||
used to work on the item ensures a certain behaviour is performed.
|
||||
|
||||
.. image:: tools.png
|
||||
:width: 700
|
||||
|
||||
The diagram above shows the relation between tools and their aspects. Note that
|
||||
tools that delegate their behaviour to aspects have more than one aspects. The
|
||||
reason is that there are different concerns involved in defining what the tools
|
||||
@ -98,15 +86,5 @@ should do. Typically ``ItemTool`` will be selecting the actual item and takes
|
||||
care of moving it around as well. ``HandleTool`` does similar things for
|
||||
handles.
|
||||
|
||||
|
||||
|
||||
Big changes from Gaphas 0.4 tool include:
|
||||
|
||||
* Tools can contain state and should be used for one view only.
|
||||
* Grabbing is done automatically for press-move-release event sequence.
|
||||
* The _What_ is separated from the _How_, leaving less tools and less
|
||||
overhead (like finding the item under the mouse pointer).
|
||||
|
||||
|
||||
.. [#] as opposed to versions < 0.5, where tools could be shared among multiple views.
|
||||
.. [#] not the AOP term. The term aspect is coming from a paper by Dick Riehe: The Tools and Materials metaphore <url...>.
|
||||
|
8
docs/tree.rst
Normal file
8
docs/tree.rst
Normal file
@ -0,0 +1,8 @@
|
||||
Tree
|
||||
####
|
||||
|
||||
Tree is an internal structure used by the default view model implementation (``gaphas.Canvas``). A tree consists of nodes.
|
||||
|
||||
The tree is optimized for depth-first search.
|
||||
|
||||
Source code: https://github.com/gaphor/gaphas/blob/master/gaphas/tree.py.
|
419
docs/undo.rst
419
docs/undo.rst
@ -1,419 +0,0 @@
|
||||
Undo - implementing basic undo behaviour with Gaphas
|
||||
####################################################
|
||||
|
||||
This document describes a basic undo system and tests Gaphas' classes with this
|
||||
system.
|
||||
|
||||
This document contains a set of test cases that is used to prove that it really
|
||||
works.
|
||||
|
||||
See state.txt about how state is recorded.
|
||||
|
||||
.. contents::
|
||||
|
||||
For this to work, some boilerplate has to be configured:
|
||||
|
||||
>>> from gaphas import state
|
||||
>>> state.observers.clear()
|
||||
>>> state.subscribers.clear()
|
||||
|
||||
>>> undo_list = []
|
||||
>>> redo_list = []
|
||||
>>> def undo_handler(event):
|
||||
... undo_list.append(event)
|
||||
>>> state.observers.add(state.revert_handler)
|
||||
>>> state.subscribers.add(undo_handler)
|
||||
|
||||
This simple undo function will revert all states collected in the undo_list:
|
||||
|
||||
>>> def undo():
|
||||
... apply_me = list(undo_list)
|
||||
... del undo_list[:]
|
||||
... apply_me.reverse()
|
||||
... for e in apply_me:
|
||||
... state.saveapply(*e)
|
||||
... redo_list[:] = undo_list[:]
|
||||
... del undo_list[:]
|
||||
|
||||
Undo functionality tests
|
||||
========================
|
||||
|
||||
The following sections contain most of the basis unit tests for undo
|
||||
management.
|
||||
|
||||
tree.py: Tree
|
||||
-------------
|
||||
Tree has no observed methods.
|
||||
|
||||
matrix.py: Matrix
|
||||
-----------------
|
||||
Matrix is used by Item classes.
|
||||
|
||||
>>> from gaphas.matrix import Matrix
|
||||
>>> m = Matrix()
|
||||
>>> m
|
||||
Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
|
||||
translate(tx, ty):
|
||||
|
||||
>>> m.translate(12, 16)
|
||||
>>> m
|
||||
Matrix(1.0, 0.0, 0.0, 1.0, 12.0, 16.0)
|
||||
>>> undo()
|
||||
>>> m
|
||||
Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
|
||||
scale(sx, sy):
|
||||
|
||||
>>> m.scale(1.5, 1.5)
|
||||
>>> m
|
||||
Matrix(1.5, 0.0, 0.0, 1.5, 0.0, 0.0)
|
||||
>>> undo()
|
||||
>>> m
|
||||
Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
|
||||
rotate(radians):
|
||||
|
||||
>>> def matrix_approx(m):
|
||||
... a = []
|
||||
... for i in tuple(m):
|
||||
... if -1e-10 < i < 1e-10: i=0
|
||||
... a.append(i)
|
||||
... return tuple(a)
|
||||
|
||||
>>> m.rotate(0.5)
|
||||
>>> m
|
||||
Matrix(0.8775825618903728, 0.479425538604203, -0.479425538604203, 0.8775825618903728, 0.0, 0.0)
|
||||
>>> undo()
|
||||
>>> matrix_approx(m)
|
||||
(1.0, 0, 0, 1.0, 0, 0)
|
||||
|
||||
Okay, nearly, close enough IMHO...
|
||||
|
||||
>>> m = Matrix()
|
||||
>>> m.translate(12, 10)
|
||||
>>> m.scale(1.5, 1.5)
|
||||
>>> m.rotate(0.5)
|
||||
>>> m
|
||||
Matrix(1.3163738428355591, 0.7191383079063045, -0.7191383079063045, 1.3163738428355591, 12.0, 10.0)
|
||||
>>> m.invert()
|
||||
>>> m
|
||||
Matrix(0.5850550412602484, -0.3196170257361353, 0.3196170257361353, 0.5850550412602484, -10.216830752484334, -2.0151461037688607)
|
||||
>>> undo()
|
||||
>>> matrix_approx(m)
|
||||
(1.0, 0, 0, 1.0, 0, 0)
|
||||
|
||||
Again, rotate does not result in an exact match, but it's close enough.
|
||||
|
||||
>>> undo_list
|
||||
[]
|
||||
|
||||
canvas.py: Canvas
|
||||
-----------------
|
||||
|
||||
>>> from gaphas import Canvas
|
||||
>>> from examples.exampleitems import Circle
|
||||
>>> canvas = Canvas()
|
||||
>>> list(canvas.get_all_items())
|
||||
[]
|
||||
>>> item = Circle()
|
||||
>>> canvas.add(item)
|
||||
|
||||
The ``request_update()`` method is observed:
|
||||
|
||||
>>> len(undo_list)
|
||||
2
|
||||
>>> canvas.request_update(item)
|
||||
>>> len(undo_list)
|
||||
3
|
||||
|
||||
On the canvas only ``add()`` and ``remove()`` are monitored:
|
||||
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>]
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items())
|
||||
[]
|
||||
>>> canvas.add(item)
|
||||
>>> del undo_list[:]
|
||||
>>> canvas.remove(item)
|
||||
>>> list(canvas.get_all_items())
|
||||
[]
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>]
|
||||
>>> undo_list
|
||||
[]
|
||||
|
||||
Parent-child relationships are restored as well:
|
||||
|
||||
TODO!
|
||||
|
||||
|
||||
>>> child = Circle()
|
||||
>>> canvas.add(child, parent=item)
|
||||
>>> canvas.get_parent(child) is item
|
||||
True
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>]
|
||||
>>> child in canvas.get_all_items()
|
||||
False
|
||||
|
||||
Now redo the previous undo action:
|
||||
|
||||
>>> undo_list[:] = redo_list[:]
|
||||
>>> undo()
|
||||
>>> canvas.get_parent(child) is item
|
||||
True
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||
|
||||
Remove also works when items are removed recursively (an item and it's
|
||||
children):
|
||||
|
||||
>>> child = Circle()
|
||||
>>> canvas.add(child, parent=item)
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||
>>> del undo_list[:]
|
||||
>>> canvas.remove(item)
|
||||
>>> list(canvas.get_all_items())
|
||||
[]
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items()) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>, <examples.exampleitems.Circle object at 0x...>]
|
||||
>>> canvas.get_children(item) # doctest: +ELLIPSIS
|
||||
[<examples.exampleitems.Circle object at 0x...>]
|
||||
|
||||
As well as the reparent() method:
|
||||
|
||||
>>> canvas = Canvas()
|
||||
>>> class NameItem:
|
||||
... def __init__(self, name):
|
||||
... super(NameItem, self).__init__()
|
||||
... self.name = name
|
||||
... def handles(self): return []
|
||||
... def ports(self): return []
|
||||
... def point(self, x, y): return 0
|
||||
... def __repr__(self):
|
||||
... return '<%s>' % self.name
|
||||
>>> ni1 = NameItem('a')
|
||||
>>> canvas.add(ni1)
|
||||
>>> ni2 = NameItem('b')
|
||||
>>> canvas.add(ni2)
|
||||
>>> ni3 = NameItem('c')
|
||||
>>> canvas.add(ni3, parent=ni1)
|
||||
>>> ni4 = NameItem('d')
|
||||
>>> canvas.add(ni4, parent=ni3)
|
||||
>>> list(canvas.get_all_items())
|
||||
[<a>, <c>, <d>, <b>]
|
||||
>>> del undo_list[:]
|
||||
>>> canvas.reparent(ni3, parent=ni2)
|
||||
>>> list(canvas.get_all_items())
|
||||
[<a>, <b>, <c>, <d>]
|
||||
>>> len(undo_list)
|
||||
1
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items())
|
||||
[<a>, <c>, <d>, <b>]
|
||||
|
||||
Redo should work too:
|
||||
|
||||
>>> undo_list[:] = redo_list[:]
|
||||
>>> undo()
|
||||
>>> list(canvas.get_all_items())
|
||||
[<a>, <b>, <c>, <d>]
|
||||
|
||||
|
||||
Undo/redo a connection: see gaphas/tests/test_undo.py
|
||||
|
||||
|
||||
connector.py: Handle
|
||||
--------------------
|
||||
Changing the Handle's position is reversible:
|
||||
|
||||
>>> from gaphas import Handle
|
||||
>>> handle = Handle()
|
||||
>>> handle.pos = 10, 12
|
||||
>>> handle.pos
|
||||
<Position object on (Variable(10, 20), Variable(12, 20))>
|
||||
>>> undo()
|
||||
>>> handle.pos
|
||||
<Position object on (Variable(0, 20), Variable(0, 20))>
|
||||
|
||||
As are all other properties:
|
||||
|
||||
>>> handle.connectable, handle.movable, handle.visible
|
||||
(False, True, True)
|
||||
>>> handle.connectable = True
|
||||
>>> handle.movable = False
|
||||
>>> handle.visible = False
|
||||
>>> handle.connectable, handle.movable, handle.visible
|
||||
(True, False, False)
|
||||
|
||||
And now undo the whole lot at once:
|
||||
|
||||
>>> undo()
|
||||
>>> handle.connectable, handle.movable, handle.visible
|
||||
(False, True, True)
|
||||
|
||||
item.py: Item
|
||||
-------------
|
||||
|
||||
The basic Item properties are canvas and matrix. Canvas has been tested before,
|
||||
while testing the Canvas class.
|
||||
|
||||
The Matrix has been tested in section matrix.py: Matrix.
|
||||
|
||||
item.py: Element
|
||||
----------------
|
||||
|
||||
An element has ``min_height`` and ``min_width`` properties.
|
||||
|
||||
>>> from gaphas import Element
|
||||
>>> from gaphas.connections import Connections
|
||||
>>> e = Element(Connections())
|
||||
>>> e.min_height, e.min_width
|
||||
(Variable(10, 100), Variable(10, 100))
|
||||
>>> e.min_height, e.min_width = 30, 40
|
||||
>>> e.min_height, e.min_width
|
||||
(Variable(30, 100), Variable(40, 100))
|
||||
|
||||
>>> undo()
|
||||
>>> e.min_height, e.min_width
|
||||
(Variable(0, 100), Variable(0, 100))
|
||||
|
||||
>>> canvas = Canvas()
|
||||
>>> canvas.add(e)
|
||||
>>> undo()
|
||||
|
||||
item.py: Line
|
||||
-------------
|
||||
|
||||
A line has the following properties: ``line_width``, ``fuzziness``,
|
||||
``orthogonal`` and ``horizontal``. Each one of then is observed for changes:
|
||||
|
||||
>>> from gaphas import Line
|
||||
>>> from gaphas.segment import Segment
|
||||
>>> l = Line(Connections())
|
||||
|
||||
Let's first add a segment to the line, to test orthogonal lines as well.
|
||||
|
||||
>>> segment = Segment(l, canvas)
|
||||
>>> _ = segment.split_segment(0)
|
||||
|
||||
>>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
|
||||
(2, 0, False, False)
|
||||
|
||||
Now change the properties:
|
||||
|
||||
>>> l.line_width = 4
|
||||
>>> l.fuzziness = 2
|
||||
>>> l.orthogonal = True
|
||||
>>> l.horizontal = True
|
||||
>>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
|
||||
(4, 2, True, True)
|
||||
|
||||
And undo the changes:
|
||||
|
||||
>>> undo()
|
||||
>>> l.line_width, l.fuzziness, l.orthogonal, l.horizontal
|
||||
(2, 0, False, False)
|
||||
|
||||
In addition to those properties, line segments can be split and merged.
|
||||
|
||||
>>> l.handles()[1].pos = 10, 10
|
||||
>>> l.handles()
|
||||
[<Handle object on (Variable(0, 20), Variable(0, 20))>, <Handle object on (Variable(10, 20), Variable(10, 20))>]
|
||||
|
||||
This is our basis for further testing.
|
||||
|
||||
>>> del undo_list[:]
|
||||
|
||||
>>> Segment(l, canvas).split_segment(0) # doctest: +ELLIPSIS
|
||||
([<Handle object on (Variable(5, 10), Variable(5, 10))>], [<gaphas.connector.LinePort object at 0x...>])
|
||||
>>> l.handles()
|
||||
[<Handle object on (Variable(0, 20), Variable(0, 20))>, <Handle object on (Variable(5, 10), Variable(5, 10))>, <Handle object on (Variable(10, 20), Variable(10, 20))>]
|
||||
|
||||
The opposite operation is performed with the merge_segment() method:
|
||||
|
||||
>>> undo()
|
||||
>>> l.handles()
|
||||
[<Handle object on (Variable(0, 20), Variable(0, 20))>, <Handle object on (Variable(10, 20), Variable(10, 20))>]
|
||||
|
||||
Also creation and removal of connected lines is recorded and can be undone:
|
||||
|
||||
>>> canvas = Canvas()
|
||||
>>> def real_connect(hitem, handle, item):
|
||||
... def real_disconnect():
|
||||
... pass
|
||||
... canvas.connections.connect_item(hitem, handle, item, port=None, constraint=None, callback=real_disconnect)
|
||||
>>> b0 = Circle()
|
||||
>>> canvas.add(b0)
|
||||
>>> b1 = Circle()
|
||||
>>> canvas.add(b1)
|
||||
>>> l = Line(Connections())
|
||||
>>> canvas.add(l)
|
||||
>>> real_connect(l, l.handles()[0], b0)
|
||||
>>> real_connect(l, l.handles()[1], b1)
|
||||
>>> canvas.connections.get_connection(l.handles()[0]) # doctest: +ELLIPSIS
|
||||
Connection(item=<gaphas.item.Line object at 0x...>)
|
||||
>>> canvas.connections.get_connection(l.handles()[1]) # doctest: +ELLIPSIS
|
||||
Connection(item=<gaphas.item.Line object at 0x...>)
|
||||
|
||||
Clear already collected undo data:
|
||||
|
||||
>>> del undo_list[:]
|
||||
|
||||
Now remove the line from the canvas:
|
||||
|
||||
>>> canvas.remove(l)
|
||||
|
||||
The handles are disconnected:
|
||||
|
||||
>>> canvas.connections.get_connection(l.handles()[0])
|
||||
>>> canvas.connections.get_connection(l.handles()[1])
|
||||
|
||||
Undoing the remove() action should put everything back in place again:
|
||||
|
||||
>>> undo()
|
||||
|
||||
>>> canvas.connections.get_connection(l.handles()[0]) # doctest: +ELLIPSIS
|
||||
Connection(item=<gaphas.item.Line object at 0x...>)
|
||||
>>> canvas.connections.get_connection(l.handles()[1]) # doctest: +ELLIPSIS
|
||||
Connection(item=<gaphas.item.Line object at 0x...>)
|
||||
|
||||
|
||||
solver.py: Variable
|
||||
-------------------
|
||||
|
||||
Variable's strength and value properties are observed:
|
||||
|
||||
>>> from gaphas.solver import Variable
|
||||
>>> v = Variable()
|
||||
>>> v.value = 10
|
||||
>>> v.strength = 100
|
||||
>>> v
|
||||
Variable(10, 100)
|
||||
>>> undo()
|
||||
>>> v
|
||||
Variable(0, 20)
|
||||
|
||||
solver.py: Solver
|
||||
-----------------
|
||||
|
||||
Solvers ``add_constraint()`` and ``remove_constraint()`` are observed.
|
||||
|
||||
>>> from gaphas.solver import Solver
|
||||
>>> from gaphas.constraint import EquationConstraint
|
||||
>>> s = Solver()
|
||||
>>> a, b = Variable(1.0), Variable(2.0)
|
||||
>>> s.add_constraint(EquationConstraint(lambda a,b: a+b, a=a, b=b))
|
||||
EquationConstraint(<lambda>, a=Variable(1, 20), b=Variable(2, 20))
|
||||
>>> undo()
|
||||
|
||||
>>> undo_list[:] = redo_list[:]
|
||||
>>> undo()
|
Loading…
x
Reference in New Issue
Block a user