Add documentation and docstrings

This commit is contained in:
Arjan Molenaar 2020-12-06 22:16:15 +01:00
parent ca0f843d7f
commit 6831281c98
No known key found for this signature in database
GPG Key ID: BF977B918996CB13
23 changed files with 189 additions and 100 deletions

View File

@ -6,6 +6,7 @@ API reference
:caption: View
:maxdepth: 1
api/protocols
api/view
api/painters
api/tools
@ -17,8 +18,7 @@ The central part for Gaphas is the View. That's the class that ensures stuff is
:maxdepth: 1
api/connections
api/solver
api/position
api/variable
One of Gaphas' USP is it's the way it handles connections and the constraint solver.
@ -28,6 +28,7 @@ One of Gaphas' USP is it's the way it handles connections and the constraint sol
api/matrix
api/quadtree
api/solver
api/tree
api/table
api/geometry

7
docs/api/connections.rst Normal file
View File

@ -0,0 +1,7 @@
Connections
===========
The ``Connections`` class can be used to manage any type of constraint.
.. autoclass:: gaphas.connections.Connections
:members:

7
docs/api/matrix.rst Normal file
View File

@ -0,0 +1,7 @@
Matrix
======
The ``Matrix`` class used to records item placement (translation), scale and skew.
.. autoclass:: gaphas.matrix.Matrix
:members:

39
docs/api/painters.rst Normal file
View File

@ -0,0 +1,39 @@
Painters
========
Painters are used to draw the view.
Each painter adheres to the ``Painter`` protocol.
.. autoclass:: gaphas.painter.Painter
:members:
Some painters, such as ``FreeHandPainter`` and ``BoundingBoxPainter``, require a special painter protocol:
.. autoclass:: gaphas.painter.painter.ItemPainterType
:members:
Default implementations
-----------------------
.. autoclass:: gaphas.painter.PainterChain
:members: append, prepend
.. autoclass:: gaphas.painter.ItemPainter
.. autoclass:: gaphas.painter.HandlePainter
.. autoclass:: gaphas.painter.BoundingBoxPainter
.. autoclass:: gaphas.painter.FreeHandPainter
Rubberband tool
---------------
A special painter is used to display rubberband selection. This painter shares some state with
the rubberband tool.
.. autoclass:: gaphas.tool.rubberband.RubberbandPainter

18
docs/api/protocols.rst Normal file
View File

@ -0,0 +1,18 @@
Protocols
=========
Although ``gaphas.Canvas`` can be used as a default model, any class that adhere's to the Model protocol can be used as a model.
.. autoclass:: gaphas.view.model.Model
:members:
An item should implement these methods, so it can be rendered by the View. Not that painters or tools can require additional methods.
.. autoclass:: gaphas.item.Item
:members:
The view should support just thise one protocol, which will allow update requests to
propagate to the view:
.. automethod:: gaphas.view.model.View.request_update

14
docs/api/tools.rst Normal file
View File

@ -0,0 +1,14 @@
Tools
=====
Tools are used to interact with the view.
Each tool is basically a function that produces a ``Gtk.EventController``.
The event controllers are already configured.
.. autofunction:: gaphas.tool.hover_tool
.. autofunction:: gaphas.tool.item_tool
.. autofunction:: gaphas.tool.placement_tool
.. autofunction:: gaphas.tool.rubberband_tool
.. autofunction:: gaphas.tool.scroll_tool
.. autofunction:: gaphas.tool.zoom_tool

11
docs/api/variable.rst Normal file
View File

@ -0,0 +1,11 @@
Variables and Position
======================
.. autoclass:: gaphas.solver.Variable
:members:
.. autofunction:: gaphas.solver.variable
.. autoclass:: gaphas.position.Position

View File

@ -1,21 +1,7 @@
View
====
Protocols
---------
View is the central class in Gaphas. It shows your diagram and allows you to interact with it.
Although gaphas.Canvas can be used as a default model, any class that adhere's to the Model protocol can be used as a model.
.. autoclass:: gaphas.view.model.Model
:members:
The view should support just thise one protocol, which will allow update requests to
propagate to the view:
.. automethod:: gaphas.view.model.View.request_update
GtkView
-------
.. autoclass:: gaphas.view.GtkView
:members:

View File

@ -87,7 +87,7 @@ exclude_patterns = [
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
autodoc_member_order = "bysource"
autodoc_mock_imports = ["gi"]
# -- Options for HTML output -------------------------------------------------

View File

@ -19,8 +19,8 @@ Above rules are constraints, which need to be applied to a rectangular
item. The rules can be satisfied (constraints can be solved) using
`constraint solver <http://en.wikipedia.org/wiki/Constraint_satisfaction_problem>`_.
Gaphas implements its own constraint solver (`gaphas.solver` module).
Items can be constrained using APIs defined in `Canvas` and `Item` classes.
Gaphas implements its own constraint solver (`gaphas.solver.Solver`).
Items can be constrained using APIs defined in `Connection` class.
Constraints API
---------------
@ -38,13 +38,6 @@ positions, i.e.
If this API does not provide some constraint declaration, then one can
fallback to `Canvas` class constraint API.
Examples
--------
Item API
^^^^^^^^
Canvas API
^^^^^^^^^^
Further Reading
---------------
Theory and examples related to constraint solving
@ -59,4 +52,3 @@ There are other projects providing constraint solvers
- http://minion.sourceforge.net/
- http://labix.org/python-constraint
- http://www.cs.washington.edu/research/constraints/cassowary/

View File

@ -6,26 +6,7 @@ This class diagram describes the basic layout of Gaphas.
.. image:: gaphas-canvas.png
:width: 700
One-oh-one:
:doc:`api/canvas`
The main canvas class (container for Items)
:doc:`api/items`
Objects placed on a Canvas. Items can draw themselves, but not act on user events
:doc:`api/solver`
A constraint solver. Nice to have when you want to connect items together in a generic way.
:doc:`api/gtkview`
A view to be used in GTK+ applications. This view class is interactive. Interaction with users is handled by Tools.
:doc:`api/painters`
Painters are the workers when it comes to painting items.
:doc:`api/tools`
Tools are used to handle user events (such as mouse movement and button presses).
:doc:`api/aspects`
Tools do not modify the items directly. They use aspects (not the AOP kind) as intermediate step. Since aspects are defined as generic functions, the behaviour for each diagram item type can be slightly different.
Tools
-----
Several tools_ are used to make the overall user experience complete.
.. _tools: tools.html
The central class is ``GtkView``. It takes a model.
A default implementation is provided by `gaphas.Canvas`.
A view is rendered by ``Painter``s. Interaction is handled
by ``Tool``s.

View File

@ -389,9 +389,8 @@ solver.py: Variable
Variable's strength and value properties are observed:
>>> from gaphas.solver import Variable
>>> v = Variable()
>>> v = Variable(strength=100)
>>> v.value = 10
>>> v.strength = 100
>>> v
Variable(10, 100)
>>> undo()

View File

@ -26,21 +26,28 @@ class ConnectionError(Exception):
class Connections:
"""Manage connections and constraints."""
def __init__(self, solver: Optional[Solver] = None):
self._solver = solver or Solver()
self._connections = table.Table(Connection, list(range(4)))
solver = property(lambda s: s._solver)
solver = property(
lambda s: s._solver, doc="The solver used by this connections instance"
)
def solve(self):
"""Solve all constraints."""
self._solver.solve()
def add_constraint(self, item, constraint):
"""Add a "simple" constraint for an item."""
self._solver.add_constraint(constraint)
self._connections.insert(item, None, None, None, constraint, None)
return constraint
def remove_constraint(self, item, constraint):
"""Remove an item specific constraint."""
self._solver.remove_constraint(constraint)
self._connections.delete(item, None, None, None, constraint, None)
@ -51,25 +58,19 @@ class Connections:
"""Create a connection between two items. The connection is registered
and the constraint is added to the constraint solver.
The pair (item, handle) should be unique and not yet connected.
The pair ``(item, handle)`` should be unique and not yet connected.
The callback is invoked when the connection is broken.
:Parameters:
item
Connecting item (i.e. a line).
handle
Handle of connecting item.
connected
Connected item (i.e. a box).
port
Port of connected item.
constraint
Constraint to keep the connection in place.
callback
Function to be called on disconnection.
Args:
item (Item): Connecting item (i.e. a line).
handle (Handle): Handle of connecting item.
connected (Item): Connected item (i.e. a box).
port (Port): Port of connected item.
constraint (Constraint): Constraint to keep the connection in place.
callback (() -> None): Function to be called on disconnection.
ConnectionError is raised in case handle is already registered
``ConnectionError`` is raised in case handle is already registered
on a connection.
"""
if self.get_connection(handle):

View File

@ -18,32 +18,39 @@ from gaphas.state import (
@runtime_checkable
class Item(Protocol):
"""This protocol should be implemented by model items.
All items that are rendered on a view.
"""
@property
def matrix(self) -> Matrix:
...
"""The "local", item-to-parent matrix."""
@property
def matrix_i2c(self) -> Matrix:
...
"""Matrix from item to toplevel."""
def handles(self) -> Sequence[Handle]:
"""Return a list of handles owned by the item."""
def ports(self) -> Sequence[Port]:
"""Return list of ports."""
"""Return list of ports owned by the item."""
def point(self, x: float, y: float) -> float:
"""Get the distance from a point (``x``, ``y``) to the item.
``x`` and ``y`` are in item coordinates.
A distance of 0 means the point is on the item.
"""
def draw(self, context: Context):
"""Render the item to a canvas view. Context contains the following
attributes:
- cairo: the Cairo Context use this one to draw
- selected, focused, hovered, dropzone: view state of items
* `cairo`: the Cairo Context use this one to draw
* `selected`, `focused`, `hovered`, `dropzone`: view state of items
(True/False)
"""

View File

@ -7,10 +7,7 @@ from gaphas.painter.painter import Painter
class PainterChain:
"""Chain up a set of painters.
like ToolChain.
"""
"""Chain up a set of painters."""
def __init__(self):
self._painters: List[Painter] = []

View File

@ -22,15 +22,6 @@ class FreeHandCairoContext:
KAPPA = 0.5522847498
def __init__(self, cr, sloppiness=0.5):
"""Create context with given sloppiness. Range [0..2.0] gives
acceptable results.
* Draftsman: 0.0
* Artist: 0.25
* Cartoonist: 0.5
* Child: 1.0
* Drunk: 2.0
"""
self.cr = cr
self.sloppiness = sloppiness # In range 0.0 .. 2.0
@ -134,7 +125,19 @@ class FreeHandCairoContext:
class FreeHandPainter:
def __init__(self, subpainter: ItemPainterType, sloppiness=1.0):
"""This painter is a wrapper for an Item painter. The Cairo context is
modified to allow for a sloppy, hand written drawing style.
Range [0..2.0] gives acceptable results.
* Draftsman: 0.0
* Artist: 0.25
* Cartoonist: 0.5
* Child: 1.0
* Drunk: 2.0
"""
def __init__(self, subpainter: ItemPainterType, sloppiness=0.5):
self.subpainter = subpainter
self.sloppiness = sloppiness

View File

@ -21,20 +21,20 @@ class Position:
def _set_x(self, v):
self._x.value = v
x = property(lambda s: s._x, _set_x)
x = property(lambda s: s._x, _set_x, doc="Position.x")
def _set_y(self, v):
self._y.value = v
y = property(lambda s: s._y, _set_y)
y = property(lambda s: s._y, _set_y, doc="Position.y")
strength = property(lambda s: s._x.strength)
strength = property(lambda s: s._x.strength, doc="Strength.")
def _set_pos(self, pos):
"""Set handle position (Item coordinates)."""
self._x.value, self._y.value = pos
pos = property(lambda s: (s._x, s._y), _set_pos)
pos = property(lambda s: (s._x, s._y), _set_pos, doc="The position.")
def __str__(self):
return f"<{self.__class__.__name__} object on ({self._x}, {self._y})>"
@ -73,11 +73,13 @@ class MatrixProjection(Constraint):
self.solve_for(self._proj_pos.x)
def add_handler(self, handler):
"""Add a callback handler."""
if not self._handlers:
self.matrix.add_handler(self._on_matrix_changed)
super().add_handler(handler)
def remove_handler(self, handler):
"""Remove a previously assigned handler."""
super().remove_handler(handler)
if not self._handlers:
self.matrix.remove_handler(self._on_matrix_changed)
@ -88,9 +90,13 @@ class MatrixProjection(Constraint):
def _set_y(self, y):
self._proj_pos.y = y
pos = property(lambda s: s._proj_pos)
x = property(lambda s: s._proj_pos.x, _set_x)
y = property(lambda s: s._proj_pos.y, _set_y)
pos = property(lambda s: s._proj_pos, doc="The projected position")
x = property(
lambda s: s._proj_pos.x, _set_x, doc="The projected position's ``x`` part."
)
y = property(
lambda s: s._proj_pos.y, _set_y, doc="The projected position's ``y`` part."
)
def mark_dirty(self, var):
if var is self._orig_pos.x or var is self._orig_pos.y:

View File

@ -62,11 +62,13 @@ class variable:
class Variable:
"""Representation of a variable in the constraint solver.
Each Variable has a @value and a @strength. In a constraint the weakest
Each Variable has a ``value`` and a ``strength``. In a constraint the weakest
variables are changed.
You can even do some calculating with it. The Variable always represents a
float variable.
The ``variable`` decorator can be used to easily define variables in classes.
"""
def __init__(self, value: SupportsFloat = 0.0, strength: int = NORMAL):
@ -75,12 +77,15 @@ class Variable:
self._handlers: Set[Callable[[Variable], None]] = set()
def add_handler(self, handler: Callable[[Variable], None]):
"""Add a handler, to be invoked when the value changes."""
self._handlers.add(handler)
def remove_handler(self, handler: Callable[[Variable], None]):
"""Remove a handler."""
self._handlers.discard(handler)
def notify(self):
"""Notify all handlers."""
for handler in self._handlers:
handler(self)
@ -88,11 +93,12 @@ class Variable:
def _set_strength(self, strength):
self._strength = strength
strength = reversible_property(lambda s: s._strength, _set_strength)
strength = reversible_property(
lambda s: s._strength, _set_strength, doc="Strength."
)
def dirty(self):
"""Mark the variable dirty in both the constraint solver and attached
constraints.
"""Mark the variable dirty in all attached constraints.
Variables are marked dirty also during constraints solving to
solve all dependent constraints, i.e. two equals constraints

View File

@ -6,6 +6,7 @@ from gaphas.view import GtkView
def hover_tool(view: GtkView):
"""Highlight the currenly hovered item."""
ctrl = Gtk.EventControllerMotion.new(view)
ctrl.connect("motion", on_motion)
return ctrl

View File

@ -29,7 +29,8 @@ class MoveType(Protocol):
...
def item_tool(view):
def item_tool(view: GtkView):
"""Handle item movement and movement of handles."""
gesture = Gtk.GestureDrag.new(view)
drag_state = DragState()
gesture.connect("drag-begin", on_drag_begin, drag_state)

View File

@ -11,6 +11,7 @@ FactoryType = Callable[[], Item]
def placement_tool(view: GtkView, factory: FactoryType, handle_index: int):
"""Place a new item on the model."""
gesture = Gtk.GestureDrag.new(view)
placement_state = PlacementState(factory, handle_index)
gesture.connect("drag-begin", on_drag_begin, placement_state)

View File

@ -10,6 +10,12 @@ class RubberbandState:
class RubberbandPainter:
"""The rubberband painter should be used in conjunction with the rubberband
tool.
``RubberbandState`` should be shared between the two.
"""
def __init__(self, rubberband_state):
self.rubberband_state = rubberband_state
@ -27,6 +33,10 @@ class RubberbandPainter:
def rubberband_tool(view, rubberband_state):
"""Rubberband selection tool.
Should be used in conjunction with ``RubberbandPainter``.
"""
gesture = Gtk.GestureDrag.new(view)
gesture.connect("drag-begin", on_drag_begin, rubberband_state)
gesture.connect("drag-update", on_drag_update, rubberband_state)

View File

@ -4,6 +4,7 @@ from gaphas.view import GtkView
def scroll_tool(view: GtkView, speed=5):
"""Scroll tool recognized 2 finger scroll gestures."""
ctrl = Gtk.EventControllerScroll.new(
view, Gtk.EventControllerScrollFlags.BOTH_AXES,
)