Enable docformatter pre-commit hook
Signed-off-by: Dan Yeaw <dan@yeaw.me>
This commit is contained in:
parent
f6bd598ea1
commit
bdd954c2c5
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
189
gaphas/canvas.py
189
gaphas/canvas.py
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
142
gaphas/item.py
142
gaphas/item.py
@ -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).
|
||||
"""
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
"""
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
149
gaphas/tool.py
149
gaphas/tool.py
@ -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())
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
185
gaphas/view.py
185
gaphas/view.py
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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="""
|
||||
|
@ -21,9 +21,7 @@ class SList:
|
||||
|
||||
|
||||
def test_adding_pair():
|
||||
"""Test adding reversible pair.
|
||||
|
||||
"""
|
||||
"""Test adding reversible pair."""
|
||||
reversible_pair(
|
||||
SList.add,
|
||||
SList.remove,
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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])
|
||||
|
@ -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")
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user