Skip to content

Commit

Permalink
Allow connecting of (Proxy)Port and Connector
Browse files Browse the repository at this point in the history
  • Loading branch information
amolenaar committed Jul 19, 2020
1 parent 6fd6844 commit c3f9718
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 4 deletions.
36 changes: 35 additions & 1 deletion gaphor/SysML/blocks/connectors.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from gaphas.connector import Handle, Port

from gaphor.diagram.connectors import Connector
from gaphor import UML
from gaphor.diagram.connectors import Connector, UnaryRelationshipConnect
from gaphor.SysML import sysml
from gaphor.SysML.blocks.block import BlockItem
from gaphor.SysML.blocks.proxyport import ProxyPortItem
from gaphor.UML.components import ConnectorItem


@Connector.register(BlockItem, ProxyPortItem)
Expand Down Expand Up @@ -40,3 +42,35 @@ def disconnect(self, handle: Handle) -> None:
del proxy_port.subject
proxy_port.canvas.reparent(proxy_port, None)
subject.unlink()


@Connector.register(ProxyPortItem, ConnectorItem)
class PropertyConnectorConnector(UnaryRelationshipConnect):
"""Connect a Connector to a Port."""

line: ConnectorItem

def allow(self, handle, port):
element = self.element

# Element should be connected -> have a subject
if not isinstance(element.subject, UML.Port):
return None

return super().allow(handle, port)

def connect_subject(self, handle):
element = self.element
line = self.line

assert element.canvas

c1 = self.get_connected(line.head)
c2 = self.get_connected(line.tail)
if c1 and c2:

if not line.subject:
assert isinstance(c1.subject, UML.ConnectableElement)
assert isinstance(c2.subject, UML.ConnectableElement)
relation = UML.model.create_connector(c1.subject, c2.subject)
line.subject = relation
13 changes: 11 additions & 2 deletions gaphor/SysML/blocks/proxyport.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ast
from typing import Optional

from gaphas.connector import Handle
from gaphas.connector import Handle, LinePort, Position
from gaphas.geometry import Rectangle, distance_rectangle_point
from gaphas.item import Item

Expand Down Expand Up @@ -39,7 +39,16 @@ def __init__(self, id=None, model=None):

h1 = Handle(connectable=True)
self._handles.append(h1)
# self._ports.append(LinePort(h1.pos, h1.pos))

d = self.dimensions()
top_left = Position((d.x, d.y))
top_right = Position((d.x1, d.y))
bottom_right = Position((d.x1, d.y1))
bottom_left = Position((d.x, d.y1))
self._ports.append(LinePort(top_left, top_right))
self._ports.append(LinePort(top_right, bottom_right))
self._ports.append(LinePort(bottom_right, bottom_left))
self._ports.append(LinePort(bottom_left, top_left))

self._last_connected_side = None
self.watch("subject[NamedElement].name")
Expand Down
78 changes: 78 additions & 0 deletions gaphor/SysML/blocks/tests/test_connectors.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import pytest

from gaphor import UML
from gaphor.diagram.connectors import Connector
from gaphor.diagram.tests.fixtures import allow, connect, disconnect
from gaphor.SysML import sysml
from gaphor.SysML.blocks.block import BlockItem
from gaphor.SysML.blocks.connectors import BlockProxyPortConnector
from gaphor.SysML.blocks.proxyport import ProxyPortItem
from gaphor.UML.components import ConnectorItem


@pytest.fixture
Expand All @@ -17,6 +20,36 @@ def proxy_port_item(diagram):
return diagram.create(ProxyPortItem)


def connected_proxy_port_item(diagram, element_factory):
proxy_port_item = diagram.create(ProxyPortItem)
block_item = diagram.create(BlockItem, subject=element_factory.create(sysml.Block))

connector = Connector(block_item, proxy_port_item)
connector.connect(proxy_port_item.handles()[0], block_item.ports()[0])

return proxy_port_item


@pytest.fixture
def head_proxy_port_item(diagram, element_factory):
return connected_proxy_port_item(diagram, element_factory)


@pytest.fixture
def tail_proxy_port_item(diagram, element_factory):
return connected_proxy_port_item(diagram, element_factory)


@pytest.fixture
def other_proxy_port_item(diagram, element_factory):
return connected_proxy_port_item(diagram, element_factory)


@pytest.fixture
def connector_item(diagram):
return diagram.create(ConnectorItem)


def test_connection_is_allowed(diagram, block_item, proxy_port_item):
connector = Connector(block_item, proxy_port_item)

Expand All @@ -43,3 +76,48 @@ def test_disconnect_proxy_port_to_block(diagram, block_item, proxy_port_item):

assert proxy_port_item.subject is None
assert proxy_port_item.canvas


def test_allow_connector_to_proxy_port(
diagram, connector_item: ConnectorItem, head_proxy_port_item: ProxyPortItem
):
assert allow(connector_item, connector_item.handles()[0], head_proxy_port_item)


def test_connect_connector_on_one_end_to_proxy_port(
diagram, connector_item: ConnectorItem, head_proxy_port_item: ProxyPortItem
):
connect(connector_item, connector_item.handles()[0], head_proxy_port_item)

assert connector_item.subject is None


def test_connect_connector_on_both_ends_to_proxy_port(
connector_item: ConnectorItem,
head_proxy_port_item: ProxyPortItem,
tail_proxy_port_item: ProxyPortItem,
):
connect(connector_item, connector_item.handles()[0], head_proxy_port_item)
connect(connector_item, connector_item.handles()[1], tail_proxy_port_item)

assert connector_item.subject
assert head_proxy_port_item.subject in connector_item.subject.end[:].role
assert tail_proxy_port_item.subject in connector_item.subject.end[:].role


def test_disconnect_connector_from_proxy_port(
connector_item: ConnectorItem,
head_proxy_port_item: ProxyPortItem,
tail_proxy_port_item: ProxyPortItem,
element_factory,
):
connect(connector_item, connector_item.handles()[0], head_proxy_port_item)
connect(connector_item, connector_item.handles()[1], tail_proxy_port_item)

disconnect(connector_item, connector_item.handles()[0])

assert not connector_item.subject
assert element_factory.lselect(UML.Connector) == []
assert element_factory.lselect(UML.ConnectorEnd) == []
assert head_proxy_port_item.subject in element_factory.select(UML.Port)
assert tail_proxy_port_item.subject in element_factory.select(UML.Port)
2 changes: 1 addition & 1 deletion gaphor/UML/classes/association.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@


@represents(UML.Association)
class AssociationItem(LinePresentation, Named):
class AssociationItem(LinePresentation[UML.Association], Named):
"""
AssociationItem represents associations.
An AssociationItem has two AssociationEnd items. Each AssociationEnd item
Expand Down
5 changes: 5 additions & 0 deletions gaphor/UML/components/connectorconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
@Connector.register(ComponentItem, ConnectorItem)
@Connector.register(InterfaceItem, ConnectorItem)
class ConnectorConnectBase(BaseConnector):
"""
This connector is left as is, mainly for backwards compatibility.
The Connector item has been removed from the Components tool
palette.
"""

element: Union[ComponentItem, InterfaceItem]
line: ConnectorItem
Expand Down
31 changes: 31 additions & 0 deletions gaphor/UML/modelfactory.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
Class,
Classifier,
Component,
ConnectableElement,
Connector,
ConnectorEnd,
Dependency,
Element,
Extension,
Expand All @@ -26,6 +29,7 @@
Interface,
Message,
MessageOccurrenceSpecification,
Port,
Property,
Realization,
Slot,
Expand Down Expand Up @@ -265,6 +269,33 @@ def create_association(type_a, type_b):
return assoc


def create_connector(type_a: ConnectableElement, type_b: ConnectableElement):
"""
Create a connector between two items.
Depending on the ends, the connector kind may be "assembly" or "delegation".
"""
assert type_a.model is type_b.model, "Head and Tail end are from different models"
model = type_a.model
conn = model.create(Connector)
end_a = model.create(ConnectorEnd)
end_b = model.create(ConnectorEnd)

conn.end = end_a
conn.end = end_b

end_a.role = type_a
end_b.role = type_b

if (isinstance(end_a, Port) and isinstance(end_b, Property)) or (
isinstance(end_a, Property) and isinstance(end_b, Port)
):
conn.kind = "delegation"
else:
conn.kind = "assembly"

return conn


def set_navigability(assoc, end, nav):
"""
Set navigability of an association end (property).
Expand Down

0 comments on commit c3f9718

Please sign in to comment.