Skip to content

Commit

Permalink
Merge pull request #145 from gaphor/item-constraints
Browse files Browse the repository at this point in the history
Item constraint handling
  • Loading branch information
amolenaar committed Nov 4, 2020
2 parents f0661ef + cdcc0d4 commit c3e2084
Show file tree
Hide file tree
Showing 28 changed files with 684 additions and 798 deletions.
21 changes: 11 additions & 10 deletions docs/undo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ item.py: Element
An element has ``min_height`` and ``min_width`` properties.

>>> from gaphas import Element
>>> e = Element()
>>> from gaphas.connections import Connections
>>> e = Element(Connections())
>>> e.min_height, e.min_width
(Variable(10, 100), Variable(10, 100))
>>> e.min_height, e.min_width = 30, 40
Expand All @@ -293,7 +294,7 @@ A line has the following properties: ``line_width``, ``fuzziness``,

>>> from gaphas import Line
>>> from gaphas.segment import Segment
>>> l = Line()
>>> l = Line(Connections())

Let's first add a segment to the line, to test orthogonal lines as well.

Expand Down Expand Up @@ -345,18 +346,18 @@ Also creation and removal of connected lines is recorded and can be undone:
>>> def real_connect(hitem, handle, item):
... def real_disconnect():
... pass
... canvas.connect_item(hitem, handle, item, port=None, constraint=None, callback=real_disconnect)
... canvas.connections.connect_item(hitem, handle, item, port=None, constraint=None, callback=real_disconnect)
>>> b0 = Item()
>>> canvas.add(b0)
>>> b1 = Item()
>>> canvas.add(b1)
>>> l = Line()
>>> l = Line(Connections())
>>> canvas.add(l)
>>> real_connect(l, l.handles()[0], b0)
>>> real_connect(l, l.handles()[1], b1)
>>> canvas.get_connection(l.handles()[0]) # doctest: +ELLIPSIS
>>> canvas.connections.get_connection(l.handles()[0]) # doctest: +ELLIPSIS
Connection(item=<gaphas.item.Line object at 0x...>)
>>> canvas.get_connection(l.handles()[1]) # doctest: +ELLIPSIS
>>> canvas.connections.get_connection(l.handles()[1]) # doctest: +ELLIPSIS
Connection(item=<gaphas.item.Line object at 0x...>)

Clear already collected undo data:
Expand All @@ -369,16 +370,16 @@ Now remove the line from the canvas:

The handles are disconnected:

>>> canvas.get_connection(l.handles()[0])
>>> canvas.get_connection(l.handles()[1])
>>> canvas.connections.get_connection(l.handles()[0])
>>> canvas.connections.get_connection(l.handles()[1])

Undoing the remove() action should put everything back in place again:

>>> undo()

>>> canvas.get_connection(l.handles()[0]) # doctest: +ELLIPSIS
>>> canvas.connections.get_connection(l.handles()[0]) # doctest: +ELLIPSIS
Connection(item=<gaphas.item.Line object at 0x...>)
>>> canvas.get_connection(l.handles()[1]) # doctest: +ELLIPSIS
>>> canvas.connections.get_connection(l.handles()[1]) # doctest: +ELLIPSIS
Connection(item=<gaphas.item.Line object at 0x...>)


Expand Down
16 changes: 8 additions & 8 deletions examples/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def factory(view, cls):
"""Simple canvas item factory."""

def wrapper():
item = cls()
item = cls(view.canvas.connections)
view.canvas.add(item)
return item

Expand All @@ -71,8 +71,8 @@ class MyBox(Box):
class MyLine(Line):
"""Line with experimental connection protocol."""

def __init__(self):
super().__init__()
def __init__(self, connections):
super().__init__(connections)
self.fuzziness = 2

def draw_head(self, context):
Expand Down Expand Up @@ -330,18 +330,18 @@ def handle_changed(view, item, what):
def create_canvas(c=None):
if not c:
c = Canvas()
b = MyBox()
b = MyBox(c.connections)
b.min_width = 20
b.min_height = 30
b.matrix.translate(20, 20)
b.width = b.height = 40
c.add(b)

bb = Box()
bb = Box(c.connections)
bb.matrix.translate(10, 10)
c.add(bb, parent=b)

bb = Box()
bb = Box(c.connections)
bb.matrix.rotate(math.pi / 1.567)
c.add(bb, parent=b)

Expand All @@ -351,7 +351,7 @@ def create_canvas(c=None):
circle.matrix.translate(50, 160)
c.add(circle)

pb = Box(60, 60)
pb = Box(c.connections, 60, 60)
pb.min_width = 40
pb.min_height = 50
pb.matrix.translate(100, 20)
Expand All @@ -365,7 +365,7 @@ def create_canvas(c=None):
t.matrix.translate(100, 170)
c.add(t)

line = MyLine()
line = MyLine(c.connections)
c.add(line)
line.handles()[1].pos = (30, 30)
segment = Segment(line, c)
Expand Down
11 changes: 4 additions & 7 deletions examples/exampleitems.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ class Box(Element):
NW +---+ NE SW +---+ SE
"""

def __init__(self, width=10, height=10):
super().__init__(width, height)
def __init__(self, connections, width=10, height=10):
super().__init__(connections, width, height)

def draw(self, context):
c = context.cairo
Expand Down Expand Up @@ -57,6 +57,8 @@ class Circle(Item):
def __init__(self):
super().__init__()
self._handles.extend((Handle(), Handle()))
h1, h2 = self._handles
h1.movable = False

def _set_radius(self, r):
h1, h2 = self._handles
Expand All @@ -70,11 +72,6 @@ def _get_radius(self):

radius = property(_get_radius, _set_radius)

def setup_canvas(self):
super().setup_canvas()
h1, h2 = self._handles
h1.movable = False

def point(self, pos):
h1, _ = self._handles
p1 = h1.pos
Expand Down
2 changes: 1 addition & 1 deletion examples/simple-box.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def create_canvas(canvas, title):
canvas.add(b2)

# Draw gaphas line
line = Line()
line = Line(canvas.connections)
line.matrix.translate(100, 60)
canvas.add(line)
line.handles()[1].pos = (30, 30)
Expand Down
47 changes: 9 additions & 38 deletions gaphas/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
==============================
To get connected item to a handle::
c = canvas.get_connection(handle)
c = canvas.connections.get_connection(handle)
if c is not None:
print c.connected
print c.port
Expand All @@ -32,13 +32,14 @@

import cairo

from gaphas import matrix, solver, tree
from gaphas import matrix, tree
from gaphas.connections import Connections
from gaphas.decorators import nonrecursive
from gaphas.state import observed, reversible_method, reversible_pair

if TYPE_CHECKING:
from gaphas.item import Item
from gaphas.view.model import View


class Context:
Expand Down Expand Up @@ -77,7 +78,7 @@ class Canvas:
def __init__(self, create_update_context=default_update_context):
self._create_update_context = create_update_context
self._tree: tree.Tree[Item] = tree.Tree()
self._connections = Connections(solver.Solver())
self._connections = Connections()

self._registered_views = set()

Expand All @@ -99,18 +100,16 @@ def add(self, item, parent=None, index=None):
True
"""
assert item not in self._tree.nodes, f"Adding already added node {item}"
self._tree.add(item, parent, index)

item._set_canvas(self)

self._tree.add(item, parent, index)
self.request_update(item)

@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."""
item._set_canvas(None)
self._tree.remove(item)
self._connections.disconnect_item(self)
self._update_views(removed_items=(item,))

def remove(self, item):
Expand All @@ -127,7 +126,7 @@ def remove(self, item):
"""
for child in reversed(self.get_children(item)):
self.remove(child)
self.remove_connections_to_item(item)
self._connections.remove_connections_to_item(item)
self._remove(item)

reversible_pair(
Expand Down Expand Up @@ -258,34 +257,6 @@ def get_all_children(self, item):
"""
return self._tree.get_all_children(item)

def connect_item(
self, item, handle, connected, port, constraint=None, callback=None
):
self._connections.connect_item(
item, handle, connected, port, constraint, callback
)

def disconnect_item(self, item, handle=None):
self._connections.disconnect_item(item, handle)

def remove_connections_to_item(self, item):
self._connections.remove_connections_to_item(item)

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.
"""
self._connections.reconnect_item(item, handle, port, constraint)

def get_connection(self, handle):
return self._connections.get_connection(handle)

def get_connections(self, item=None, handle=None, connected=None, port=None):
return self._connections.get_connections(item, handle, connected, port)

def sort(self, items):
"""Sort a list of items in the order in which they are traversed in the
canvas (Depth first).
Expand Down Expand Up @@ -401,15 +372,15 @@ def dirty_items_with_ancestors():
except Exception as e:
logging.error("Error while updating canvas", exc_info=e)

def register_view(self, view):
def register_view(self, view: 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.
"""
self._registered_views.add(view)

def unregister_view(self, view):
def unregister_view(self, view: View):
"""Unregister a view on this canvas.
This method is called when setting a canvas on a view and should
Expand Down
23 changes: 17 additions & 6 deletions gaphas/connections.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""This module contains a connections manager."""

from collections import namedtuple
from typing import Optional

from gaphas import table
from gaphas.solver import Solver
Expand All @@ -25,12 +26,24 @@ class ConnectionError(Exception):


class Connections:
def __init__(self, solver: Solver):
self._solver = solver
def __init__(self, solver: Optional[Solver] = None):
self._solver = solver or Solver()
self._connections = table.Table(Connection, list(range(4)))

solver = property(lambda s: s._solver)

def solve(self):
self._solver.solve()

def add_constraint(self, item, constraint):
self._solver.add_constraint(constraint)
self._connections.insert(item, None, None, None, constraint, None)
return constraint

def remove_constraint(self, item, constraint):
self._solver.remove_constraint(constraint)
self._connections.delete(item, None, None, None, constraint, None)

@observed
def connect_item(
self, item, handle, connected, port, constraint=None, callback=None
Expand Down Expand Up @@ -151,8 +164,7 @@ def reconnect_item(self, item, handle, port=None, constraint=None):
def get_connection(self, handle):
"""Get connection information for specified handle.
>>> from gaphas.solver import Solver
>>> c = Connections(Solver())
>>> c = Connections()
>>> from gaphas.item import Line
>>> line = Line()
>>> from gaphas import item
Expand All @@ -177,8 +189,7 @@ def get_connections(self, item=None, handle=None, connected=None, port=None):
that are connected). If ``item`` is connected to itself it
will also appear in the list.
>>> from gaphas.solver import Solver
>>> c = Connections(Solver())
>>> c = Connections()
>>> from gaphas import item
>>> i = item.Line()
>>> ii = item.Line()
Expand Down
2 changes: 1 addition & 1 deletion gaphas/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Handle:
"""

def __init__(self, pos=(0, 0), strength=NORMAL, connectable=False, movable=True):
self._pos = Position(pos, strength)
self._pos = Position(pos[0], pos[1], strength)
self._connectable = connectable
self._movable = movable
self._visible = True
Expand Down

0 comments on commit c3e2084

Please sign in to comment.