Skip to content

Commit

Permalink
Merge pull request #352 from gaphor/feature/bdd-association-shortcuts
Browse files Browse the repository at this point in the history
Add Part and Shared Association to Tool Palette
  • Loading branch information
danyeaw committed Jun 30, 2020
2 parents 6c47a48 + 9bb4191 commit ea8ca89
Show file tree
Hide file tree
Showing 10 changed files with 1,586 additions and 1,360 deletions.
62 changes: 50 additions & 12 deletions gaphor/SysML/toolbox.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""The action definition for the SysML toolbox."""

from enum import Enum

from gaphas.item import SE

import gaphor.SysML.diagramitems as sysml_items
Expand All @@ -9,13 +11,12 @@
from gaphor.diagram.diagramtoolbox import ToolboxDefinition, ToolDef
from gaphor.diagram.diagramtools import PlacementTool
from gaphor.SysML import sysml
from gaphor.UML.toolbox import namespace_config


def namespace_config(new_item):
subject = new_item.subject
diagram = new_item.canvas.diagram
subject.package = diagram.namespace
subject.name = f"New{type(subject).__name__}"
class AssociationType(Enum):
COMPOSITE = "composite"
SHARED = "shared"


def initial_pseudostate_config(new_item):
Expand All @@ -31,6 +32,28 @@ def metaclass_config(new_item):
new_item.subject.name = "Class"


def create_association(
assoc_item: uml_items.AssociationItem, association_type: AssociationType
) -> None:
assoc = assoc_item.subject
assoc.memberEnd.append(assoc_item.model.create(UML.Property))
assoc.memberEnd.append(assoc_item.model.create(UML.Property))

assoc_item.head_end.subject = assoc.memberEnd[0]
assoc_item.tail_end.subject = assoc.memberEnd[1]

UML.model.set_navigability(assoc, assoc_item.head_end.subject, True)
assoc_item.head_end.subject.aggregation = association_type.value


def composite_association_config(assoc_item: uml_items.AssociationItem) -> None:
create_association(assoc_item, AssociationType.COMPOSITE)


def shared_association_config(assoc_item: uml_items.AssociationItem) -> None:
create_association(assoc_item, AssociationType.SHARED)


# Actions: ((section (name, label, icon_name, shortcut)), ...)
sysml_toolbox_actions: ToolboxDefinition = (
(
Expand Down Expand Up @@ -108,6 +131,28 @@ def metaclass_config(new_item):
),
handle_index=SE,
),
ToolDef(
"toolbox-composite-association",
gettext("Composite Association"),
"gaphor-composite-association-symbolic",
"<Shift>Z",
PlacementTool.new_item_factory(
uml_items.AssociationItem,
UML.Association,
config_func=composite_association_config,
),
),
ToolDef(
"toolbox-shared-association",
gettext("Shared Association"),
"gaphor-shared-association-symbolic",
"<Shift>Q",
PlacementTool.new_item_factory(
uml_items.AssociationItem,
UML.Association,
config_func=shared_association_config,
),
),
ToolDef(
"toolbox-association",
gettext("Association"),
Expand Down Expand Up @@ -141,13 +186,6 @@ def metaclass_config(new_item):
"<Shift>Y",
PlacementTool.new_item_factory(sysml_items.ProxyPortItem),
),
ToolDef(
"toolbox-property",
gettext("Property"),
"gaphor-property-symbolic",
"<Shift>M",
PlacementTool.new_item_factory(sysml_items.PropertyItem),
),
),
),
(
Expand Down
67 changes: 35 additions & 32 deletions gaphor/UML/classes/classconnect.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Classes related (dependency, implementation) adapter connections."""

from gaphas import Handle

from gaphor import UML
from gaphor.diagram.connectors import (
Connector,
Expand Down Expand Up @@ -109,28 +111,39 @@ def connect_subject(self, handle):
c1 = self.get_connected(line.head)
c2 = self.get_connected(line.tail)
if c1 and c2:
head_type = c1.subject
tail_type = c2.subject

# First check if we do not already contain the right subject:
if line.subject:
if not line.subject:
relation = UML.model.create_association(c1.subject, c2.subject)
relation.package = element.canvas.diagram.namespace
line.head_end.subject = relation.memberEnd[0]
line.tail_end.subject = relation.memberEnd[1]

# Set subject last so that event handlers can trigger
line.subject = relation

else:
assert isinstance(line.subject, UML.Association)
end1 = line.subject.memberEnd[0]
end2 = line.subject.memberEnd[1]
if (end1.type is head_type and end2.type is tail_type) or (
end2.type is head_type and end1.type is tail_type
if (end1.type is c1.subject and end2.type is c2.subject) or (
end2.type is c1.subject and end1.type is c2.subject
):
return

# Create new association
relation = UML.model.create_association(head_type, tail_type)
relation.package = element.canvas.diagram.namespace

line.head_end.subject = relation.memberEnd[0]
line.tail_end.subject = relation.memberEnd[1]

# Do subject itself last, so event handlers can trigger
line.subject = relation
line.subject.memberEnd[0].type = c1.subject
line.subject.memberEnd[1].type = c2.subject
UML.model.set_navigability(
line.subject,
line.head_end.subject,
line.subject.memberEnd[0].navigability,
)
line.head_end.subject.aggregation = line.subject.memberEnd[0].aggregation
UML.model.set_navigability(
line.subject,
line.tail_end.subject,
line.subject.memberEnd[1].navigability,
)
line.tail_end.subject.aggregation = line.subject.memberEnd[1].aggregation

def reconnect(self, handle, port):
line = self.line
Expand All @@ -152,24 +165,14 @@ def reconnect(self, handle, port):
oend.subject.type = c.subject
UML.model.set_navigability(line.subject, oend.subject, nav)

def disconnect_subject(self, handle):
"""
Disconnect model element.
Disconnect property (memberEnd) too, in case of end of life for
Extension
def disconnect_subject(self, handle: Handle) -> None:
"""Disconnect the type of each member end.
On connect, we pair association member ends with the element they
connect to. On disconnect, we remove this relation.
"""
opposite = self.line.opposite(handle)
c1 = self.get_connected(handle)
c2 = self.get_connected(opposite)
if c1 and c2:
old: UML.Association = self.line.subject
del self.line.subject
del self.line.head_end.subject
del self.line.tail_end.subject
if old and len(old.presentation) == 0:
for e in list(old.memberEnd):
e.unlink()
old.unlink()
for e in list(self.line.subject.memberEnd):
e.type = None


@Connector.register(Named, ImplementationItem)
Expand Down
3 changes: 2 additions & 1 deletion gaphor/UML/classes/classespropertypages.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,8 @@ def __init__(self, item):
def construct_end(self, builder, end_name, end):
subject = end.subject
title = builder.get_object(f"{end_name}-title")
title.set_text(f"{end_name.title()} (: {subject.type.name})")
if subject.type:
title.set_text(f"{end_name.title()} (: {subject.type.name})")

self.update_end_name(builder, end_name, subject)

Expand Down
6 changes: 3 additions & 3 deletions gaphor/UML/classes/tests/test_classconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,6 @@ def test_disconnect(self):
assert asc.subject is not None

self.disconnect(asc, asc.head)

# after disconnection: one diagram and two classes
self.assertEqual(3, len(list(self.element_factory.select())))
self.disconnect(asc, asc.tail)
assert c1 is not self.get_connected(asc.head)
assert c2 is not self.get_connected(asc.tail)
19 changes: 7 additions & 12 deletions gaphor/UML/modelfactory.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,17 +269,12 @@ def set_navigability(assoc, end, nav):
"""
Set navigability of an association end (property).
There are three possible values for ``nav`` parameter
True
association end is navigable
False
association end is not navigable
None
association end navigability is unknown
There are two ways of specifying that an end is navigable
There are three possible values for ``nav`` parameter:
1. True - association end is navigable
2. False - association end is not navigable
3. None - association end navigability is unknown
There are two ways of specifying that an end is navigable:
- an end is in Association.navigableOwnedEnd collection
- an end is class (interface) attribute (stored in Class.ownedAttribute
collection)
Expand All @@ -291,7 +286,7 @@ def set_navigability(assoc, end, nav):
There two association ends A.x and B.y, A.x is navigable.
Therefore navigable association ends are constructed in following way
Therefore, we construct navigable association ends in the following way:
- if A is a class or an interface, then A.x is an attribute owned by A
- if A is other classifier, then association is more general
Expand All @@ -302,7 +297,7 @@ def set_navigability(assoc, end, nav):
- when A and B are instances of Node class, then it is a
communication path
Therefore navigable association end may be stored as one of
Therefore, we store the navigable association end as one of the following:
- {Class,Interface}.ownedAttribute due to their capabilities of
editing owned members
- Association.navigableOwnedEnd
Expand Down
14 changes: 14 additions & 0 deletions gaphor/UML/toolbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ def metaclass_config(new_item):
),
handle_index=SE,
),
ToolDef(
"toolbox-composite-association",
gettext("Composite Association"),
"gaphor-composite-association-symbolic",
"<Shift>Z",
PlacementTool.new_item_factory(diagramitems.AssociationItem),
),
ToolDef(
"toolbox-shared-association",
gettext("Shared Association"),
"gaphor-shared-association-symbolic",
"<Shift>Q",
PlacementTool.new_item_factory(diagramitems.AssociationItem),
),
ToolDef(
"toolbox-association",
gettext("Association"),
Expand Down
2 changes: 2 additions & 0 deletions gaphor/ui/icons/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ ICONS=diagram \
package \
interface \
association \
composite-association \
shared-association \
generalization \
dependency \
implementation \
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit ea8ca89

Please sign in to comment.