Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stereotypes for association ends #311

Merged
merged 5 commits into from
May 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 24 additions & 6 deletions gaphor/UML/classes/classespropertypages.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from gaphor.UML.classes.interface import Folded, InterfaceItem
from gaphor.UML.classes.klass import ClassItem
from gaphor.UML.components.connector import ConnectorItem
from gaphor.UML.profiles.stereotypepropertypages import stereotype_model

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -439,16 +440,31 @@ def __init__(self, item):
self.semaphore = 0

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()} (: {end.subject.type.name})")
title.set_text(f"{end_name.title()} (: {subject.type.name})")

self.update_end_name(builder, end_name, end.subject)
self.update_end_name(builder, end_name, subject)

navigation = builder.get_object(f"{end_name}-navigation")
navigation.set_active(self.NAVIGABILITY.index(end.subject.navigability))
navigation.set_active(self.NAVIGABILITY.index(subject.navigability))

aggregation = builder.get_object(f"{end_name}-aggregation")
aggregation.set_active(self.AGGREGATION.index(end.subject.aggregation))
aggregation.set_active(self.AGGREGATION.index(subject.aggregation))

stereotypes = UML.model.get_stereotypes(subject)
if stereotypes:
stereotype_list = builder.get_object(f"{end_name}-stereotype-list")
model, toggle_handler, set_value_handler = stereotype_model(subject)
stereotype_list.set_model(model)
return {
f"{end_name}-toggle-stereotype": toggle_handler,
f"{end_name}-set-slot-value": set_value_handler,
}
else:
stereotype_frame = builder.get_object(f"{end_name}-stereotype-frame")
stereotype_frame.destroy()
return {}

def update_end_name(self, builder, end_name, subject):
name = builder.get_object(f"{end_name}-name")
Expand All @@ -474,8 +490,8 @@ def construct(self):
show_direction = builder.get_object("show-direction")
show_direction.set_active(self.item.show_direction)

self.construct_end(builder, "head", head)
self.construct_end(builder, "tail", tail)
head_signal_handlers = self.construct_end(builder, "head", head)
tail_signal_handlers = self.construct_end(builder, "tail", tail)

def handler(event):
end_name = "head" if event.element is head.subject else "tail"
Expand All @@ -501,6 +517,8 @@ def handler(event):
"tail-navigation-changed": (self._on_end_navigability_change, tail),
"tail-aggregation-changed": (self._on_end_aggregation_change, tail),
"association-editor-destroy": (self.watcher.unsubscribe_all,),
**head_signal_handlers,
**tail_signal_handlers,
}
)

Expand Down
216 changes: 113 additions & 103 deletions gaphor/UML/profiles/stereotypepropertypages.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,132 +36,142 @@ def construct(self):
else:
show_stereotypes.destroy()

self.model = Gtk.TreeStore.new(
[
str, # stereotype/attribute
str, # value
bool, # is applied stereotype
bool, # show checkbox (is stereotype)
bool, # value editable
object, # stereotype / attribute
object, # value editable
object, # slot element
]
model, toggle_stereotype_handler, set_slot_value_handler = stereotype_model(
subject
)

stereotype_list = builder.get_object("stereotype-list")
stereotype_list.set_model(self.model)
stereotype_list.set_model(model)

builder.connect_signals(
{
"show-stereotypes-changed": (self._on_show_stereotypes_change,),
"toggle-stereotype": (self._toggle_stereotype, self.model, 2),
"set-slot-value": (self._set_value, self.model, 1),
"toggle-stereotype": toggle_stereotype_handler,
"set-slot-value": set_slot_value_handler,
}
)
self.refresh()
return builder.get_object("stereotypes-editor")

def refresh(self):
subject = self.item.subject
stereotypes = UML.model.get_stereotypes(subject)
instances = subject.appliedStereotype

def upsert(path, parent, row_data):
try:
new_row = self.model.get_iter(path)
except ValueError:
new_row = self.model.append(parent, row_data)
else:
row = self.model[path]
row[:] = row_data
return new_row

# shortcut map stereotype -> slot (InstanceSpecification)
slots = {}
for applied in instances:
for slot in applied.slot:
slots[slot.definingFeature] = slot

for st_index, st in enumerate(stereotypes):
for applied in instances:
if st in applied.classifier:
break
else:
applied = None

parent = upsert(
f"{st_index}",
None,
(st.name, "", bool(applied), True, False, st, None, None),
)
for attr_index, attr in enumerate(
attr for attr in st.ownedAttribute if not attr.association
):
slot = slots.get(attr)
value = slot.value if slot else ""
upsert(
f"{st_index}:{attr_index}",
parent,
(
attr.name,
value,
bool(applied),
False,
bool(applied),
attr,
applied,
slot,
),
)
return builder.get_object("stereotypes-editor")

@transactional
def _on_show_stereotypes_change(self, button):
self.item.show_stereotypes = button.get_active()

@transactional
def _toggle_stereotype(self, renderer, path, model, col):
row = self.model[path]
name, old_value, is_applied, _, _, stereotype, _, _ = row
value = not is_applied

subject = self.item.subject
if value:
UML.model.apply_stereotype(subject, stereotype)
def stereotype_model(subject):
model = Gtk.TreeStore.new(
[
str, # stereotype/attribute
str, # value
bool, # is applied stereotype
bool, # show checkbox (is stereotype)
bool, # value editable
object, # stereotype / attribute
object, # value editable
object, # slot element
]
)
refresh(subject, model)

return model, (_toggle_stereotype, subject, model), (_set_value, model)


def refresh(subject, model):
stereotypes = UML.model.get_stereotypes(subject)
instances = subject.appliedStereotype

def upsert(path, parent, row_data):
try:
new_row = model.get_iter(path)
except ValueError:
new_row = model.append(parent, row_data)
else:
UML.model.remove_stereotype(subject, stereotype)
row = model[path]
row[:] = row_data
return new_row

row[2] = value
# shortcut map stereotype -> slot (InstanceSpecification)
slots = {}
for applied in instances:
for slot in applied.slot:
slots[slot.definingFeature] = slot

self.refresh()
for st_index, st in enumerate(stereotypes):
for applied in instances:
if st in applied.classifier:
break
else:
applied = None

@transactional
def _set_value(self, renderer, path, value, model, col=0):
"""
Set value of stereotype property applied to an UML element.
parent = upsert(
f"{st_index}",
None,
(st.name, "", bool(applied), True, False, st, None, None),
)
for attr_index, attr in enumerate(
attr for attr in st.ownedAttribute if not attr.association
):
slot = slots.get(attr)
value = slot.value if slot else ""
upsert(
f"{st_index}:{attr_index}",
parent,
(
attr.name,
value,
bool(applied),
False,
bool(applied),
attr,
applied,
slot,
),
)

Slot is created if instance Create valuChange value of instance spe
"""
row = self.model[path]
name, old_value, is_applied, _, _, attr, applied, slot = row
if isinstance(attr, UML.Stereotype):
return # don't edit stereotype rows

if slot is None and not value:
return # nothing to do and don't create slot without value
@transactional
def _toggle_stereotype(renderer, path, subject, model):
row = model[path]
name, old_value, is_applied, _, _, stereotype, _, _ = row
value = not is_applied

if slot is None:
slot = UML.model.add_slot(applied, attr)
if value:
UML.model.apply_stereotype(subject, stereotype)
else:
UML.model.remove_stereotype(subject, stereotype)

assert slot
row[2] = value

if value:
slot.value = value
else:
# no value, then remove slot
del applied.slot[slot]
slot = None
value = ""
refresh(subject, model)


@transactional
def _set_value(renderer, path, value, model):
"""
Set value of stereotype property applied to an UML element.

Slot is created if instance Create valuChange value of instance spe
"""
row = model[path]
name, old_value, is_applied, _, _, attr, applied, slot = row
if isinstance(attr, UML.Stereotype):
return # don't edit stereotype rows

if slot is None and not value:
return # nothing to do and don't create slot without value

if slot is None:
slot = UML.model.add_slot(applied, attr)

assert slot

if value:
slot.value = value
else:
# no value, then remove slot
del applied.slot[slot]
slot = None
value = ""

row[1] = value
row[5] = slot
row[1] = value
row[5] = slot
9 changes: 7 additions & 2 deletions gaphor/UML/profiles/tests/test_stereotypepage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Test Stereotype Property Page."""

import pytest
from gi.repository import Gtk

from gaphor import UML
from gaphor.UML.classes.klass import ClassItem
Expand Down Expand Up @@ -38,7 +39,11 @@ def test_stereotype_page_with_stereotype(element_factory, diagram, class_):

editor = StereotypePage(class_)
page = editor.construct()
editor.refresh()

assert len(editor.model) == 1
box = page.get_children()[0]
frame = box.get_children()[1]
stereotype_view = frame.get_children()[0]

assert isinstance(stereotype_view, Gtk.TreeView)
assert len(stereotype_view.get_model()) == 1
assert page is not None