Enable docformatter pre-commit hook

Signed-off-by: Dan Yeaw <dan@yeaw.me>
This commit is contained in:
Dan Yeaw 2020-09-15 20:23:50 -04:00
parent f6bd598ea1
commit bdd954c2c5
No known key found for this signature in database
GPG Key ID: 77A923EF537B61A4
42 changed files with 674 additions and 1208 deletions

View File

@ -22,3 +22,8 @@ repos:
hooks:
- id: isort
additional_dependencies: [toml]
- repo: https://github.com/myint/docformatter
rev: v1.3.1
hooks:
- id: docformatter
args: [--in-place]

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python
"""
A simple demo app.
"""A simple demo app.
It sports a small canvas and some trivial operations:
@ -11,7 +10,6 @@ It sports a small canvas and some trivial operations:
- Record state changes
- Play back state changes (= undo !) With visual updates
- Exports to SVG and PNG
"""
import math
@ -52,9 +50,7 @@ def undo_handler(event):
def factory(view, cls):
"""
Simple canvas item factory.
"""
"""Simple canvas item factory."""
def wrapper():
item = cls()
@ -65,13 +61,11 @@ def factory(view, cls):
class MyBox(Box):
"""Box with an example connection protocol.
"""
"""Box with an example connection protocol."""
class MyLine(Line):
"""Line with experimental connection protocol.
"""
"""Line with experimental connection protocol."""
def __init__(self):
super().__init__()
@ -93,9 +87,7 @@ class MyLine(Line):
class MyText(Text):
"""
Text with experimental connection protocol.
"""
"""Text with experimental connection protocol."""
def draw(self, context):
Text.draw(self, context)

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python
"""A simple example containing two boxes and a line.
"""
"""A simple example containing two boxes and a line."""
import gi
from gaphas import Canvas, GtkView

View File

@ -11,10 +11,8 @@ from gaphas.item import Element
def singledispatch(func):
"""
Wrapper around singledispatch(), with an extra compatibility function
so code will not break when upgrading from 1.0 to 1.1.
"""
"""Wrapper around singledispatch(), with an extra compatibility function so
code will not break when upgrading from 1.0 to 1.1."""
wrapped = real_singledispatch(func)
def when_type(*types):
@ -38,9 +36,7 @@ def singledispatch(func):
class ItemFinder:
"""
Find an item on the canvas.
"""
"""Find an item on the canvas."""
def __init__(self, view):
self.view = view
@ -54,11 +50,10 @@ Finder = singledispatch(ItemFinder)
class ItemSelection:
"""
A role for items. When dealing with selection.
"""A role for items. When dealing with selection.
Behaviour can be overridden by applying the @aspect decorator
to a subclass.
Behaviour can be overridden by applying the @aspect decorator to a
subclass.
"""
def __init__(self, item, view):
@ -66,9 +61,7 @@ class ItemSelection:
self.view = view
def select(self):
"""
Set selection on the view.
"""
"""Set selection on the view."""
self.view.focused_item = self.item
def unselect(self):
@ -80,8 +73,7 @@ Selection = singledispatch(ItemSelection)
class ItemInMotion:
"""
Aspect for dealing with motion on an item.
"""Aspect for dealing with motion on an item.
In this case the item is moved.
"""
@ -95,8 +87,9 @@ class ItemInMotion:
self.last_x, self.last_y = pos
def move(self, pos):
"""
Move the item. x and y are in view coordinates.
"""Move the item.
x and y are in view coordinates.
"""
item = self.item
view = self.view
@ -118,9 +111,7 @@ InMotion = singledispatch(ItemInMotion)
class ItemHandleFinder:
"""
Deals with the task of finding handles.
"""
"""Deals with the task of finding handles."""
def __init__(self, item, view):
self.item = item
@ -134,9 +125,7 @@ HandleFinder = singledispatch(ItemHandleFinder)
class ItemHandleSelection:
"""
Deal with selection of the handle.
"""
"""Deal with selection of the handle."""
def __init__(self, item, handle, view):
self.item = item
@ -172,9 +161,7 @@ class ElementHandleSelection(ItemHandleSelection):
class ItemHandleInMotion:
"""
Move a handle (role is applied to the handle)
"""
"""Move a handle (role is applied to the handle)"""
GLUE_DISTANCE = 10
@ -214,8 +201,7 @@ class ItemHandleInMotion:
pass
def glue(self, pos, distance=GLUE_DISTANCE):
"""
Glue to an item near a specific point.
"""Glue to an item near a specific point.
Returns a ConnectionSink or None.
"""
@ -250,9 +236,7 @@ HandleInMotion = singledispatch(ItemHandleInMotion)
class ItemConnector:
"""Connect or disconnect an item's handle to another item or port.
"""
"""Connect or disconnect an item's handle to another item or port."""
GLUE_DISTANCE = 10 # Glue distance in view points
@ -264,9 +248,7 @@ class ItemConnector:
return True
def glue(self, sink):
"""
Glue the Connector handle on the sink's port.
"""
"""Glue the Connector handle on the sink's port."""
handle = self.handle
item = self.item
matrix = item.canvas.get_matrix_i2i(item, sink.item)
@ -276,8 +258,7 @@ class ItemConnector:
handle.pos = matrix.transform_point(*gluepos)
def connect(self, sink):
"""
Connect the handle to a sink (item, port).
"""Connect the handle to a sink (item, port).
Note that connect() also takes care of disconnecting in case a
handle is reattached to another element.
@ -297,9 +278,8 @@ class ItemConnector:
self.connect_handle(sink)
def connect_handle(self, sink, callback=None):
"""
Create constraint between handle of a line and port of
connectable item.
"""Create constraint between handle of a line and port of connectable
item.
:Parameters:
sink
@ -318,9 +298,7 @@ class ItemConnector:
)
def disconnect(self):
"""
Disconnect the handle from the attached element.
"""
"""Disconnect the handle from the attached element."""
self.item.canvas.disconnect_item(self.item, self.handle)
@ -332,7 +310,6 @@ class ItemConnectionSink:
A sink is another item that an item's handle is connected to like a
connectable item or port.
"""
def __init__(self, item, port):
@ -340,8 +317,8 @@ class ItemConnectionSink:
self.port = port
def find_port(self, pos):
"""
Glue to the closest item on the canvas.
"""Glue to the closest item on the canvas.
If the item can connect, it returns a port.
"""
port = None
@ -365,10 +342,8 @@ ConnectionSink = singledispatch(ItemConnectionSink)
class ItemPaintFocused:
"""
Paints on top of all items, just for the focused item and only
when it's hovered (see gaphas.painter.FocusedItemPainter)
"""
"""Paints on top of all items, just for the focused item and only when it's
hovered (see gaphas.painter.FocusedItemPainter)"""
def __init__(self, item, view):
self.item = item

View File

@ -1,6 +1,5 @@
"""
A Canvas owns a set of Items and acts as a container for both the
items and a constraint solver.
"""A Canvas owns a set of Items and acts as a container for both the items and
a constraint solver.
Connections
===========
@ -24,7 +23,6 @@ To get all connected items (i.e. items on both sides of a line)::
To get connecting items (i.e. all lines connected to a class)::
lines = (c.item for c in canvas.get_connections(connected=item))
"""
import logging
from collections import namedtuple
@ -51,15 +49,12 @@ Connection = namedtuple("Connection", "item handle connected port constraint cal
class ConnectionError(Exception):
"""
Exception raised when there is an error when connecting an items
with each other.
"""
"""Exception raised when there is an error when connecting an items with
each other."""
class Context:
"""
Context used for updating and drawing items in a drawing canvas.
"""Context used for updating and drawing items in a drawing canvas.
>>> c=Context(one=1,two='two')
>>> c.one
@ -79,9 +74,7 @@ class Context:
def instant_cairo_context():
"""
A simple Cairo context, not attached to any window.
"""
"""A simple Cairo context, not attached to any window."""
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0)
return cairo.Context(surface)
@ -91,9 +84,7 @@ def default_update_context(item, cairo=instant_cairo_context()):
class Canvas:
"""
Container class for items.
"""
"""Container class for items."""
def __init__(self, create_update_context=default_update_context):
self._create_update_context = create_update_context
@ -110,8 +101,7 @@ class Canvas:
@observed
def add(self, item, parent=None, index=None):
"""
Add an item to the canvas.
"""Add an item to the canvas.
>>> c = Canvas()
>>> from gaphas import item
@ -134,10 +124,8 @@ class Canvas:
@observed
def _remove(self, item):
"""
Remove is done in a separate, @observed, method so the undo
system can restore removed items in the right order.
"""
"""Remove is done in a separate, @observed, method so the undo system
can restore removed items in the right order."""
item._set_canvas(None)
self._tree.remove(item)
self._update_views(removed_items=(item,))
@ -145,8 +133,7 @@ class Canvas:
self._dirty_matrix_items.discard(item)
def remove(self, item):
"""
Remove item from the canvas.
"""Remove item from the canvas.
>>> c = Canvas()
>>> from gaphas import item
@ -173,9 +160,7 @@ class Canvas:
@observed
def reparent(self, item, parent, index=None):
"""
Set new parent for an item.
"""
"""Set new parent for an item."""
self._tree.reparent(item, parent, index)
self._dirty_index = True
@ -190,8 +175,7 @@ class Canvas:
)
def get_all_items(self):
"""
Get a list of all items.
"""Get a list of all items.
>>> c = Canvas()
>>> c.get_all_items()
@ -205,8 +189,7 @@ class Canvas:
return self._tree.nodes
def get_root_items(self):
"""
Return the root items of the canvas.
"""Return the root items of the canvas.
>>> c = Canvas()
>>> c.get_all_items()
@ -222,8 +205,7 @@ class Canvas:
return self._tree.get_children(None)
def get_parent(self, item):
"""
See `tree.Tree.get_parent()`.
"""See `tree.Tree.get_parent()`.
>>> c = Canvas()
>>> from gaphas import item
@ -238,8 +220,7 @@ class Canvas:
return self._tree.get_parent(item)
def get_ancestors(self, item):
"""
See `tree.Tree.get_ancestors()`.
"""See `tree.Tree.get_ancestors()`.
>>> c = Canvas()
>>> from gaphas import item
@ -259,8 +240,7 @@ class Canvas:
return self._tree.get_ancestors(item)
def get_children(self, item):
"""
See `tree.Tree.get_children()`.
"""See `tree.Tree.get_children()`.
>>> c = Canvas()
>>> from gaphas import item
@ -280,8 +260,7 @@ class Canvas:
return self._tree.get_children(item)
def get_all_children(self, item):
"""
See `tree.Tree.get_all_children()`.
"""See `tree.Tree.get_all_children()`.
>>> c = Canvas()
>>> from gaphas import item
@ -304,10 +283,8 @@ class Canvas:
def connect_item(
self, item, handle, connected, port, constraint=None, callback=None
):
"""
Create a connection between two items. The connection is
registered and the constraint is added to the constraint
solver.
"""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.
@ -341,9 +318,10 @@ class Canvas:
self._solver.add_constraint(constraint)
def disconnect_item(self, item, handle=None):
"""
Disconnect the connections of an item. If handle is not None,
only the connection for that handle is disconnected.
"""Disconnect the connections of an item.
If handle is not None, only the connection for that handle is
disconnected.
"""
# disconnect on canvas level
for cinfo in list(self._connections.query(item=item, handle=handle)):
@ -351,9 +329,7 @@ class Canvas:
@observed
def _disconnect_item(self, item, handle, connected, port, constraint, callback):
"""
Perform the real disconnect.
"""
"""Perform the real disconnect."""
# Same arguments as connect_item, makes reverser easy
if constraint:
self._solver.remove_constraint(constraint)
@ -366,11 +342,11 @@ class Canvas:
reversible_pair(connect_item, _disconnect_item)
def remove_connections_to_item(self, item):
"""
Remove all connections (handles connected to and constraints)
for a specific item (to and from the item). This is some
brute force cleanup (e.g. if constraints are referenced by
items, those references are not cleaned up).
"""Remove all connections (handles connected to and constraints) for a
specific item (to and from the item).
This is some brute force cleanup (e.g. if constraints are
referenced by items, those references are not cleaned up).
"""
disconnect_item = self._disconnect_item
# remove connections from this item
@ -382,10 +358,9 @@ class Canvas:
@observed
def reconnect_item(self, item, handle, port=None, constraint=None):
"""
Update an existing connection. This is used to provide a new
constraint to the connection. ``item`` and ``handle`` are
the keys to the to-be-updated connection.
"""Update an existing connection. This is used to provide a new
constraint to the connection. ``item`` and ``handle`` are the keys to
the to-be-updated connection.
>>> c = Canvas()
>>> from gaphas import item
@ -423,7 +398,6 @@ class Canvas:
Traceback (most recent call last):
...
ValueError: No data available for item ...
"""
# checks:
cinfo = self.get_connection(handle)
@ -459,8 +433,7 @@ class Canvas:
)
def get_connection(self, handle):
"""
Get connection information for specified handle.
"""Get connection information for specified handle.
>>> c = Canvas()
>>> from gaphas.item import Line
@ -482,8 +455,7 @@ class Canvas:
return None
def get_connections(self, item=None, handle=None, connected=None, port=None):
"""
Return an iterator of connection information.
"""Return an iterator of connection information.
The list contains (item, handle). As a result an item may be
in the list more than once (depending on the number of handles
@ -518,9 +490,8 @@ class Canvas:
)
def sort(self, items, reverse=False):
"""
Sort a list of items in the order in which they are traversed
in the canvas (Depth first).
"""Sort a list of items in the order in which they are traversed in the
canvas (Depth first).
>>> c = Canvas()
>>> from gaphas import item
@ -540,8 +511,7 @@ class Canvas:
return sorted(items, key=attrgetter("_canvas_index"), reverse=reverse)
def get_matrix_i2c(self, item, calculate=False):
"""
Get the Item to Canvas matrix for ``item``.
"""Get the Item to Canvas matrix for ``item``.
item:
The item who's item-to-canvas transformation matrix should
@ -557,8 +527,8 @@ class Canvas:
return item._matrix_i2c
def get_matrix_c2i(self, item, calculate=False):
"""
Get the Canvas to Item matrix for ``item``.
"""Get the Canvas to Item matrix for ``item``.
See `get_matrix_i2c()`.
"""
if item._matrix_c2i is None or calculate:
@ -576,8 +546,7 @@ class Canvas:
@observed
def request_update(self, item, update=True, matrix=True):
"""
Set an update request for the item.
"""Set an update request for the item.
>>> c = Canvas()
>>> from gaphas import item
@ -601,15 +570,11 @@ class Canvas:
reversible_method(request_update, reverse=request_update)
def request_matrix_update(self, item):
"""
Schedule only the matrix to be updated.
"""
"""Schedule only the matrix to be updated."""
self.request_update(item, update=False, matrix=True)
def require_update(self):
"""
Returns ``True`` or ``False`` depending on if an update is
needed.
"""Returns ``True`` or ``False`` depending on if an update is needed.
>>> c=Canvas()
>>> c.require_update()
@ -627,10 +592,8 @@ class Canvas:
@AsyncIO(single=True)
def update(self):
"""
Update the canvas, if called from within a gtk-mainloop, the
update job is scheduled as idle job.
"""
"""Update the canvas, if called from within a gtk-mainloop, the update
job is scheduled as idle job."""
self.update_now()
def _pre_update_items(self, items):
@ -652,9 +615,7 @@ class Canvas:
@nonrecursive
def update_now(self):
"""
Perform an update of the items that requested an update.
"""
"""Perform an update of the items that requested an update."""
sort = self.sort
if self._dirty_index:
@ -713,9 +674,8 @@ class Canvas:
self._update_views(dirty_items, dirty_matrix_items)
def update_matrices(self, items):
"""
Recalculate matrices of the items. Items' children matrices
are recalculated, too.
"""Recalculate matrices of the items. Items' children matrices are
recalculated, too.
Return items, which matrices were recalculated.
"""
@ -736,9 +696,7 @@ class Canvas:
return changed
def update_matrix(self, item, parent=None):
"""
Update matrices of an item.
"""
"""Update matrices of an item."""
try:
orig_matrix_i2c = cairo.Matrix(*item._matrix_i2c)
except TypeError:
@ -759,9 +717,10 @@ class Canvas:
item._matrix_c2i.invert()
def update_constraints(self, items):
"""
Update constraints. Also variables may be marked as dirty
before the constraint solver kicks in.
"""Update constraints.
Also variables may be marked as dirty before the constraint
solver kicks in.
"""
# request solving of external constraints associated with dirty items
request_resolve = self._solver.request_resolve
@ -774,9 +733,8 @@ class Canvas:
self._solver.solve()
def _normalize(self, items):
"""
Update handle positions of items, so the first handle is
always located at (0, 0).
"""Update handle positions of items, so the first handle is always
located at (0, 0).
Return those items, which matrices changed due to first handle
movement.
@ -809,38 +767,37 @@ class Canvas:
return self.update_matrices(dirty_matrix_items)
def update_index(self):
"""
Provide each item in the canvas with an index attribute. This
makes for fast searching of items.
"""Provide each item in the canvas with an index attribute.
This makes for fast searching of items.
"""
self._tree.index_nodes("_canvas_index")
def register_view(self, view):
"""
Register a view on this canvas. This method is called when
setting a canvas on a view and should not be called directly
from user code.
"""Register a view on this canvas.
This method is called when setting a canvas on a view and should
not be called directly from user code.
"""
self._registered_views.add(view)
def unregister_view(self, view):
"""
Unregister a view on this canvas. This method is called when
setting a canvas on a view and should not be called directly
from user code.
"""Unregister a view on this canvas.
This method is called when setting a canvas on a view and should
not be called directly from user code.
"""
self._registered_views.discard(view)
def _update_views(self, dirty_items=(), dirty_matrix_items=(), removed_items=()):
"""
Send an update notification to all registered views.
"""
"""Send an update notification to all registered views."""
for v in self._registered_views:
v.request_update(dirty_items, dirty_matrix_items, removed_items)
def __getstate__(self):
"""
Persist canvas. Dirty item sets and views are not saved.
"""Persist canvas.
Dirty item sets and views are not saved.
"""
d = dict(self.__dict__)
for n in (
@ -856,8 +813,7 @@ class Canvas:
return d
def __setstate__(self, state):
"""
Load persisted state.
"""Load persisted state.
Before loading the state, the constructor is called.
"""
@ -869,8 +825,7 @@ class Canvas:
# self.update()
def project(self, item, *points):
"""
Project item's points into canvas coordinate system.
"""Project item's points into canvas coordinate system.
If there is only one point returned than projected point is
returned. If there are more than one points, then tuple of

View File

@ -1,6 +1,4 @@
"""
Basic connectors such as Ports and Handles.
"""
"""Basic connectors such as Ports and Handles."""
import functools
import warnings
@ -26,8 +24,7 @@ def deprecated(message, since):
class Position:
"""
A point constructed of two `Variable`'s.
"""A point constructed of two `Variable`'s.
>>> vp = Position((3, 5))
>>> vp.x, vp.y
@ -47,9 +44,7 @@ class Position:
self.y.strength = strength
def _set_pos(self, pos):
"""
Set handle position (Item coordinates).
"""
"""Set handle position (Item coordinates)."""
self.x, self.y = pos
pos = property(lambda s: (s.x, s.y), _set_pos)
@ -60,8 +55,7 @@ class Position:
__repr__ = __str__
def __getitem__(self, index):
"""
Shorthand for returning the x(0) or y(1) component of the point.
"""Shorthand for returning the x(0) or y(1) component of the point.
>>> h = Position((3, 5))
>>> h[0]
@ -73,8 +67,7 @@ class Position:
class Handle:
"""
Handles are used to support modifications of Items.
"""Handles are used to support modifications of Items.
If the handle is connected to an item, the ``connected_to``
property should refer to the item. A ``disconnect`` handler should
@ -163,7 +156,6 @@ class Port:
"""Port connectable part of an item.
The Item's handle connects to a port.
"""
def __init__(self):
@ -178,22 +170,16 @@ class Port:
connectable = reversible_property(lambda s: s._connectable, _set_connectable)
def glue(self, pos):
"""
Get glue point on the port and distance to the port.
"""
"""Get glue point on the port and distance to the port."""
raise NotImplementedError("Glue method not implemented")
def constraint(self, canvas, item, handle, glue_item):
"""
Create connection constraint between item's handle and glue item.
"""
"""Create connection constraint between item's handle and glue item."""
raise NotImplementedError("Constraint method not implemented")
class LinePort(Port):
"""
Port defined as a line between two handles.
"""
"""Port defined as a line between two handles."""
def __init__(self, start, end):
super().__init__()
@ -202,8 +188,7 @@ class LinePort(Port):
self.end = end
def glue(self, pos):
"""
Get glue point on the port and distance to the port.
"""Get glue point on the port and distance to the port.
>>> p1, p2 = (0.0, 0.0), (100.0, 100.0)
>>> port = LinePort(p1, p2)
@ -216,27 +201,22 @@ class LinePort(Port):
return pl, d
def constraint(self, canvas, item, handle, glue_item):
"""
Create connection line constraint between item's handle and
the port.
"""
"""Create connection line constraint between item's handle and the
port."""
line = canvas.project(glue_item, self.start, self.end)
point = canvas.project(item, handle.pos)
return LineConstraint(line, point)
class PointPort(Port):
"""
Port defined as a point.
"""
"""Port defined as a point."""
def __init__(self, point):
super().__init__()
self.point = point
def glue(self, pos):
"""
Get glue point on the port and distance to the port.
"""Get glue point on the port and distance to the port.
>>> h = Handle((10, 10))
>>> port = PointPort(h.pos)
@ -247,10 +227,8 @@ class PointPort(Port):
return self.point, d
def constraint(self, canvas, item, handle, glue_item):
"""
Return connection position constraint between item's handle
and the port.
"""
"""Return connection position constraint between item's handle and the
port."""
origin = canvas.project(glue_item, self.point)
point = canvas.project(item, handle.pos)
c = PositionConstraint(origin, point)

View File

@ -1,8 +1,7 @@
"""
This module contains several flavors of constraint classes. Each has
a method `Constraint.solve_for(name)` and a method
`Constraint.mark_dirty(v)`. These methods are used by the constraint
solver (`solver.Solver`) to set the variables.
"""This module contains several flavors of constraint classes. Each has a
method `Constraint.solve_for(name)` and a method `Constraint.mark_dirty(v)`.
These methods are used by the constraint solver (`solver.Solver`) to set the
variables.
Variables should be of type `solver.Variable`.
@ -41,8 +40,7 @@ def _update(variable, value):
class Constraint:
"""
Constraint base class.
"""Constraint base class.
- _variables - list of all variables
- _weakest - list of weakest variables
@ -51,12 +49,11 @@ class Constraint:
disabled = False
def __init__(self, *variables):
"""
Create new constraint, register all variables, and find
weakest variables.
"""Create new constraint, register all variables, and find weakest
variables.
Any value can be added. It is assumed to be a variable if it
has a 'strength' attribute.
Any value can be added. It is assumed to be a variable if it has
a 'strength' attribute.
"""
self._variables = []
for v in variables:
@ -69,33 +66,28 @@ class Constraint:
self._solver_has_projections = False
def create_weakest_list(self):
"""
Create list of weakest variables.
"""
"""Create list of weakest variables."""
# strength = min([v.strength for v in self._variables])
strength = min(v.strength for v in self._variables)
self._weakest = [v for v in self._variables if v.strength == strength]
def variables(self):
"""
Return an iterator which iterates over the variables that are
held by this constraint.
"""
"""Return an iterator which iterates over the variables that are held
by this constraint."""
return self._variables
def weakest(self):
"""
Return the weakest variable. The weakest variable should be
always as first element of Constraint._weakest list.
"""Return the weakest variable.
The weakest variable should be always as first element of
Constraint._weakest list.
"""
return self._weakest[0]
def mark_dirty(self, v):
"""
Mark variable v dirty and if possible move it to the end of
Constraint._weakest list to maintain weakest variable
invariants (see gaphas.solver module documentation).
"""
"""Mark variable v dirty and if possible move it to the end of
Constraint._weakest list to maintain weakest variable invariants (see
gaphas.solver module documentation)."""
weakest = self.weakest()
# Fast lane:
if v is weakest:
@ -114,27 +106,26 @@ class Constraint:
return
def solve(self):
"""
Solve the constraint. This is done by determining the weakest
variable and calling solve_for() for that variable. The
weakest variable is always in the set of variables with the
weakest strength. The least recently changed variable is
considered the weakest.
"""Solve the constraint.
This is done by determining the weakest variable and calling
solve_for() for that variable. The weakest variable is always in
the set of variables with the weakest strength. The least
recently changed variable is considered the weakest.
"""
wvar = self.weakest()
self.solve_for(wvar)
def solve_for(self, var):
"""
Solve the constraint for a given variable.
"""Solve the constraint for a given variable.
The variable itself is updated.
"""
raise NotImplementedError
class EqualsConstraint(Constraint):
"""
Constraint, which ensures that two arguments ``a`` and ``b`` are equal:
"""Constraint, which ensures that two arguments ``a`` and ``b`` are equal:
a + delta = b
@ -172,10 +163,9 @@ class EqualsConstraint(Constraint):
class CenterConstraint(Constraint):
"""
Simple Constraint, takes three arguments: 'a', 'b' and center.
When solved, the constraint ensures 'center' is located in the
middle of 'a' and 'b'.
"""Simple Constraint, takes three arguments: 'a', 'b' and center. When
solved, the constraint ensures 'center' is located in the middle of 'a' and
'b'.
>>> from gaphas.solver import Variable
>>> a, b, center = Variable(1.0), Variable(3.0), Variable()
@ -207,11 +197,9 @@ class CenterConstraint(Constraint):
class LessThanConstraint(Constraint):
"""
Ensure ``smaller`` is less than ``bigger``. The variable that is
passed as to-be-solved is left alone (cause it is the variable
that has not been moved lately). Instead the other variable is
solved.
"""Ensure ``smaller`` is less than ``bigger``. The variable that is passed
as to-be-solved is left alone (cause it is the variable that has not been
moved lately). Instead the other variable is solved.
>>> from gaphas.solver import Variable
>>> a, b = Variable(3.0), Variable(2.0)
@ -254,8 +242,7 @@ ITERLIMIT = 1000 # iteration limit
class EquationConstraint(Constraint):
"""
Equation solver using attributes and introspection.
"""Equation solver using attributes and introspection.
Takes a function, named arg value (opt.) and returns a Constraint
object Calling EquationConstraint.solve_for will solve the
@ -294,16 +281,12 @@ class EquationConstraint(Constraint):
return f"EquationConstraint({self._f.__code__.co_name})"
def __getattr__(self, name):
"""
Used to extract function argument values.
"""
"""Used to extract function argument values."""
self._args[name]
return self.solve_for(name)
def __setattr__(self, name, value):
"""
Sets function argument values.
"""
"""Sets function argument values."""
# Note - once self._args is created, no new attributes can
# be added to self.__dict__. This is a good thing as it throws
# an exception if you try to assign to an arg which is inappropriate
@ -319,18 +302,14 @@ class EquationConstraint(Constraint):
object.__setattr__(self, name, value)
def _set(self, **args):
"""
Sets values of function arguments.
"""
"""Sets values of function arguments."""
for arg in args:
self._args[arg] # raise exception if arg not in _args
setattr(self, arg, args[arg])
def solve_for(self, var):
"""
Solve this constraint for the variable named 'arg' in the
constraint.
"""
"""Solve this constraint for the variable named 'arg' in the
constraint."""
args = {}
for nm, v in list(self._args.items()):
args[nm] = v.value
@ -341,9 +320,7 @@ class EquationConstraint(Constraint):
var.value = v
def _solve_for(self, arg, args):
"""
Newton's method solver
"""
"""Newton's method solver."""
# args = self._args
close_runs = 10 # after getting close, do more passes
if args[arg]:
@ -356,7 +333,7 @@ class EquationConstraint(Constraint):
x1 = x0 * 1.1
def f(x):
"""function to solve"""
"""function to solve."""
args[arg] = x
return self._f(**args)
@ -393,9 +370,8 @@ class EquationConstraint(Constraint):
class BalanceConstraint(Constraint):
"""
Ensure that a variable ``v`` is between values specified by
``band`` and in distance proportional from ``band[0]``.
"""Ensure that a variable ``v`` is between values specified by ``band`` and
in distance proportional from ``band[0]``.
Consider
@ -443,8 +419,7 @@ class BalanceConstraint(Constraint):
class LineConstraint(Constraint):
"""
Ensure a point is kept on a line.
"""Ensure a point is kept on a line.
Attributes:
- _line: line defined by tuple ((x1, y1), (x2, y2))
@ -494,8 +469,7 @@ class LineConstraint(Constraint):
self._solve()
def _solve(self):
"""
Solve the equation for the connected_handle.
"""Solve the equation for the connected_handle.
>>> from gaphas.solver import Variable
>>> line = (Variable(0), Variable(0)), (Variable(30), Variable(20))
@ -523,8 +497,7 @@ class LineConstraint(Constraint):
class PositionConstraint(Constraint):
"""
Ensure that point is always in origin position.
"""Ensure that point is always in origin position.
Attributes:
- _origin: origin position
@ -538,19 +511,16 @@ class PositionConstraint(Constraint):
self._point = point
def solve_for(self, var=None):
"""
Ensure that point's coordinates are the same as coordinates of the
origin position.
"""
"""Ensure that point's coordinates are the same as coordinates of the
origin position."""
x, y = self._origin[0].value, self._origin[1].value
_update(self._point[0], x)
_update(self._point[1], y)
class LineAlignConstraint(Constraint):
"""
Ensure a point is kept on a line in position specified by align
and padding information.
"""Ensure a point is kept on a line in position specified by align and
padding information.
Align is specified as a number between 0 and 1, for example
0

View File

@ -1,6 +1,4 @@
"""
Custom decorators.
"""
"""Custom decorators."""
import threading
import gi
@ -14,11 +12,9 @@ DEBUG_ASYNC = False
class AsyncIO:
"""
Instead of calling the function, schedule an idle handler at a
given priority. This requires the async'ed method to be called
from within the GTK main loop. Otherwise the method is executed
directly.
"""Instead of calling the function, schedule an idle handler at a given
priority. This requires the async'ed method to be called from within the
GTK main loop. Otherwise the method is executed directly.
Note:
the current implementation of async single mode only works for
@ -147,8 +143,7 @@ class AsyncIO:
def nonrecursive(func):
"""
Enforce a function or method is not executed recursively:
"""Enforce a function or method is not executed recursively:
>>> class A(object):
... @nonrecursive
@ -163,9 +158,8 @@ def nonrecursive(func):
m = threading.Lock()
def wrapper(*args, **kwargs):
"""
Decorate function with a mutex that prohibits recursive execution.
"""
"""Decorate function with a mutex that prohibits recursive
execution."""
if m.acquire(False):
try:
return func(*args, **kwargs)
@ -176,8 +170,7 @@ def nonrecursive(func):
class recursive:
"""
This decorator limits the recursion for a specific function
"""This decorator limits the recursion for a specific function.
>>> class A(object):
... def __init__(self): self.r = 0

View File

@ -1,5 +1,5 @@
"""
Simple example items.
"""Simple example items.
These items are used in various tests.
"""
from gaphas.connector import Handle, LinePort, PointPort, Position
@ -9,9 +9,9 @@ from gaphas.util import path_ellipse, text_align, text_multiline
class Box(Element):
""" A Box has 4 handles (for a start):
NW +---+ NE
SW +---+ SE
"""A Box has 4 handles (for a start):
NW +---+ NE SW +---+ SE
"""
def __init__(self, width=10, height=10):
@ -31,8 +31,7 @@ class Box(Element):
class PortoBox(Box):
"""
Box item with few flavours of port(o)s.
"""Box item with few flavours of port(o)s.
Default box ports are disabled. Three, non-default connectable
ports are created (represented by ``x`` on the picture).
@ -116,9 +115,7 @@ class PortoBox(Box):
class Text(Item):
"""
Simple item showing some text on the canvas.
"""
"""Simple item showing some text on the canvas."""
def __init__(self, text=None, plain=False, multiline=False, align_x=1, align_y=-1):
super().__init__()
@ -142,8 +139,7 @@ class Text(Item):
class FatLine(Item):
"""
Simple, vertical line with two handles.
"""Simple, vertical line with two handles.
todo: rectangle port instead of line port would be nicer
"""

View File

@ -1,5 +1,4 @@
"""
Cairo context using Steve Hanov's freehand drawing code.
"""Cairo context using Steve Hanov's freehand drawing code.
# Crazyline. By Steve Hanov, 2008 Released to the public domain.
@ -21,9 +20,8 @@ class FreeHandCairoContext:
KAPPA = 0.5522847498
def __init__(self, cr, sloppiness=0.5):
"""
Create context with given sloppiness. Range [0..2.0] gives acceptable
results.
"""Create context with given sloppiness. Range [0..2.0] gives
acceptable results.
* Draftsman: 0.0
* Artist: 0.25

View File

@ -1,20 +1,17 @@
"""
Geometry functions.
"""Geometry functions.
Rectangle is a utility class for working with rectangles (unions and
intersections).
A point is represented as a tuple `(x, y)`.
"""
from math import sqrt
class Rectangle:
"""
Python Rectangle implementation. Rectangles can be added (union),
substituted (intersection) and points and rectangles can be tested
to be in the rectangle.
"""Python Rectangle implementation. Rectangles can be added (union),
substituted (intersection) and points and rectangles can be tested to be in
the rectangle.
>>> r1= Rectangle(1,1,5,5)
>>> r2 = Rectangle(3,3,6,7)
@ -54,8 +51,7 @@ class Rectangle:
self.height = height
def _set_x1(self, x1):
"""
"""
""""""
width = x1 - self.x
if width < 0:
width = 0
@ -64,8 +60,7 @@ class Rectangle:
x1 = property(lambda s: s.x + s.width, _set_x1)
def _set_y1(self, y1):
"""
"""
""""""
height = y1 - self.y
if height < 0:
height = 0
@ -128,9 +123,8 @@ class Rectangle:
)
def __add__(self, obj):
"""
Create a new Rectangle is the union of the current rectangle
with another Rectangle, tuple `(x,y)` or tuple `(x, y, width, height)`.
"""Create a new Rectangle is the union of the current rectangle with
another Rectangle, tuple `(x,y)` or tuple `(x, y, width, height)`.
>>> r=Rectangle(5, 7, 20, 25)
>>> r + (0, 0)
@ -172,9 +166,8 @@ class Rectangle:
return self
def __sub__(self, obj):
"""
Create a new Rectangle is the union of the current rectangle
with another Rectangle or tuple (x, y, width, height).
"""Create a new Rectangle is the union of the current rectangle with
another Rectangle or tuple (x, y, width, height).
>>> r = Rectangle(5, 7, 20, 25)
>>> r - (20, 30, 40, 50)
@ -216,9 +209,8 @@ class Rectangle:
return self
def __contains__(self, obj):
"""
Check if a point `(x, y)` in inside rectangle `(x, y, width, height)`
or if a rectangle instance is inside with the rectangle.
"""Check if a point `(x, y)` in inside rectangle `(x, y, width,
height)` or if a rectangle instance is inside with the rectangle.
>>> r=Rectangle(10, 5, 12, 12)
>>> (0, 0) in r
@ -258,8 +250,7 @@ class Rectangle:
def distance_point_point(point1, point2=(0.0, 0.0)):
"""
Return the distance from point ``point1`` to ``point2``.
"""Return the distance from point ``point1`` to ``point2``.
>>> f"{distance_point_point((0,0), (1,1))}:.3f"
'1.414'
@ -270,10 +261,8 @@ def distance_point_point(point1, point2=(0.0, 0.0)):
def distance_point_point_fast(point1, point2=(0.0, 0.0)):
"""
Return the distance from point ``point1`` to ``point2``. This
version is faster than ``distance_point_point()``, but less
precise.
"""Return the distance from point ``point1`` to ``point2``. This version is
faster than ``distance_point_point()``, but less precise.
>>> distance_point_point_fast((0,0), (1,1))
2
@ -284,9 +273,8 @@ def distance_point_point_fast(point1, point2=(0.0, 0.0)):
def distance_rectangle_point(rect, point):
"""
Return the distance (fast) from a rectangle ``(x, y, width,height)``
to a ``point``.
"""Return the distance (fast) from a rectangle ``(x, y, width,height)`` to
a ``point``.
>>> distance_rectangle_point(Rectangle(0, 0, 10, 10), (11, -1))
2
@ -379,9 +367,8 @@ def point_on_rectangle(rect, point, border=False):
def distance_line_point(line_start, line_end, point):
"""
Calculate the distance of a ``point`` from a line. The line is
marked by begin and end point ``line_start`` and ``line_end``.
"""Calculate the distance of a ``point`` from a line. The line is marked by
begin and end point ``line_start`` and ``line_end``.
A tuple is returned containing the distance and point on the line.
@ -429,10 +416,9 @@ def distance_line_point(line_start, line_end, point):
def intersect_line_line(line1_start, line1_end, line2_start, line2_end):
"""
Find the point where the lines (segments) defined by
``(line1_start, line1_end)`` and ``(line2_start, line2_end)``
intersect. If no intersection occurs, ``None`` is returned.
"""Find the point where the lines (segments) defined by ``(line1_start,
line1_end)`` and ``(line2_start, line2_end)`` intersect. If no
intersection occurs, ``None`` is returned.
>>> intersect_line_line((3, 0), (8, 10), (0, 0), (10, 10))
(6, 6)
@ -567,17 +553,14 @@ def intersect_line_line(line1_start, line1_end, line2_start, line2_end):
def rectangle_contains(inner, outer):
"""
Returns True if ``inner`` rect is contained in ``outer`` rect.
"""
"""Returns True if ``inner`` rect is contained in ``outer`` rect."""
ix, iy, iw, ih = inner
ox, oy, ow, oh = outer
return ox <= ix and oy <= iy and ox + ow >= ix + iw and oy + oh >= iy + ih
def rectangle_intersects(recta, rectb):
"""
Return True if ``recta`` and ``rectb`` intersect.
"""Return True if ``recta`` and ``rectb`` intersect.
>>> rectangle_intersects((5,5,20, 20), (10, 10, 1, 1))
True
@ -590,9 +573,8 @@ def rectangle_intersects(recta, rectb):
def rectangle_clip(recta, rectb):
"""
Return the clipped rectangle of ``recta`` and ``rectb``. If they
do not intersect, ``None`` is returned.
"""Return the clipped rectangle of ``recta`` and ``rectb``. If they do not
intersect, ``None`` is returned.
>>> rectangle_clip((0, 0, 20, 20), (10, 10, 20, 20))
(10, 10, 10, 10)

View File

@ -1,6 +1,4 @@
"""
Module implements guides when moving items and handles around.
"""
"""Module implements guides when moving items and handles around."""
from functools import reduce
from gaphas.aspect import (
@ -16,23 +14,17 @@ from gaphas.item import Element, Item, Line
class ItemGuide:
"""
Get edges on an item, on which we can align the items.
"""
"""Get edges on an item, on which we can align the items."""
def __init__(self, item):
self.item = item
def horizontal(self):
"""
Return horizontal edges (on y axis)
"""
"""Return horizontal edges (on y axis)"""
return ()
def vertical(self):
"""
Return vertical edges (on x axis)
"""
"""Return vertical edges (on x axis)"""
return ()
@ -41,9 +33,7 @@ Guide = singledispatch(ItemGuide)
@Guide.register(Element)
class ElementGuide(ItemGuide):
"""Guide to align Element items.
"""
"""Guide to align Element items."""
def horizontal(self):
y = self.item.height
@ -56,9 +46,7 @@ class ElementGuide(ItemGuide):
@Guide.register(Line)
class LineGuide(ItemGuide):
"""Guide for orthogonal lines.
"""
"""Guide for orthogonal lines."""
def horizontal(self):
line = self.item
@ -98,9 +86,7 @@ class Guides:
class GuideMixin:
"""
Helper methods for guides.
"""
"""Helper methods for guides."""
MARGIN = 2
@ -153,9 +139,7 @@ class GuideMixin:
return dy, edges_y
def get_excluded_items(self):
"""
Get a set of items excluded from guide calculation.
"""
"""Get a set of items excluded from guide calculation."""
item = self.item
view = self.view
@ -206,10 +190,8 @@ class GuideMixin:
@InMotion.register(Item)
class GuidedItemInMotion(GuideMixin, ItemInMotion):
"""
Move the item, lock position on any element that's located at the
same location.
"""
"""Move the item, lock position on any element that's located at the same
location."""
def move(self, pos):
item = self.item
@ -256,8 +238,8 @@ class GuidedItemInMotion(GuideMixin, ItemInMotion):
class GuidedItemHandleInMotion(GuideMixin, ItemHandleInMotion):
"""Move a handle and lock the position of other elements.
Locks the position of another element that's located at the same position.
Locks the position of another element that's located at the same
position.
"""
def move(self, pos):

View File

@ -1,6 +1,4 @@
"""
Basic items.
"""
"""Basic items."""
from math import atan2
from weakref import WeakKeyDictionary, WeakSet
@ -23,8 +21,7 @@ from gaphas.state import (
class Item:
"""
Base class (or interface) for items on a canvas.Canvas.
"""Base class (or interface) for items on a canvas.Canvas.
Attributes:
@ -64,9 +61,9 @@ class Item:
@observed
def _set_canvas(self, canvas):
"""
Set the canvas. Should only be called from Canvas.add and
Canvas.remove().
"""Set the canvas.
Should only be called from Canvas.add and Canvas.remove().
"""
assert not canvas or not self._canvas or self._canvas is canvas
if self._canvas:
@ -82,8 +79,8 @@ class Item:
constraints = property(lambda s: s._constraints, doc="Item constraints")
def setup_canvas(self):
"""
Called when the canvas is set for the item.
"""Called when the canvas is set for the item.
This method can be used to create constraints.
"""
add = self.canvas.solver.add_constraint
@ -91,8 +88,8 @@ class Item:
add(c)
def teardown_canvas(self):
"""
Called when the canvas is unset for the item.
"""Called when the canvas is unset for the item.
This method can be used to dispose constraints.
"""
self.canvas.disconnect_item(self)
@ -103,9 +100,7 @@ class Item:
@observed
def _set_matrix(self, matrix):
"""
Set the conversion matrix (parent -> item)
"""
"""Set the conversion matrix (parent -> item)"""
if not isinstance(matrix, Matrix):
matrix = Matrix(*matrix)
self._matrix = matrix
@ -117,8 +112,7 @@ class Item:
self._canvas.request_update(self, update=update, matrix=matrix)
def pre_update(self, context):
"""
Perform any changes before item update here, for example:
"""Perform any changes before item update here, for example:
- change matrix
- move handles
@ -130,8 +124,7 @@ class Item:
pass
def post_update(self, context):
"""
Method called after item update.
"""Method called after item update.
If some variables should be used during drawing or in another
update, then they should be calculated in post method.
@ -144,9 +137,8 @@ class Item:
pass
def normalize(self):
"""
Update handle positions of the item, so the first handle is
always located at (0, 0).
"""Update handle positions of the item, so the first handle is always
located at (0, 0).
Note that, since this method basically does some housekeeping
during the update phase, there's no need to keep track of the
@ -178,9 +170,8 @@ class Item:
return updated
def draw(self, context):
"""
Render the item to a canvas view.
Context contains the following attributes:
"""Render the item to a canvas view. Context contains the following
attributes:
- cairo: the Cairo Context use this one to draw
- view: the view that is to be rendered to
@ -192,20 +183,16 @@ class Item:
pass
def handles(self):
"""
Return a list of handles owned by the item.
"""
"""Return a list of handles owned by the item."""
return self._handles
def ports(self):
"""
Return list of ports.
"""
"""Return list of ports."""
return self._ports
def point(self, pos):
"""
Get the distance from a point (``x``, ``y``) to the item.
"""Get the distance from a point (``x``, ``y``) to the item.
``x`` and ``y`` are in item coordinates.
"""
pass
@ -220,8 +207,7 @@ class Item:
delta=0.0,
align=None,
):
"""
Utility (factory) method to create item's internal constraint
"""Utility (factory) method to create item's internal constraint
between two positions or between a position and a line.
Position is a tuple of coordinates, i.e. ``(2, 4)``.
@ -273,9 +259,7 @@ class Item:
return cc
def __getstate__(self):
"""
Persist all, but calculated values (``_matrix_?2?``).
"""
"""Persist all, but calculated values (``_matrix_?2?``)."""
d = dict(self.__dict__)
for n in ("_matrix_i2c", "_matrix_c2i", "_matrix_i2v", "_matrix_v2i"):
try:
@ -286,8 +270,9 @@ class Item:
return d
def __setstate__(self, state):
"""
Set state. No ``__init__()`` is called.
"""Set state.
No ``__init__()`` is called.
"""
for n in ("_matrix_i2c", "_matrix_c2i"):
setattr(self, n, None)
@ -301,12 +286,9 @@ class Item:
class Element(Item):
"""
An Element has 4 handles (for a start)::
"""An Element has 4 handles (for a start)::
NW +---+ NE
| |
SW +---+ SE
NW +---+ NE | | SW +---+ SE
"""
min_width = solvable(strength=REQUIRED, varname="_min_width")
@ -370,10 +352,8 @@ class Element(Item):
h[SE].pos.x = h[NW].pos.x + width
def _get_width(self):
"""
Width of the box, calculated as the distance from the left and
right handle.
"""
"""Width of the box, calculated as the distance from the left and right
handle."""
h = self._handles
return float(h[SE].pos.x) - float(h[NW].pos.x)
@ -397,18 +377,14 @@ class Element(Item):
h[SE].pos.y = h[NW].pos.y + height
def _get_height(self):
"""
Height.
"""
"""Height."""
h = self._handles
return float(h[SE].pos.y) - float(h[NW].pos.y)
height = property(_get_height, _set_height)
def normalize(self):
"""
Normalize only NW and SE handles
"""
"""Normalize only NW and SE handles."""
updated = False
handles = self._handles
handles = (handles[NW], handles[SE])
@ -426,8 +402,7 @@ class Element(Item):
return updated
def point(self, pos):
"""
Distance from the point (x, y) to the item.
"""Distance from the point (x, y) to the item.
>>> e = Element()
>>> e.point((20, 10))
@ -441,8 +416,7 @@ class Element(Item):
class Line(Item):
"""
A Line item.
"""A Line item.
Properties:
- fuzziness (0.0..n): an extra margin that should be taken into
@ -486,10 +460,11 @@ class Line(Item):
fuzziness = reversible_property(lambda s: s._fuzziness, _set_fuzziness)
def _update_orthogonal_constraints(self, orthogonal):
"""
Update the constraints required to maintain the orthogonal line.
The actual constraints attribute (``_orthogonal_constraints``) is
observed, so the undo system will update the contents properly
"""Update the constraints required to maintain the orthogonal line.
The actual constraints attribute (``_orthogonal_constraints``)
is observed, so the undo system will update the contents
properly
"""
if not self.canvas:
self._orthogonal_constraints = orthogonal and [None] or []
@ -523,9 +498,9 @@ class Line(Item):
@observed
def _set_orthogonal_constraints(self, orthogonal_constraints):
"""
Setter for the constraints maintained. Required for the undo
system.
"""Setter for the constraints maintained.
Required for the undo system.
"""
self._orthogonal_constraints = orthogonal_constraints
@ -573,16 +548,15 @@ class Line(Item):
horizontal = reversible_property(lambda s: s._horizontal, _set_horizontal)
def setup_canvas(self):
"""
Setup constraints. In this case orthogonal.
"""Setup constraints.
In this case orthogonal.
"""
super().setup_canvas()
self._update_orthogonal_constraints(self.orthogonal)
def teardown_canvas(self):
"""
Remove constraints created in setup_canvas().
"""
"""Remove constraints created in setup_canvas()."""
super().teardown_canvas()
for c in self._orthogonal_constraints:
self.canvas.solver.remove_constraint(c)
@ -622,9 +596,10 @@ class Line(Item):
return LinePort(p1, p2)
def _update_ports(self):
"""
Update line ports. This destroys all previously created ports
and should only be used when initializing the line.
"""Update line ports.
This destroys all previously created ports and should only be
used when initializing the line.
"""
assert len(self._handles) >= 2, "Not enough segments"
self._ports = []
@ -633,9 +608,7 @@ class Line(Item):
self._ports.append(self._create_port(h1.pos, h2.pos))
def opposite(self, handle):
"""
Given the handle of one end of the line, return the other end.
"""
"""Given the handle of one end of the line, return the other end."""
handles = self._handles
if handle is handles[0]:
return handles[-1]
@ -645,8 +618,7 @@ class Line(Item):
raise KeyError("Handle is not an end handle")
def post_update(self, context):
"""
"""
""""""
super().post_update(context)
h0, h1 = self._handles[:2]
p0, p1 = h0.pos, h1.pos
@ -675,20 +647,16 @@ class Line(Item):
return max(0, distance - self.fuzziness)
def draw_head(self, context):
"""
Default head drawer: move cursor to the first handle.
"""
"""Default head drawer: move cursor to the first handle."""
context.cairo.move_to(0, 0)
def draw_tail(self, context):
"""
Default tail drawer: draw line to the last handle.
"""
"""Default tail drawer: draw line to the last handle."""
context.cairo.line_to(0, 0)
def draw(self, context):
"""
Draw the line itself.
"""Draw the line itself.
See Item.draw(context).
"""

View File

@ -1,6 +1,5 @@
"""
Some Gaphor specific updates to the canvas. This is done by setting
the correct properties on gaphas' modules.
"""Some Gaphor specific updates to the canvas. This is done by setting the
correct properties on gaphas' modules.
Matrix
------
@ -14,8 +13,7 @@ from gaphas.state import observed, reversible_method
class Matrix:
"""
Matrix wrapper. This version sends @observed messages on state changes
"""Matrix wrapper. This version sends @observed messages on state changes.
>>> cairo.Matrix()
cairo.Matrix(1, 0, 0, 1, 0, 0)

View File

@ -1,5 +1,4 @@
"""
The painter module provides different painters for parts of the canvas.
"""The painter module provides different painters for parts of the canvas.
Painters can be swapped in and out.
@ -20,9 +19,7 @@ TOLERANCE = 0.8
class Painter:
"""
Painter interface.
"""
"""Painter interface."""
def __init__(self, view=None):
self.view = view
@ -31,15 +28,13 @@ class Painter:
self.view = view
def paint(self, context):
"""
Do the paint action (called from the View).
"""
"""Do the paint action (called from the View)."""
pass
class PainterChain(Painter):
"""
Chain up a set of painters.
"""Chain up a set of painters.
like ToolChain.
"""
@ -53,32 +48,26 @@ class PainterChain(Painter):
painter.set_view(self.view)
def append(self, painter):
"""
Add a painter to the list of painters.
"""
"""Add a painter to the list of painters."""
self._painters.append(painter)
painter.set_view(self.view)
return self
def prepend(self, painter):
"""
Add a painter to the beginning of the list of painters.
"""
"""Add a painter to the beginning of the list of painters."""
self._painters.insert(0, painter)
def paint(self, context):
"""
See Painter.paint().
"""
"""See Painter.paint()."""
for painter in self._painters:
painter.paint(context)
class DrawContext(Context):
"""
Special context for draw()'ing the item. The draw-context contains
stuff like the cairo context and properties like selected and
focused.
"""Special context for draw()'ing the item.
The draw-context contains stuff like the cairo context and
properties like selected and focused.
"""
def __init__(self, **kwargs):
@ -113,9 +102,7 @@ class ItemPainter(Painter):
cairo.restore()
def draw_items(self, items, cairo):
"""
Draw the items.
"""
"""Draw the items."""
for item in items:
self.draw_item(item, cairo)
if DEBUG_DRAW_BOUNDING_BOX:
@ -144,11 +131,9 @@ class ItemPainter(Painter):
class CairoBoundingBoxContext:
"""
Delegate all calls to the wrapped CairoBoundingBoxContext,
intercept ``stroke()``, ``fill()`` and a few others so the
bounding box of the item involved can be calculated.
"""
"""Delegate all calls to the wrapped CairoBoundingBoxContext, intercept
``stroke()``, ``fill()`` and a few others so the bounding box of the item
involved can be calculated."""
def __init__(self, cairo):
self._cairo = cairo
@ -158,9 +143,7 @@ class CairoBoundingBoxContext:
return getattr(self._cairo, key)
def get_bounds(self):
"""
Return the bounding box.
"""
"""Return the bounding box."""
return self._bounds or Rectangle()
def _update_bounds(self, bounds):
@ -171,9 +154,9 @@ class CairoBoundingBoxContext:
self._bounds += bounds
def _extents(self, extents_func, line_width=False):
"""
Calculate the bounding box for a given drawing operation. if
``line_width`` is True, the current line-width is taken into
"""Calculate the bounding box for a given drawing operation.
if ``line_width`` is True, the current line-width is taken into
account.
"""
cr = self._cairo
@ -191,43 +174,33 @@ class CairoBoundingBoxContext:
return b
def fill(self, b=None):
"""
Interceptor for Cairo drawing method.
"""
"""Interceptor for Cairo drawing method."""
cr = self._cairo
if not b:
b = self._extents(cr.fill_extents)
cr.fill()
def fill_preserve(self, b=None):
"""
Interceptor for Cairo drawing method.
"""
"""Interceptor for Cairo drawing method."""
cr = self._cairo
if not b:
b = self._extents(cr.fill_extents)
def stroke(self, b=None):
"""
Interceptor for Cairo drawing method.
"""
"""Interceptor for Cairo drawing method."""
cr = self._cairo
if not b:
b = self._extents(cr.stroke_extents, line_width=True)
cr.stroke()
def stroke_preserve(self, b=None):
"""
Interceptor for Cairo drawing method.
"""
"""Interceptor for Cairo drawing method."""
cr = self._cairo
if not b:
b = self._extents(cr.stroke_extents, line_width=True)
def show_text(self, utf8, b=None):
"""
Interceptor for Cairo drawing method.
"""
"""Interceptor for Cairo drawing method."""
cr = self._cairo
if not b:
x, y = cr.get_current_point()
@ -240,10 +213,8 @@ class CairoBoundingBoxContext:
class BoundingBoxPainter(Painter):
"""
This specific case of an ItemPainter is used to calculate the
bounding boxes (in canvas coordinates) for the items.
"""
"""This specific case of an ItemPainter is used to calculate the bounding
boxes (in canvas coordinates) for the items."""
draw_all = True
@ -271,9 +242,7 @@ class BoundingBoxPainter(Painter):
view.set_item_bounding_box(item, bounds)
def draw_items(self, items, cairo):
"""
Draw the items.
"""
"""Draw the items."""
for item in items:
self.draw_item(item, cairo)
@ -282,13 +251,11 @@ class BoundingBoxPainter(Painter):
class HandlePainter(Painter):
"""
Draw handles of items that are marked as selected in the view.
"""
"""Draw handles of items that are marked as selected in the view."""
def _draw_handles(self, item, cairo, opacity=None, inner=False):
"""
Draw handles for an item.
"""Draw handles for an item.
The handles are drawn in non-antialiased mode for clarity.
"""
view = self.view
@ -348,10 +315,8 @@ class HandlePainter(Painter):
class ToolPainter(Painter):
"""
ToolPainter allows the Tool defined on a view to do some special
drawing.
"""
"""ToolPainter allows the Tool defined on a view to do some special
drawing."""
def paint(self, context):
view = self.view
@ -364,10 +329,8 @@ class ToolPainter(Painter):
class FocusedItemPainter(Painter):
"""
This painter allows for drawing on top of all the other layers for the
focused item.
"""
"""This painter allows for drawing on top of all the other layers for the
focused item."""
def paint(self, context):
view = self.view
@ -377,9 +340,7 @@ class FocusedItemPainter(Painter):
def DefaultPainter(view=None):
"""
Default painter, containing item, handle and tool painters.
"""
"""Default painter, containing item, handle and tool painters."""
return (
PainterChain(view)
.append(ItemPainter())

View File

@ -1,6 +1,4 @@
"""
Some extra picklers needed to gracefully dump and load a canvas.
"""
"""Some extra picklers needed to gracefully dump and load a canvas."""
import copyreg

View File

@ -1,7 +1,6 @@
class Projection:
"""
Projections are used to convert values from one space to another,
e.g. from Canvas to Item space or visa versa.
"""Projections are used to convert values from one space to another, e.g.
from Canvas to Item space or visa versa.
In order to be a Projection the ``value`` and ``strength``
properties should be implemented and a method named ``variable()``
@ -41,9 +40,7 @@ class Projection:
strength = property(lambda s: s._var.strength)
def variable(self):
"""
Return the variable owned by the projection.
"""
"""Return the variable owned by the projection."""
return self._var
def __float__(self):
@ -56,8 +53,7 @@ class Projection:
class VariableProjection(Projection):
"""
Project a single `solver.Variable` to another space/coordinate system.
"""Project a single `solver.Variable` to another space/coordinate system.
The value has been set in the "other" coordinate system. A
callback is executed when the value changes.
@ -89,10 +85,8 @@ class VariableProjection(Projection):
class CanvasProjection:
"""
Project a point as Canvas coordinates. Although this is a
projection, it behaves like a tuple with two Variables
(Projections).
"""Project a point as Canvas coordinates. Although this is a projection,
it behaves like a tuple with two Variables (Projections).
>>> canvas = Canvas()
>>> from gaphas.item import Element
@ -138,9 +132,10 @@ class CanvasProjection:
item.canvas.request_update(item, matrix=False)
def _get_value(self):
"""
Return two delegating variables. Each variable should contain
a value attribute with the real value.
"""Return two delegating variables.
Each variable should contain a value attribute with the real
value.
"""
item = self._item
x, y = self._point.x, self._point.y

View File

@ -23,8 +23,7 @@ from gaphas.geometry import rectangle_clip, rectangle_contains, rectangle_inters
class Quadtree:
"""
The Quad-tree.
"""The Quad-tree.
Rectangles use the same scheme throughout Gaphas: (x, y, width, height).
@ -80,8 +79,7 @@ class Quadtree:
"""
def __init__(self, bounds=(0, 0, 0, 0), capacity=10):
"""
Create a new Quadtree instance.
"""Create a new Quadtree instance.
Bounds is the boundaries of the quadtree. this is fixed and do not
change depending on the contents.
@ -97,17 +95,16 @@ class Quadtree:
bounds = property(lambda s: s._bucket.bounds)
def resize(self, bounds):
"""
Resize the tree.
"""Resize the tree.
The tree structure is rebuild.
"""
self._bucket = QuadtreeBucket(bounds, self._capacity)
self.rebuild()
def get_soft_bounds(self):
"""
Calculate the size of all items in the tree. This size may be
beyond the limits of the tree itself.
"""Calculate the size of all items in the tree. This size may be beyond
the limits of the tree itself.
Returns a tuple (x, y, width, height).
@ -147,11 +144,11 @@ class Quadtree:
soft_bounds = property(get_soft_bounds)
def add(self, item, bounds, data=None):
"""
Add an item to the tree.
"""Add an item to the tree.
If an item already exists, its bounds are updated and the item
is moved to the right bucket.
Data can be used to add some extra info to the item
is moved to the right bucket. Data can be used to add some extra
info to the item
"""
# Clip item bounds to fit in top-level bucket
# Keep original bounds in _ids, for reference
@ -180,25 +177,19 @@ class Quadtree:
self._ids[item] = (bounds, data, clipped_bounds)
def remove(self, item):
"""
Remove an item from the tree.
"""
"""Remove an item from the tree."""
bounds, data, clipped_bounds = self._ids[item]
del self._ids[item]
if clipped_bounds:
self._bucket.find_bucket(clipped_bounds).remove(item)
def clear(self):
"""
Remove all items from the tree.
"""
"""Remove all items from the tree."""
self._bucket.clear()
self._ids.clear()
def rebuild(self):
"""
Rebuild the tree structure.
"""
"""Rebuild the tree structure."""
# Clean bucket and items:
self._bucket.clear()
@ -209,68 +200,54 @@ class Quadtree:
self._ids[item] = (bounds, data, clipped_bounds)
def get_bounds(self, item):
"""
Return the bounding box for the given item.
"""
"""Return the bounding box for the given item."""
return self._ids[item][0]
def get_data(self, item):
"""
Return the data for the given item, None if no data was provided.
"""
"""Return the data for the given item, None if no data was provided."""
return self._ids[item][1]
def get_clipped_bounds(self, item):
"""
Return the bounding box for the given item. The bounding box
is clipped on the boundaries of the tree (provided on
construction or with resize()).
"""Return the bounding box for the given item.
The bounding box is clipped on the boundaries of the tree
(provided on construction or with resize()).
"""
return self._ids[item][2]
def find_inside(self, rect):
"""
Find all items in the given rectangle (x, y, with, height).
"""Find all items in the given rectangle (x, y, with, height).
Returns a set.
"""
return set(self._bucket.find(rect, method=rectangle_contains))
def find_intersect(self, rect):
"""
Find all items that intersect with the given rectangle
(x, y, width, height).
"""Find all items that intersect with the given rectangle (x, y, width,
height).
Returns a set.
"""
return set(self._bucket.find(rect, method=rectangle_intersects))
def __len__(self):
"""
Return number of items in tree.
"""
"""Return number of items in tree."""
return len(self._ids)
def __contains__(self, item):
"""
Check if an item is in tree.
"""
"""Check if an item is in tree."""
return item in self._ids
def dump(self):
"""
Print structure to stdout.
"""
"""Print structure to stdout."""
self._bucket.dump()
class QuadtreeBucket:
"""
A node in a Quadtree structure.
"""
"""A node in a Quadtree structure."""
def __init__(self, bounds, capacity):
"""
Set bounding box for the node as (x, y, width, height).
"""
"""Set bounding box for the node as (x, y, width, height)."""
self.bounds = bounds
self.capacity = capacity
@ -278,10 +255,10 @@ class QuadtreeBucket:
self._buckets = []
def add(self, item, bounds):
"""
Add an item to the quadtree.
The bucket is split when necessary.
Items are otherwise added to this bucket, not some sub-bucket.
"""Add an item to the quadtree.
The bucket is split when necessary. Items are otherwise added to
this bucket, not some sub-bucket.
"""
assert rectangle_contains(bounds, self.bounds)
# create new subnodes if threshold is reached
@ -305,25 +282,25 @@ class QuadtreeBucket:
self.items[item] = bounds
def remove(self, item):
"""
Remove an item from the quadtree bucket.
The item should be contained by *this* bucket (not a sub-bucket).
"""Remove an item from the quadtree bucket.
The item should be contained by *this* bucket (not a sub-
bucket).
"""
del self.items[item]
def update(self, item, new_bounds):
"""
Update the position of an item within the current bucket.
The item should live in the current bucket, but may be placed in a
sub-bucket.
"""Update the position of an item within the current bucket.
The item should live in the current bucket, but may be placed in
a sub-bucket.
"""
assert item in self.items
self.remove(item)
self.find_bucket(new_bounds).add(item, new_bounds)
def find_bucket(self, bounds):
"""
Find the bucket that holds a bounding box.
"""Find the bucket that holds a bounding box.
This method should be used to find a bucket that fits, before
add() or remove() is called.
@ -346,9 +323,8 @@ class QuadtreeBucket:
return self._buckets[index].find_bucket(bounds)
def find(self, rect, method):
"""
Find all items in the given rectangle (x, y, with, height).
Method can be either the contains or intersects function.
"""Find all items in the given rectangle (x, y, with, height). Method
can be either the contains or intersects function.
Returns an iterator.
"""
@ -360,9 +336,7 @@ class QuadtreeBucket:
yield from bucket.find(rect, method=method)
def clear(self):
"""
Clear the bucket, including sub-buckets.
"""
"""Clear the bucket, including sub-buckets."""
del self._buckets[:]
self.items.clear()

View File

@ -1,6 +1,4 @@
"""
Allow for easily adding segments to lines.
"""
"""Allow for easily adding segments to lines."""
from cairo import ANTIALIAS_NONE, Matrix
from gaphas.aspect import (
@ -42,8 +40,7 @@ class LineSegment:
return handles and handles[0]
def split_segment(self, segment, count=2):
"""
Split one item segment into ``count`` equal pieces.
"""Split one item segment into ``count`` equal pieces.
Two lists are returned
@ -91,8 +88,7 @@ class LineSegment:
return handles, ports
def merge_segment(self, segment, count=2):
"""
Merge two (or more) item segments.
"""Merge two (or more) item segments.
Tuple of two lists is returned, list of deleted handles and
list of deleted ports.
@ -134,8 +130,7 @@ class LineSegment:
return deleted_handles, deleted_ports
def _recreate_constraints(self):
"""
Create connection constraints between connecting lines and an item.
"""Create connection constraints between connecting lines and an item.
:Parameters:
connected
@ -173,8 +168,8 @@ class LineSegment:
class SegmentHandleFinder(ItemHandleFinder):
"""Find a handle on a line.
Creates a new handle if the mouse is located between two handles. The
position aligns with the points drawn by the SegmentPainter.
Creates a new handle if the mouse is located between two handles.
The position aligns with the points drawn by the SegmentPainter.
"""
def get_handle_at_point(self, pos):
@ -196,10 +191,8 @@ class SegmentHandleFinder(ItemHandleFinder):
@HandleSelection.register(Line)
class SegmentHandleSelection(ItemHandleSelection):
"""
In addition to the default behaviour, merge segments if the handle
is released.
"""
"""In addition to the default behaviour, merge segments if the handle is
released."""
def unselect(self):
item = self.item
@ -232,10 +225,8 @@ class SegmentHandleSelection(ItemHandleSelection):
@PaintFocused.register(Line)
class LineSegmentPainter(ItemPaintFocused):
"""
This painter draws pseudo-handles on gaphas.item.Line
objects. Each line can be split by dragging those points, which
will result in a new handle.
"""This painter draws pseudo-handles on gaphas.item.Line objects. Each line
can be split by dragging those points, which will result in a new handle.
ConnectHandleTool take care of performing the user interaction
required for this feature.

View File

@ -1,8 +1,7 @@
"""
Constraint solver allows to define constraint between two or more
different variables and keep this constraint always true when one or
more of the constrained variables change. For example, one may want to
keep two variables always equal.
"""Constraint solver allows to define constraint between two or more different
variables and keep this constraint always true when one or more of the
constrained variables change. For example, one may want to keep two variables
always equal.
Variables change and at some point of time we want to make all
constraints valid again. This process is called solving constraints.
@ -57,7 +56,6 @@ class Variable:
You can even do some calculating with it. The Variable always represents a
float variable.
"""
def __init__(self, value=0.0, strength=NORMAL):
@ -80,9 +78,8 @@ class Variable:
strength = reversible_property(lambda s: s._strength, _set_strength)
def dirty(self):
"""
Mark the variable dirty in both the constraint solver and
attached constraints.
"""Mark the variable dirty in both the constraint solver and attached
constraints.
Variables are marked dirty also during constraints solving to
solve all dependent constraints, i.e. two equals constraints
@ -311,9 +308,9 @@ class Variable:
class Solver:
"""
Solve constraints. A constraint should have accompanying
variables.
"""Solve constraints.
A constraint should have accompanying variables.
"""
def __init__(self):
@ -325,9 +322,8 @@ class Solver:
constraints = property(lambda s: s._constraints)
def request_resolve(self, variable, projections_only=False):
"""
Mark a variable as "dirty". This means it it solved the next
time the constraints are resolved.
"""Mark a variable as "dirty". This means it it solved the next time
the constraints are resolved.
If projections_only is set to True, only constraints using the
variable through a Projection instance (e.i. variable itself
@ -375,10 +371,8 @@ class Solver:
@observed
def add_constraint(self, constraint):
"""
Add a constraint.
The actual constraint is returned, so the constraint can be
removed later on.
"""Add a constraint. The actual constraint is returned, so the
constraint can be removed later on.
Example:
@ -410,8 +404,7 @@ class Solver:
@observed
def remove_constraint(self, constraint):
"""
Remove a constraint from the solver
"""Remove a constraint from the solver.
>>> from gaphas.constraint import EquationConstraint
>>> s = Solver()
@ -441,15 +434,12 @@ class Solver:
reversible_pair(add_constraint, remove_constraint)
def request_resolve_constraint(self, c):
"""
Request resolving a constraint.
"""
"""Request resolving a constraint."""
self._marked_cons.append(c)
def constraints_with_variable(self, *variables):
"""
Return an iterator of constraints that work with variable.
The variable in question should be exposed by the constraints
"""Return an iterator of constraints that work with variable. The
variable in question should be exposed by the constraints
`constraint.Constraint.variables()` method.
>>> from gaphas.constraint import EquationConstraint
@ -567,8 +557,7 @@ class Solver:
class solvable:
"""
Easy-to-use drop Variable descriptor.
"""Easy-to-use drop Variable descriptor.
>>> class A(object):
... x = solvable(varname='_v_x')
@ -613,9 +602,10 @@ class solvable:
class JuggleError(AssertionError):
"""
Variable juggling exception. Raised when constraint's variables
are marking each other dirty forever.
"""Variable juggling exception.
Raised when constraint's variables are marking each other dirty
forever.
"""

View File

@ -1,6 +1,5 @@
"""
This module is the central point where Gaphas' classes report their
state changes.
"""This module is the central point where Gaphas' classes report their state
changes.
Invocations of method and state changing properties are emitted to all
functions (or bound methods) registered in the 'observers' set. Use
@ -15,7 +14,6 @@ For this to work the revert_handler has to be added to the observers
set::
gaphas.state.observers.add(gaphas.state.revert_handler)
"""
from functools import update_wrapper
from inspect import getfullargspec as _getargspec
@ -46,8 +44,7 @@ mutex = Lock()
def observed(func):
"""
Simple observer, dispatches events to functions registered in the
"""Simple observer, dispatches events to functions registered in the
observers list.
On the function an ``__observer__`` property is set, which
@ -77,9 +74,8 @@ def observed(func):
def dispatch(event, queue):
"""
Dispatch an event to a queue of event handlers.
Event handlers should have signature: handler(event).
"""Dispatch an event to a queue of event handlers. Event handlers should
have signature: handler(event).
>>> def handler(event):
... print("event handled", event)
@ -106,10 +102,8 @@ _reverse = {}
def reversible_function(func, reverse, bind={}):
"""
Straight forward reversible method, if func is invoked, reverse
is dispatched with bind as arguments.
"""
"""Straight forward reversible method, if func is invoked, reverse is
dispatched with bind as arguments."""
global _reverse
func = getfunction(func)
_reverse[func] = (reverse, getargnames(reverse), bind)
@ -119,10 +113,9 @@ reversible_method = reversible_function
def reversible_pair(func1, func2, bind1={}, bind2={}):
"""
Treat a pair of functions (func1 and func2) as each others inverse
operation. bind1 provides arguments that can overrule the default
values (or add additional values). bind2 does the same for func2.
"""Treat a pair of functions (func1 and func2) as each others inverse
operation. bind1 provides arguments that can overrule the default values
(or add additional values). bind2 does the same for func2.
See `revert_handler()` for doctesting.
"""
@ -135,10 +128,9 @@ def reversible_pair(func1, func2, bind1={}, bind2={}):
def reversible_property(fget=None, fset=None, fdel=None, doc=None, bind={}):
"""
Replacement for the property descriptor. In addition to creating a
property instance, the property is registered as reversible and
reverse events can be send out when changes occur.
"""Replacement for the property descriptor. In addition to creating a
property instance, the property is registered as reversible and reverse
events can be send out when changes occur.
Caveat: we can't handle both fset and fdel in the proper
way. Therefore fdel should somehow invoke fset. (personally, I
@ -165,8 +157,7 @@ def reversible_property(fget=None, fset=None, fdel=None, doc=None, bind={}):
def revert_handler(event):
"""
Event handler, generates undoable statements and puts them on the
"""Event handler, generates undoable statements and puts them on the
subscribers queue.
First thing to do is to actually enable the revert_handler:
@ -245,8 +236,8 @@ def revert_handler(event):
def saveapply(func, kw):
"""
Do apply a set of keywords to a method or function.
"""Do apply a set of keywords to a method or function.
The function names should be known at meta-level, since arguments
are applied as func(\\*\\*kwargs).
"""
@ -270,7 +261,5 @@ def getargnames(func):
def getfunction(func):
"""
Return the function associated with a class method.
"""
"""Return the function associated with a class method."""
return func

View File

@ -1,18 +1,16 @@
"""
Table is a storage class that can be used to store information, like
one would in a database table, with indexes on the desired "columns."
"""
"""Table is a storage class that can be used to store information, like one
would in a database table, with indexes on the desired "columns."."""
from functools import reduce
class Table:
"""
A Table structure with indexing. Optimized for lookups.
"""A Table structure with indexing.
Optimized for lookups.
"""
def __init__(self, columns, indexes):
"""
Create a new Store instance with columns and indexes:
"""Create a new Store instance with columns and indexes:
>>> from collections import namedtuple
>>> C = namedtuple('C', "foo bar baz")
@ -30,8 +28,7 @@ class Table:
columns = property(lambda s: s._type)
def insert(self, *values):
"""
Add a set of values to the store.
"""Add a set of values to the store.
>>> from collections import namedtuple
>>> C = namedtuple('C', "foo bar baz")
@ -62,9 +59,8 @@ class Table:
index[n][v] = {data}
def delete(self, *_row, **kv):
"""
Remove value from the table. Either a complete set may be
given or just one entry in "column=value" style.
"""Remove value from the table. Either a complete set may be given or
just one entry in "column=value" style.
>>> from collections import namedtuple
>>> C = namedtuple('C', "foo bar baz")
@ -121,8 +117,7 @@ class Table:
del index[n][v]
def query(self, **kv):
"""
Get rows (tuples) for each key defined. An iterator is returned.
"""Get rows (tuples) for each key defined. An iterator is returned.
>>> from collections import namedtuple
>>> C = namedtuple('C', "foo bar baz")

View File

@ -1,6 +1,5 @@
"""
Tools provide interactive behavior to a `View` by handling specific
events sent by view.
"""Tools provide interactive behavior to a `View` by handling specific events
sent by view.
Some of implemented tools are
@ -49,9 +48,7 @@ Event = Context
class Tool:
"""
Base class for a tool. This class
A word on click events:
"""Base class for a tool. This class A word on click events:
Mouse (pointer) button click. A button press is normally followed
by a button release. Double and triple clicks should work together
@ -101,9 +98,10 @@ class Tool:
self.view = view
def _dispatch(self, event):
"""
Deal with the event. The event is dispatched to a specific
handler for the event type.
"""Deal with the event.
The event is dispatched to a specific handler for the event
type.
"""
handler = self.EVENT_HANDLERS.get(event.type)
if handler:
@ -119,11 +117,10 @@ class Tool:
return self._dispatch(event)
def draw(self, context):
"""
Some tools (such as Rubberband selection) may need to draw
something on the canvas. This can be done through the draw()
method. This is called after all items are drawn.
The context contains the following fields:
"""Some tools (such as Rubberband selection) may need to draw something
on the canvas. This can be done through the draw() method. This is
called after all items are drawn. The context contains the following
fields:
- context: the render context (contains context.view and context.cairo)
- cairo: the Cairo drawing context
@ -132,13 +129,12 @@ class Tool:
class ToolChain(Tool):
"""
A ToolChain can be used to chain tools together, for example
HoverTool, HandleTool, SelectionTool.
"""A ToolChain can be used to chain tools together, for example HoverTool,
HandleTool, SelectionTool.
The grabbed item is bypassed in case a double or triple click
event is received. Should make sure this doesn't end up in
dangling states.
The grabbed item is bypassed in case a double or triple click event
is received. Should make sure this doesn't end up in dangling
states.
"""
def __init__(self, view=None):
@ -152,8 +148,9 @@ class ToolChain(Tool):
tool.set_view(self.view)
def append(self, tool):
"""
Append a tool to the chain. Self is returned.
"""Append a tool to the chain.
Self is returned.
"""
self._tools.append(tool)
tool.view = self.view
@ -178,20 +175,19 @@ class ToolChain(Tool):
self._grabbed_tool = None
def validate_grabbed_tool(self, event):
"""
Check if it's valid to have a grabbed tool on an event. If not
the grabbed tool will be released.
"""Check if it's valid to have a grabbed tool on an event.
If not the grabbed tool will be released.
"""
if event.type in self.FORCE_UNGRAB_EVENTS:
self.ungrab(self._grabbed_tool)
def handle(self, event):
"""
Handle the event by calling each tool until the event is
handled or grabbed.
"""Handle the event by calling each tool until the event is handled or
grabbed.
If a tool is returning True on a button press event, the
motion and button release events are also passed to this
If a tool is returning True on a button press event, the motion
and button release events are also passed to this
"""
handler = self.EVENT_HANDLERS.get(event.type)
@ -220,9 +216,7 @@ class ToolChain(Tool):
class HoverTool(Tool):
"""
Make the item under the mouse cursor the "hovered item".
"""
"""Make the item under the mouse cursor the "hovered item"."""
def on_motion_notify(self, event):
view = self.view
@ -231,11 +225,10 @@ class HoverTool(Tool):
class ItemTool(Tool):
"""
ItemTool does selection and dragging of items. On a button click,
the currently "hovered item" is selected. If CTRL or SHIFT are
pressed, already selected items remain selected. The last selected
item gets the focus (e.g. receives key press events).
"""ItemTool does selection and dragging of items. On a button click, the
currently "hovered item" is selected. If CTRL or SHIFT are pressed, already
selected items remain selected. The last selected item gets the focus (e.g.
receives key press events).
The roles used are Selection (select, unselect) and InMotion (move).
"""
@ -249,8 +242,7 @@ class ItemTool(Tool):
return self.view.hovered_item
def movable_items(self):
"""
Filter the items that should eventually be moved.
"""Filter the items that should eventually be moved.
Returns InMotion aspects for the items.
"""
@ -301,8 +293,8 @@ class ItemTool(Tool):
return True
def on_motion_notify(self, event):
"""
Normally do nothing.
"""Normally do nothing.
If a button is pressed move the items around.
"""
if event.get_state()[1] & Gdk.EventMask.BUTTON_PRESS_MASK:
@ -319,11 +311,10 @@ class ItemTool(Tool):
class HandleTool(Tool):
"""
Tool for moving handles around.
"""Tool for moving handles around.
By default this tool does not provide connecting handles to
another item (see `ConnectHandleTool`).
By default this tool does not provide connecting handles to another
item (see `ConnectHandleTool`).
"""
def __init__(self, view=None):
@ -333,9 +324,10 @@ class HandleTool(Tool):
self.motion_handle = None
def grab_handle(self, item, handle):
"""
Grab a specific handle. This can be used from the
PlacementTool to set the state of the handle tool.
"""Grab a specific handle.
This can be used from the PlacementTool to set the state of the
handle tool.
"""
assert item is None and handle is None or handle in item.handles()
self.grabbed_item = item
@ -345,9 +337,7 @@ class HandleTool(Tool):
selection.select()
def ungrab_handle(self):
"""
Reset grabbed_handle and grabbed_item.
"""
"""Reset grabbed_handle and grabbed_item."""
item = self.grabbed_item
handle = self.grabbed_handle
self.grabbed_handle = None
@ -357,10 +347,10 @@ class HandleTool(Tool):
selection.unselect()
def on_button_press(self, event):
"""
Handle button press events. If the (mouse) button is pressed
on top of a Handle (item.Handle), that handle is grabbed and
can be dragged around.
"""Handle button press events.
If the (mouse) button is pressed on top of a Handle
(item.Handle), that handle is grabbed and can be dragged around.
"""
view = self.view
@ -388,9 +378,7 @@ class HandleTool(Tool):
return True
def on_button_release(self, event):
"""
Release a grabbed handle.
"""
"""Release a grabbed handle."""
# queue extra redraw to make sure the item is drawn properly
grabbed_handle, grabbed_item = self.grabbed_handle, self.grabbed_item
@ -405,10 +393,10 @@ class HandleTool(Tool):
return True
def on_motion_notify(self, event):
"""
Handle motion events. If a handle is grabbed: drag it around,
else, if the pointer is over a handle, make the owning item
the hovered-item.
"""Handle motion events.
If a handle is grabbed: drag it around, else, if the pointer is
over a handle, make the owning item the hovered-item.
"""
if (
self.grabbed_handle
@ -477,10 +465,10 @@ PAN_VALUE = 0
class PanTool(Tool):
"""
Captures drag events with the middle mouse button and uses them to
translate the canvas within the view. Trumps the ZoomTool, so
should be placed later in the ToolChain.
"""Captures drag events with the middle mouse button and uses them to
translate the canvas within the view.
Trumps the ZoomTool, so should be placed later in the ToolChain.
"""
def __init__(self, view=None):
@ -544,7 +532,6 @@ class ZoomTool(Tool):
Uses two different user inputs to zoom:
- Ctrl + middle-mouse dragging in the up-down direction.
- Ctrl + mouse-wheel
"""
def __init__(self, view=None):
@ -672,18 +659,14 @@ class PlacementTool(Tool):
class TextEditTool(Tool):
"""
Demo of a text edit tool (just displays a text edit box at the
cursor position.
"""
"""Demo of a text edit tool (just displays a text edit box at the cursor
position."""
def __init__(self, view=None):
super().__init__(view)
def on_double_click(self, event):
"""
Create a popup window with some editable text.
"""
"""Create a popup window with some editable text."""
window = Gtk.Window()
window.set_property("decorated", False)
window.set_resize_mode(Gtk.ResizeMode.IMMEDIATE)
@ -721,8 +704,7 @@ class TextEditTool(Tool):
class ConnectHandleTool(HandleTool):
"""
Tool for connecting two items.
"""Tool for connecting two items.
There are two items involved. Handle of connecting item (usually a
line) is being dragged by an user towards another item (item in
@ -732,18 +714,15 @@ class ConnectHandleTool(HandleTool):
"""
def glue(self, item, handle, vpos):
"""
Perform a small glue action to ensure the handle is at a
proper location for connecting.
"""
"""Perform a small glue action to ensure the handle is at a proper
location for connecting."""
if self.motion_handle:
return self.motion_handle.glue(vpos)
else:
return HandleInMotion(item, handle, self.view).glue(vpos)
def connect(self, item, handle, vpos):
"""
Connect a handle of a item to connectable item.
"""Connect a handle of a item to connectable item.
Connectable item is found by `ConnectHandleTool.glue` method.
@ -780,9 +759,7 @@ class ConnectHandleTool(HandleTool):
def DefaultTool(view=None):
"""
The default tool chain build from HoverTool, ItemTool and HandleTool.
"""
"""The default tool chain build from HoverTool, ItemTool and HandleTool."""
return (
ToolChain(view)
.append(HoverTool())

View File

@ -1,11 +1,8 @@
"""
Simple class containing the tree structure for the canvas items.
"""
"""Simple class containing the tree structure for the canvas items."""
class Tree:
"""
A Tree structure. Nodes are stores in a depth-first order.
"""A Tree structure. Nodes are stores in a depth-first order.
``None`` is the root node.
@ -26,8 +23,7 @@ class Tree:
nodes = property(lambda s: list(s._nodes))
def get_parent(self, node):
"""
Return the parent item of ``node``.
"""Return the parent item of ``node``.
>>> tree = Tree()
>>> tree.add('n1')
@ -38,8 +34,7 @@ class Tree:
return self._parents.get(node)
def get_children(self, node):
"""
Return all child objects of ``node``.
"""Return all child objects of ``node``.
>>> tree = Tree()
>>> tree.add('n1')
@ -53,8 +48,7 @@ class Tree:
return self._children[node]
def get_siblings(self, node):
"""
Get all siblings of ``node``, including ``node``.
"""Get all siblings of ``node``, including ``node``.
>>> tree = Tree()
>>> tree.add('n1')
@ -67,8 +61,7 @@ class Tree:
return self._children[parent]
def get_next_sibling(self, node):
"""
Return the node on the same level after ``node``.
"""Return the node on the same level after ``node``.
>>> tree = Tree()
>>> tree.add('n1')
@ -86,8 +79,7 @@ class Tree:
return siblings[siblings.index(node) + 1]
def get_previous_sibling(self, node):
"""
Return the node on the same level before ``node``.
"""Return the node on the same level before ``node``.
>>> tree = Tree()
>>> tree.add('n1')
@ -108,8 +100,7 @@ class Tree:
return siblings[index]
def get_all_children(self, node):
"""
Iterate all children (and children of children and so forth)
"""Iterate all children (and children of children and so forth)
>>> tree = Tree()
>>> tree.add('n1')
@ -128,8 +119,7 @@ class Tree:
yield from self.get_all_children(c)
def get_ancestors(self, node):
"""
Iterate all parents and parents of parents, etc.
"""Iterate all parents and parents of parents, etc.
>>> tree = Tree()
>>> tree.add('n1')
@ -150,9 +140,8 @@ class Tree:
parent = self.get_parent(parent)
def index_nodes(self, index_key):
"""
Provide each item in the tree with an index attribute. This
makes for fast sorting of items.
"""Provide each item in the tree with an index attribute. This makes
for fast sorting of items.
>>> class A(object):
... def __init__(self, n):
@ -175,10 +164,8 @@ class Tree:
list(map(setattr, nodes, [index_key] * lnodes, list(range(lnodes))))
def _add_to_nodes(self, node, parent, index=None):
"""
Helper method to place nodes on the right location in the
nodes list Called only from add() and reparent()
"""
"""Helper method to place nodes on the right location in the nodes list
Called only from add() and reparent()"""
nodes = self._nodes
siblings = self._children[parent]
try:
@ -202,9 +189,7 @@ class Tree:
nodes.insert(nodes.index(atnode), node)
def _add(self, node, parent=None, index=None):
"""
Helper method for both add() and reparent().
"""
"""Helper method for both add() and reparent()."""
assert node not in self._nodes
siblings = self._children[parent]
@ -222,9 +207,8 @@ class Tree:
self._parents[node] = parent
def add(self, node, parent=None, index=None):
"""
Add node to the tree. parent is the parent node, which may be
None if the item should be added to the root item.
"""Add node to the tree. parent is the parent node, which may be None
if the item should be added to the root item.
For usage, see the unit tests.
"""
@ -243,8 +227,7 @@ class Tree:
pass
def remove(self, node):
"""
Remove ``node`` from the tree.
"""Remove ``node`` from the tree.
For usage, see the unit tests.
"""
@ -254,11 +237,11 @@ class Tree:
self._remove(node)
def _reparent_nodes(self, node, parent):
"""
Helper for reparent().
"""Helper for reparent().
The _children and _parent trees can be left intact as far as
children of the reparented node are concerned. Only the
position in the _nodes list changes.
children of the reparented node are concerned. Only the position
in the _nodes list changes.
"""
self._nodes.remove(node)
self._add_to_nodes(node, parent)
@ -266,8 +249,7 @@ class Tree:
self._reparent_nodes(c, node)
def reparent(self, node, parent, index=None):
"""
Set new parent for a ``node``. ``Parent`` can be ``None``,
"""Set new parent for a ``node``. ``Parent`` can be ``None``,
indicating it's added to the top.
>>> tree = Tree()

View File

@ -1,15 +1,12 @@
"""Helper functions and classes for Cairo (drawing engine used by the canvas).
"""
"""Helper functions and classes for Cairo (drawing engine used by the
canvas)."""
from math import pi
import cairo
def text_extents(cr, text, font=None, multiline=False, padding=1):
"""
Simple way to determine the size of a piece of text.
"""
"""Simple way to determine the size of a piece of text."""
if not text:
return 0, 0
if font:
@ -36,8 +33,8 @@ def text_center(cr, x, y, text):
def text_align(cr, x, y, text, align_x=0, align_y=0, padding_x=0, padding_y=0):
"""
Draw text relative to (x, y).
"""Draw text relative to (x, y).
x, y - coordinates
text - text to print (utf8)
align_x - -1 (top), 0 (middle), 1 (bottom)
@ -66,8 +63,8 @@ def text_align(cr, x, y, text, align_x=0, align_y=0, padding_x=0, padding_y=0):
def text_multiline(cr, x, y, text, padding=1):
"""
Draw a string of text with embedded newlines.
"""Draw a string of text with embedded newlines.
cr - cairo context
x - leftmost x
y - topmost y
@ -85,9 +82,7 @@ def text_multiline(cr, x, y, text, padding=1):
def text_underline(cr, x, y, text, offset=1.5):
"""
Draw text with underline.
"""
"""Draw text with underline."""
x_bear, y_bear, w, h, x_adv, y_adv = cr.text_extents(text)
cr.move_to(x, y - y_bear)
cr.show_text(text)
@ -98,10 +93,11 @@ def text_underline(cr, x, y, text, offset=1.5):
def text_set_font(cr, font):
"""
Set the font from a string. E.g. 'sans 10' or 'sans italic bold
12' only restriction is that the font name should be the first
option and the font size as last argument
"""Set the font from a string.
E.g. 'sans 10' or 'sans italic bold 12' only restriction is that the
font name should be the first option and the font size as last
argument
"""
font = font.split()
cr.select_font_face(
@ -113,8 +109,8 @@ def text_set_font(cr, font):
def path_ellipse(cr, x, y, width, height, angle=0):
"""
Draw an ellipse.
"""Draw an ellipse.
x - center x
y - center y
width - width of ellipse (in x direction when angle=0)

View File

@ -1,6 +1,4 @@
"""This module contains everything to display a Canvas on a screen.
"""
"""This module contains everything to display a Canvas on a screen."""
import cairo
from gi.repository import Gdk, GLib, GObject, Gtk
@ -20,9 +18,7 @@ DEFAULT_CURSOR = Gdk.CursorType.LEFT_PTR
class View:
"""
View class for gaphas.Canvas objects.
"""
"""View class for gaphas.Canvas objects."""
def __init__(self, canvas=None):
self._matrix = cairo.Matrix()
@ -62,20 +58,17 @@ class View:
canvas = property(lambda s: s._canvas, _set_canvas)
def emit(self, *args, **kwargs):
"""
Placeholder method for signal emission functionality.
"""
"""Placeholder method for signal emission functionality."""
pass
def queue_draw_item(self, *items):
"""
Placeholder for item redraw queueing.
"""
"""Placeholder for item redraw queueing."""
pass
def select_item(self, item):
"""
Select an item. This adds @item to the set of selected items.
"""Select an item.
This adds @item to the set of selected items.
"""
self.queue_draw_item(item)
if item not in self._selected_items:
@ -83,9 +76,7 @@ class View:
self.emit("selection-changed", self._selected_items)
def unselect_item(self, item):
"""
Unselect an item.
"""
"""Unselect an item."""
self.queue_draw_item(item)
if item in self._selected_items:
self._selected_items.discard(item)
@ -96,9 +87,7 @@ class View:
self.select_item(item)
def unselect_all(self):
"""
Clearing the selected_item also clears the focused_item.
"""
"""Clearing the selected_item also clears the focused_item."""
self.queue_draw_item(*self._selected_items)
self._selected_items.clear()
self.focused_item = None
@ -112,10 +101,8 @@ class View:
)
def _set_focused_item(self, item):
"""
Set the focused item, this item is also added to the
selected_items set.
"""
"""Set the focused item, this item is also added to the selected_items
set."""
if item is not self._focused_item:
self.queue_draw_item(self._focused_item, item)
@ -126,9 +113,7 @@ class View:
self.emit("focus-changed", item)
def _del_focused_item(self):
"""
Items that loose focus remain selected.
"""
"""Items that loose focus remain selected."""
self._set_focused_item(None)
focused_item = property(
@ -139,18 +124,14 @@ class View:
)
def _set_hovered_item(self, item):
"""
Set the hovered item.
"""
"""Set the hovered item."""
if item is not self._hovered_item:
self.queue_draw_item(self._hovered_item, item)
self._hovered_item = item
self.emit("hover-changed", item)
def _del_hovered_item(self):
"""
Unset the hovered item.
"""
"""Unset the hovered item."""
self._set_hovered_item(None)
hovered_item = property(
@ -161,18 +142,14 @@ class View:
)
def _set_dropzone_item(self, item):
"""
Set dropzone item.
"""
"""Set dropzone item."""
if item is not self._dropzone_item:
self.queue_draw_item(self._dropzone_item, item)
self._dropzone_item = item
self.emit("dropzone-changed", item)
def _del_dropzone_item(self):
"""
Unset dropzone item.
"""
"""Unset dropzone item."""
self._set_dropzone_item(None)
dropzone_item = property(
@ -183,8 +160,9 @@ class View:
)
def _set_painter(self, painter):
"""
Set the painter to use. Painters should implement painter.Painter.
"""Set the painter to use.
Painters should implement painter.Painter.
"""
self._painter = painter
painter.set_view(self)
@ -193,9 +171,7 @@ class View:
painter = property(lambda s: s._painter, _set_painter)
def _set_bounding_box_painter(self, painter):
"""
Set the painter to use for bounding box calculations.
"""
"""Set the painter to use for bounding box calculations."""
self._bounding_box_painter = painter
painter.set_view(self)
self.emit("painter-changed")
@ -205,8 +181,7 @@ class View:
)
def get_item_at_point(self, pos, selected=True):
"""
Return the topmost item located at ``pos`` (x, y).
"""Return the topmost item located at ``pos`` (x, y).
Parameters:
- selected: if False returns first non-selected item
@ -227,13 +202,10 @@ class View:
return None
def get_handle_at_point(self, pos, distance=6):
"""
Look for a handle at ``pos`` and return the
tuple (item, handle).
"""
"""Look for a handle at ``pos`` and return the tuple (item, handle)."""
def find(item):
""" Find item's handle at pos """
"""Find item's handle at pos."""
v2i = self.get_matrix_v2i(item)
d = distance_point_point_fast(v2i.transform_distance(0, distance))
x, y = v2i.transform_point(*pos)
@ -270,8 +242,7 @@ class View:
return None, None
def get_port_at_point(self, vpos, distance=10, exclude=None):
"""
Find item with port closest to specified position.
"""Find item with port closest to specified position.
List of items to be ignored can be specified with `exclude`
parameter.
@ -325,8 +296,8 @@ class View:
return item, port, glue_pos
def get_items_in_rectangle(self, rect, intersect=True, reverse=False):
"""
Return the items in the rectangle 'rect'.
"""Return the items in the rectangle 'rect'.
Items are automatically sorted in canvas' processing order.
"""
if intersect:
@ -336,17 +307,15 @@ class View:
return self._canvas.sort(items, reverse=reverse)
def select_in_rectangle(self, rect):
"""
Select all items who have their bounding box within the
rectangle @rect.
"""Select all items who have their bounding box within the rectangle.
@rect.
"""
items = self._qtree.find_inside(rect)
list(map(self.select_item, items))
def zoom(self, factor):
"""
Zoom in/out by factor @factor.
"""
"""Zoom in/out by factor @factor."""
# TODO: should the scale factor be clipped?
self._matrix.scale(factor, factor)
@ -355,8 +324,7 @@ class View:
self.request_update((), self._canvas.get_all_items())
def set_item_bounding_box(self, item, bounds):
"""
Update the bounding box of the item.
"""Update the bounding box of the item.
``bounds`` is in view coordinates.
@ -369,18 +337,14 @@ class View:
self._qtree.add(item=item, bounds=bounds, data=(ix0, iy0, ix1, iy1))
def get_item_bounding_box(self, item):
"""
Get the bounding box for the item, in view coordinates.
"""
"""Get the bounding box for the item, in view coordinates."""
return self._qtree.get_bounds(item)
bounding_box = property(lambda s: s._bounds)
def update_bounding_box(self, cr, items=None):
"""
Update the bounding boxes of the canvas items for this view,
in canvas coordinates.
"""
"""Update the bounding boxes of the canvas items for this view, in
canvas coordinates."""
painter = self._bounding_box_painter
if items is None:
items = self.canvas.get_all_items()
@ -392,25 +356,19 @@ class View:
self._bounds = Rectangle(*self._qtree.soft_bounds)
def get_matrix_i2v(self, item):
"""
Get Item to View matrix for ``item``.
"""
"""Get Item to View matrix for ``item``."""
if self not in item._matrix_i2v:
self.update_matrix(item)
return item._matrix_i2v[self]
def get_matrix_v2i(self, item):
"""
Get View to Item matrix for ``item``.
"""
"""Get View to Item matrix for ``item``."""
if self not in item._matrix_v2i:
self.update_matrix(item)
return item._matrix_v2i[self]
def update_matrix(self, item):
"""
Update item matrices related to view.
"""
"""Update item matrices related to view."""
matrix_i2c = self.canvas.get_matrix_i2c(item)
try:
i2v = matrix_i2c.multiply(self._matrix)
@ -425,9 +383,7 @@ class View:
item._matrix_v2i[self] = v2i
def _clear_matrices(self):
"""
Clear registered data in Item's _matrix{i2c|v2i} attributes.
"""
"""Clear registered data in Item's _matrix{i2c|v2i} attributes."""
for item in self.canvas.get_all_items():
try:
del item._matrix_i2v[self]
@ -438,10 +394,9 @@ class View:
class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
# NOTE: Inherit from GTK+ class first, otherwise BusErrors may occur!
"""
GTK+ widget for rendering a canvas.Canvas to a screen. The view
uses Tools from `tool.py` to handle events and Painters from
`painter.py` to draw. Both are configurable.
"""GTK+ widget for rendering a canvas.Canvas to a screen. The view uses
Tools from `tool.py` to handle events and Painters from `painter.py` to
draw. Both are configurable.
The widget already contains adjustment objects (`hadjustment`,
`vadjustment`) to be used for scrollbars.
@ -565,9 +520,7 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
raise AttributeError(f"Unknown property {prop.name}")
def emit(self, *args, **kwargs):
"""
Delegate signal emissions to the DrawingArea (=GTK+)
"""
"""Delegate signal emissions to the DrawingArea (=GTK+)"""
Gtk.DrawingArea.emit(self, *args, **kwargs)
def _set_canvas(self, canvas):
@ -591,8 +544,9 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
canvas = property(lambda s: s._canvas, _set_canvas)
def _set_tool(self, tool):
"""
Set the tool to use. Tools should implement tool.Tool.
"""Set the tool to use.
Tools should implement tool.Tool.
"""
self._tool = tool
tool.set_view(self)
@ -605,9 +559,7 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
vadjustment = property(lambda s: s._vadjustment)
def zoom(self, factor):
"""
Zoom in/out by factor ``factor``.
"""
"""Zoom in/out by factor ``factor``."""
super().zoom(factor)
self.queue_draw_refresh()
@ -664,30 +616,28 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
self._vadjustment.set_page_size(ah)
def queue_draw_item(self, *items):
"""
Like ``DrawingArea.queue_draw_area``, but use the bounds of
the item as update areas. Of course with a pythonic flavor:
update any number of items at once.
"""Like ``DrawingArea.queue_draw_area``, but use the bounds of the item
as update areas.
Of course with a pythonic flavor: update any number of items at
once.
"""
self.update_back_buffer()
def queue_draw_area(self, x, y, w, h):
"""
Queue an update for portion of the view port.
"""
"""Queue an update for portion of the view port."""
self.update_back_buffer()
def queue_draw_refresh(self):
"""
Redraw the entire view.
"""
"""Redraw the entire view."""
self.update_back_buffer()
def request_update(self, items, matrix_only_items=(), removed_items=()):
"""
Request update for items. Items will get a full update
treatment, while ``matrix_only_items`` will only have their
bounding box recalculated.
"""Request update for items.
Items will get a full update treatment, while
``matrix_only_items`` will only have their bounding box
recalculated.
"""
if items:
self._dirty_items.update(items)
@ -713,9 +663,7 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
@AsyncIO(single=True)
def update(self):
"""
Update view status according to the items updated by the canvas.
"""
"""Update view status according to the items updated by the canvas."""
if not self.get_window():
return
@ -854,9 +802,7 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
return False
def do_draw(self, cr):
"""
Render canvas to the screen.
"""
"""Render canvas to the screen."""
if not self._canvas:
return
@ -869,18 +815,17 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable, View):
return False
def do_event(self, event):
"""
Handle GDK events. Events are delegated to a `tool.Tool`.
"""Handle GDK events.
Events are delegated to a `tool.Tool`.
"""
if self._tool:
return self._tool.handle(event) and True or False
return False
def on_adjustment_changed(self, adj):
"""
Change the transformation matrix of the view to reflect the
value of the x/y adjustment (scrollbar).
"""
"""Change the transformation matrix of the view to reflect the value of
the x/y adjustment (scrollbar)."""
value = adj.get_value()
if value == 0.0:
return

View File

@ -17,8 +17,8 @@ from gi.repository import Gtk # noqa: isort:skip
class SimpleCanvas:
"""Creates a test canvas object.
Adds a view, canvas, and handle connection tool to a test
case. Two boxes and a line are added to the canvas as well.
Adds a view, canvas, and handle connection tool to a test case. Two
boxes and a line are added to the canvas as well.
"""
def __init__(self):
@ -59,9 +59,7 @@ class SimpleCanvas:
@pytest.fixture()
def simple_canvas():
"""Creates a `SimpleCanvas`.
"""
"""Creates a `SimpleCanvas`."""
return SimpleCanvas()

View File

@ -1,6 +1,4 @@
"""Test aspects for items.
"""
"""Test aspects for items."""
import pytest
@ -23,9 +21,7 @@ def cvi():
def test_selection_select(cvi):
"""Test the Selection role methods.
"""
"""Test the Selection role methods."""
cvi.canvas.add(cvi.item)
selection = Selection(cvi.item, cvi.view)
assert cvi.item not in cvi.view.selected_items
@ -38,9 +34,7 @@ def test_selection_select(cvi):
def test_selection_move(cvi):
"""Test the Selection role methods.
"""
"""Test the Selection role methods."""
cvi.canvas.add(cvi.item)
in_motion = InMotion(cvi.item, cvi.view)
assert (1, 0, 0, 1, 0, 0) == tuple(cvi.item.matrix)

View File

@ -7,7 +7,7 @@ from gaphas.item import Line
def test_update_matrices():
"""Test updating of matrices"""
"""Test updating of matrices."""
c = Canvas()
i = Box()
ii = Box()
@ -150,9 +150,7 @@ def test_disconnect_item_with_constraint_by_deleting_element():
def test_line_projection():
"""Test projection with line's handle on element's side.
"""
"""Test projection with line's handle on element's side."""
line = Line()
line.matrix.translate(15, 50)
h1, h2 = line.handles()
@ -173,9 +171,7 @@ def test_line_projection():
def test_remove_connected_item():
"""Test adding canvas constraint.
"""
"""Test adding canvas constraint."""
canvas = Canvas()
from gaphas.aspect import ConnectionSink, Connector

View File

@ -3,7 +3,7 @@ from gaphas.solver import Variable
def test_pos_constraint():
"""Test position constraint"""
"""Test position constraint."""
x1, y1 = Variable(10), Variable(11)
x2, y2 = Variable(12), Variable(13)
pc = PositionConstraint(origin=(x1, y1), point=(x2, y2))
@ -29,8 +29,7 @@ def test_pos_constraint():
def test_delta():
"""Test line align constraint delta.
"""
"""Test line align constraint delta."""
line = (Variable(0), Variable(0)), (Variable(30), Variable(20))
point = (Variable(15), Variable(10))
lc = LineAlignConstraint(line=line, point=point, align=0.5, delta=5)
@ -46,8 +45,7 @@ def test_delta():
def test_delta_below_zero():
"""Test line align constraint with delta below zero.
"""
"""Test line align constraint with delta below zero."""
line = (Variable(0), Variable(0)), (Variable(30), Variable(20))
point = (Variable(15), Variable(10))
lc = LineAlignConstraint(line=line, point=point, align=0.5, delta=-5)

View File

@ -20,9 +20,7 @@ def cb():
def test_creation_with_size(cb):
"""Test if initial size holds when added to a canvas.
"""
"""Test if initial size holds when added to a canvas."""
cb.box.width = 150
cb.box.height = 153
@ -40,9 +38,7 @@ def test_creation_with_size(cb):
def test_resize_se(cb):
"""Test resizing of element by dragging its SE handle.
"""
"""Test resizing of element by dragging its SE handle."""
cb.canvas.add(cb.box)
h_nw, h_ne, h_se, h_sw = cb.handles
@ -67,9 +63,7 @@ def test_resize_se(cb):
def test_minimal_se(cb):
"""Test resizing of element by dragging its SE handle.
"""
"""Test resizing of element by dragging its SE handle."""
cb.canvas.add(cb.box)
h_nw, h_ne, h_se, h_sw = cb.handles

View File

@ -29,9 +29,7 @@ def win():
def test_find_closest(win):
"""Test find closest method.
"""
"""Test find closest method."""
set1 = [0, 10, 20]
set2 = [2, 15, 30]

View File

@ -1,8 +1,7 @@
"""Item constraint creation tests.
The test check functionality of `Item.constraint` method, not constraints
themselves.
The test check functionality of `Item.constraint` method, not
constraints themselves.
"""
import pytest
@ -24,9 +23,7 @@ def item_pos():
def test_line_constraint(item_pos):
"""Test line creation constraint.
"""
"""Test line creation constraint."""
line = (Variable(3), Variable(4)), (Variable(5), Variable(6))
item_pos.item.constraint(line=(item_pos.pos1, line))
assert 1 == len(item_pos.item._constraints)
@ -38,9 +35,7 @@ def test_line_constraint(item_pos):
def test_horizontal_constraint(item_pos):
"""Test horizontal constraint creation.
"""
"""Test horizontal constraint creation."""
item_pos.item.constraint(horizontal=(item_pos.pos1, item_pos.pos2))
assert 1 == len(item_pos.item._constraints)
@ -52,9 +47,7 @@ def test_horizontal_constraint(item_pos):
def test_vertical_constraint(item_pos):
"""Test vertical constraint creation.
"""
"""Test vertical constraint creation."""
item_pos.item.constraint(vertical=(item_pos.pos1, item_pos.pos2))
assert 1 == len(item_pos.item._constraints)
@ -66,9 +59,7 @@ def test_vertical_constraint(item_pos):
def test_left_of_constraint(item_pos):
"""Test "less than" constraint (horizontal) creation.
"""
"""Test "less than" constraint (horizontal) creation."""
item_pos.item.constraint(left_of=(item_pos.pos1, item_pos.pos2))
assert 1 == len(item_pos.item._constraints)
@ -79,9 +70,7 @@ def test_left_of_constraint(item_pos):
def test_above_constraint(item_pos):
"""
Test "less than" constraint (vertical) creation.
"""
"""Test "less than" constraint (vertical) creation."""
item_pos.item.constraint(above=(item_pos.pos1, item_pos.pos2))
assert 1 == len(item_pos.item._constraints)

View File

@ -1,6 +1,4 @@
"""Basic item tests for lines.
"""
"""Basic item tests for lines."""
from gaphas.canvas import Canvas
from gaphas.item import Line
@ -8,17 +6,13 @@ from gaphas.segment import Segment
def test_initial_ports(revert_undo):
"""Test initial ports amount.
"""
"""Test initial ports amount."""
line = Line()
assert 1 == len(line.ports())
def test_orthogonal_horizontal_undo(revert_undo, undo_fixture):
"""Test orthogonal line constraints bug (#107).
"""
"""Test orthogonal line constraints bug (#107)."""
canvas = Canvas()
line = Line()
canvas.add(line)
@ -49,9 +43,7 @@ def test_orthogonal_horizontal_undo(revert_undo, undo_fixture):
def test_orthogonal_line_undo(revert_undo, undo_fixture):
"""Test orthogonal line undo.
"""
"""Test orthogonal line undo."""
canvas = Canvas()
line = Line()
canvas.add(line)

View File

@ -24,9 +24,8 @@ class MyPickler(pickle.Pickler):
class MyDisconnect:
"""Create a disconnect object.
The disconnect object should be located at top-level, so the pickle code
can find it.
The disconnect object should be located at top-level, so the pickle
code can find it.
"""
def __call__(self):
@ -91,9 +90,7 @@ def test_pickle(canvas_fixture):
def test_pickle_connect(canvas_fixture):
"""Persist a connection.
"""
"""Persist a connection."""
canvas_fixture.line.handles()[0].visible = False
canvas_fixture.line.handles()[0].connected_to = canvas_fixture.box
canvas_fixture.line.handles()[0].disconnect = MyDisconnect()

View File

@ -71,9 +71,7 @@ def test_moving_items(qtree):
def test_get_data(qtree):
"""Test extra data added to a node.
"""
"""Test extra data added to a node."""
for i in range(0, 100, 10):
for j in range(0, 100, 10):
qtree.add(item=f"{i:d}x{j:d}", bounds=(i, j, 10, 10), data=i + j)

View File

@ -1,6 +1,4 @@
"""Test segment aspects for items.
"""
"""Test segment aspects for items."""
import pytest
from gaphas.canvas import Canvas
@ -24,9 +22,7 @@ def segment_fixture():
def test_segment_fails_for_item(seg):
"""Test if Segment aspect can be applied to Item.
"""
"""Test if Segment aspect can be applied to Item."""
try:
Segment(seg.item, seg.view)
except TypeError:
@ -36,9 +32,7 @@ def test_segment_fails_for_item(seg):
def test_segment(seg):
"""Test add a new segment to a line.
"""
"""Test add a new segment to a line."""
line = Line()
seg.canvas.add(line)
segment = Segment(line, seg.view)
@ -51,8 +45,7 @@ def test_segment(seg):
def test_split_single(simple_canvas):
"""Test single line splitting
"""
"""Test single line splitting."""
# Start with 2 handles & 1 port, after split: expect 3 handles & 2 ports
assert len(simple_canvas.line.handles()) == 2
assert len(simple_canvas.line.ports()) == 1
@ -86,9 +79,7 @@ def test_split_single(simple_canvas):
def test_split_multiple(simple_canvas):
"""Test multiple line splitting.
"""
"""Test multiple line splitting."""
simple_canvas.line.handles()[1].pos = (20, 16)
handles = simple_canvas.line.handles()
old_ports = simple_canvas.line.ports()[:]
@ -131,8 +122,7 @@ def test_split_multiple(simple_canvas):
def test_ports_after_split(simple_canvas):
"""Test ports removal after split
"""
"""Test ports removal after split."""
simple_canvas.line.handles()[1].pos = (20, 16)
segment = Segment(simple_canvas.line, simple_canvas.view)
@ -153,9 +143,7 @@ def test_ports_after_split(simple_canvas):
def test_constraints_after_split(simple_canvas):
"""Test if constraints are recreated after line split.
"""
"""Test if constraints are recreated after line split."""
# Connect line2 to self.line
line2 = Line()
simple_canvas.canvas.add(line2)
@ -175,9 +163,7 @@ def test_constraints_after_split(simple_canvas):
def test_split_undo(simple_canvas, revert_undo, undo_fixture):
"""Test line splitting undo.
"""
"""Test line splitting undo."""
simple_canvas.line.handles()[1].pos = (20, 0)
# We start with two handles and one port, after split 3 handles and
@ -197,9 +183,7 @@ def test_split_undo(simple_canvas, revert_undo, undo_fixture):
def test_orthogonal_line_split(simple_canvas):
"""Test orthogonal line splitting.
"""
"""Test orthogonal line splitting."""
# Start with no orthogonal constraints
assert len(simple_canvas.line._orthogonal_constraints) == 0
@ -222,9 +206,7 @@ def test_orthogonal_line_split(simple_canvas):
def test_params_error_exc(simple_canvas):
"""Test parameter error exceptions.
"""
"""Test parameter error exceptions."""
line = Line()
segment = Segment(line, simple_canvas.view)
@ -248,9 +230,7 @@ def test_params_error_exc(simple_canvas):
def test_merge_first_single(simple_canvas):
"""Test single line merging starting from 1st segment.
"""
"""Test single line merging starting from 1st segment."""
simple_canvas.line.handles()[1].pos = (20, 0)
segment = Segment(simple_canvas.line, simple_canvas.view)
segment.split_segment(0)
@ -284,9 +264,7 @@ def test_merge_first_single(simple_canvas):
def test_constraints_after_merge(simple_canvas):
"""Test if constraints are recreated after line merge.
"""
"""Test if constraints are recreated after line merge."""
line2 = Line()
simple_canvas.canvas.add(line2)
head = line2.handles()[0]
@ -312,9 +290,7 @@ def test_constraints_after_merge(simple_canvas):
def test_merge_multiple(simple_canvas):
"""Test multiple line merge.
"""
"""Test multiple line merge."""
simple_canvas.line.handles()[1].pos = (20, 16)
segment = Segment(simple_canvas.line, simple_canvas.view)
segment.split_segment(0, count=3)
@ -339,9 +315,7 @@ def test_merge_multiple(simple_canvas):
def test_merge_undo(simple_canvas, revert_undo, undo_fixture):
"""Test line merging undo.
"""
"""Test line merging undo."""
simple_canvas.line.handles()[1].pos = (20, 0)
segment = Segment(simple_canvas.line, simple_canvas.view)
@ -366,9 +340,7 @@ def test_merge_undo(simple_canvas, revert_undo, undo_fixture):
def test_orthogonal_line_merge(simple_canvas):
"""Test orthogonal line merging.
"""
"""Test orthogonal line merging."""
assert 12 == len(simple_canvas.canvas.solver._constraints)
simple_canvas.line.handles()[-1].pos = 100, 100
@ -393,9 +365,7 @@ def test_orthogonal_line_merge(simple_canvas):
@pytest.mark.parametrize("num_segments", [-1, 2, (0, 1), 0, 1, (0, 3)])
def test_params_errors(simple_canvas, num_segments):
"""Test parameter error exceptions.
"""
"""Test parameter error exceptions."""
line = Line()
simple_canvas.canvas.add(line)
segment = Segment(line, simple_canvas.view)

View File

@ -1,6 +1,4 @@
"""Test constraint solver.
"""
"""Test constraint solver."""
from timeit import Timer
import pytest
@ -40,17 +38,13 @@ def solver_fixture():
def test_weakest_list(solv):
"""Test weakest list.
"""
"""Test weakest list."""
assert solv.b in solv.c_eq._weakest
assert solv.c in solv.c_eq._weakest
def test_weakest_list_order(solv):
"""Test weakest list order.
"""
"""Test weakest list order."""
weakest = [el for el in solv.c_eq._weakest]
solv.a.value = 4
@ -72,17 +66,13 @@ def test_weakest_list_order(solv):
def test_strength_change(solv):
"""Test strength change.
"""
"""Test strength change."""
solv.b.strength = 9
assert solv.c_eq._weakest == [solv.b]
def test_min_size(solv):
"""Test minimal size constraint.
"""
"""Test minimal size constraint."""
v1 = Variable(0)
v2 = Variable(10)
v3 = Variable(10)
@ -124,9 +114,7 @@ def test_min_size(solv):
def test_speed_run_weakest():
"""Speed test for weakest variable.
"""
"""Speed test for weakest variable."""
results = Timer(
setup=SETUP,
stmt="""

View File

@ -21,9 +21,7 @@ class SList:
def test_adding_pair():
"""Test adding reversible pair.
"""
"""Test adding reversible pair."""
reversible_pair(
SList.add,
SList.remove,

View File

@ -1,6 +1,4 @@
"""Test all the tools.
"""
"""Test all the tools."""
from gaphas.canvas import Context
from gaphas.constraint import LineConstraint
from gaphas.tool import ConnectHandleTool
@ -12,9 +10,7 @@ Event = Context
def test_item_and_port_glue(simple_canvas):
"""Test glue operation to an item and its ports.
"""
"""Test glue operation to an item and its ports."""
ports = simple_canvas.box1.ports()
# Glue to port nw-ne
@ -39,9 +35,7 @@ def test_item_and_port_glue(simple_canvas):
def test_failed_glue(simple_canvas):
"""Test glue from too far distance.
"""
"""Test glue from too far distance."""
sink = simple_canvas.tool.glue(simple_canvas.line, simple_canvas.head, (90, 50))
assert sink is None
@ -49,9 +43,8 @@ def test_failed_glue(simple_canvas):
def test_glue_no_port_no_can_glue(simple_canvas):
"""Test no glue with no port.
Test if glue method does not call ConnectHandleTool.can_glue method when
port is not found.
Test if glue method does not call ConnectHandleTool.can_glue method
when port is not found.
"""
class Tool(ConnectHandleTool):
@ -70,9 +63,7 @@ def test_glue_no_port_no_can_glue(simple_canvas):
def test_connect(simple_canvas):
"""Test connection to an item.
"""
"""Test connection to an item."""
line, head = simple_canvas.line, simple_canvas.head
simple_canvas.tool.connect(line, head, (120, 50))
cinfo = simple_canvas.canvas.get_connection(head)
@ -91,9 +82,7 @@ def test_connect(simple_canvas):
def test_reconnect_another(simple_canvas):
"""Test reconnection to another item.
"""
"""Test reconnection to another item."""
line, head = simple_canvas.line, simple_canvas.head
simple_canvas.tool.connect(line, head, (120, 50))
cinfo = simple_canvas.canvas.get_connection(head)
@ -120,9 +109,7 @@ def test_reconnect_another(simple_canvas):
def test_reconnect_same(simple_canvas):
"""Test reconnection to same item.
"""
"""Test reconnection to same item."""
line, head = simple_canvas.line, simple_canvas.head
simple_canvas.tool.connect(line, head, (120, 50))
cinfo = simple_canvas.canvas.get_connection(head)
@ -144,9 +131,7 @@ def test_reconnect_same(simple_canvas):
def xtest_find_port(simple_canvas):
"""Test finding a port.
"""
"""Test finding a port."""
line, head = simple_canvas.line, simple_canvas.head
p1, p2, p3, p4 = simple_canvas.box1.ports()

View File

@ -10,9 +10,7 @@ def tree_fixture():
def test_add(tree_fixture):
""" Test creating node trees.
"""
"""Test creating node trees."""
tree = tree_fixture[0]
n = tree_fixture[1]
tree.add(n[1])
@ -69,9 +67,7 @@ def test_add_on_index(tree_fixture):
def test_remove(tree_fixture):
"""Test removal of nodes.
"""
"""Test removal of nodes."""
tree = tree_fixture[0]
n = tree_fixture[1]
tree.add(n[1])

View File

@ -1,6 +1,4 @@
"""Test cases for the View class.
"""
"""Test cases for the View class."""
import math
import pytest
@ -37,9 +35,7 @@ def view_fixture():
def test_bounding_box_calculations(view_fixture):
"""A view created before and after the canvas is populated should contain
the same data.
"""
the same data."""
view_fixture.view.realize()
view_fixture.box.matrix = (1.0, 0.0, 0.0, 1, 10, 10)
@ -77,9 +73,7 @@ def test_bounding_box_calculations(view_fixture):
def test_get_item_at_point(view_fixture):
"""Hover tool only reacts on motion-notify events.
"""
"""Hover tool only reacts on motion-notify events."""
view_fixture.box.width = 50
view_fixture.box.height = 50
assert len(view_fixture.view._qtree._ids) == 1
@ -182,9 +176,7 @@ def test_view_registration(view_fixture):
def test_view_registration_2(view_fixture):
"""Test view registration and destroy when view is destroyed.
"""
"""Test view registration and destroy when view is destroyed."""
assert hasattr(view_fixture.box, "_matrix_i2v")
assert hasattr(view_fixture.box, "_matrix_v2i")