diff --git a/gaphor/core/modeling/diagram.py b/gaphor/core/modeling/diagram.py index 279b8f7ab1..b4367d16c4 100644 --- a/gaphor/core/modeling/diagram.py +++ b/gaphor/core/modeling/diagram.py @@ -323,10 +323,15 @@ def iter_children(item): yield child yield from iter_children(child) - for root in self.ownedPresentation: - if not root.parent: - yield root - yield from iter_children(root) + def traverse_items() -> Iterable[Presentation]: + for root in self.ownedPresentation: + if not root.parent: + yield root + yield from iter_children(root) + + yield from sorted( + traverse_items(), key=lambda e: int(isinstance(e, gaphas.Line)) + ) def get_parent(self, item: Presentation) -> Presentation | None: return item.parent diff --git a/gaphor/core/modeling/element.py b/gaphor/core/modeling/element.py index 6837ceee4c..866b3692bb 100644 --- a/gaphor/core/modeling/element.py +++ b/gaphor/core/modeling/element.py @@ -5,7 +5,6 @@ import logging import uuid -from contextlib import contextmanager from typing import TYPE_CHECKING, Callable, Iterator, Protocol, TypeVar, overload from gaphor.core.modeling.event import ElementUpdated @@ -200,10 +199,6 @@ def watcher( def handle(self, event: object) -> None: ... - @contextmanager - def block_events(self) -> Iterator[RepositoryProtocol]: - ... - class EventWatcherProtocol(Protocol): def watch(self, path: str, handler: Handler | None = None) -> EventWatcherProtocol: diff --git a/gaphor/core/modeling/presentation.py b/gaphor/core/modeling/presentation.py index 24e651ba9f..b2185c1021 100644 --- a/gaphor/core/modeling/presentation.py +++ b/gaphor/core/modeling/presentation.py @@ -23,14 +23,7 @@ class Presentation(Matrices, Element, Generic[S]): """This presentation is used to link the behaviors of - `gaphor.core.modeling` and `gaphas.Item`. - - Note that Presentations are not managed by the Element Factory. - Instead, Presentation objects are owned by Diagram. As a result they - do not emit ElementCreated and ElementDeleted events. Presentations - have their own create and delete events: ElementCreated and - ElementDeleted. - """ + `gaphor.core.modeling` and `gaphas.Item`.""" def __init__(self, diagram: Diagram, id: Id | None = None) -> None: super().__init__(id=id, model=diagram.model) diff --git a/gaphor/core/modeling/tests/test_diagram.py b/gaphor/core/modeling/tests/test_diagram.py index b9a1a83501..7c8203daa5 100644 --- a/gaphor/core/modeling/tests/test_diagram.py +++ b/gaphor/core/modeling/tests/test_diagram.py @@ -1,10 +1,7 @@ import gaphas import pytest -from gaphor.core.eventmanager import EventManager -from gaphor.core.modeling import Diagram, ElementFactory, Presentation, StyleSheet -from gaphor.core.modeling.elementdispatcher import ElementDispatcher -from gaphor.UML.modelinglanguage import UMLModelingLanguage +from gaphor.core.modeling import Diagram, Presentation, StyleSheet class Example(gaphas.Element, Presentation): @@ -16,15 +13,18 @@ def unlink(self): super().unlink() +class ExampleLine(gaphas.Line, Presentation): + def __init__(self, diagram, id): + super().__init__(connections=diagram.connections, diagram=diagram, id=id) + + def unlink(self): + self.test_unlinked = True + super().unlink() + + @pytest.fixture -def element_factory(): - event_manager = EventManager() - element_dispatcher = ElementDispatcher(event_manager, UMLModelingLanguage()) - element_factory = ElementFactory(event_manager, element_dispatcher) - yield element_factory - element_factory.shutdown() - element_dispatcher.shutdown() - event_manager.shutdown() +def diagram(element_factory): + return element_factory.create(Diagram) def test_diagram_can_be_used_as_gtkview_model(): @@ -82,7 +82,6 @@ def request_update(self, items, removed_items) -> None: def test_remove_presentation_triggers_view(element_factory): diagram = element_factory.create(Diagram) - print(diagram.watcher()) view = ViewMock() diagram.register_view(view) @@ -93,3 +92,29 @@ def test_remove_presentation_triggers_view(element_factory): assert example.diagram is None assert example not in diagram.ownedPresentation assert example in view.removed_items + + +def test_order_presentations_lines_are_last(diagram): + example_line = diagram.create(ExampleLine) + example = diagram.create(Example) + + assert list(diagram.get_all_items()) == [example, example_line] + + +def test_order_presentations_line_is_grouped(diagram): + example_line = diagram.create(ExampleLine) + example_1 = diagram.create(Example) + example_2 = diagram.create(Example) + + example_line.parent = example_1 + + assert list(diagram.get_all_items()) == [example_1, example_2, example_line] + + +def test_order_grouped_presentations(diagram): + example_1 = diagram.create(Example) + example_2 = diagram.create(Example) + + example_1.parent = example_2 + + assert list(diagram.get_all_items()) == [example_2, example_1] diff --git a/gaphor/services/tests/test_undo_presentation.py b/gaphor/services/tests/test_undo_presentation.py index c9d0aa4047..5297e1d39d 100644 --- a/gaphor/services/tests/test_undo_presentation.py +++ b/gaphor/services/tests/test_undo_presentation.py @@ -51,7 +51,7 @@ def test_line_delete(diagram, undo_manager, event_manager): def test_line_orthogonal_property(diagram, undo_manager, event_manager): with Transaction(event_manager): - line = LinePresentation(diagram) + line = diagram.create(LinePresentation) line.insert_handle(0, Handle()) with Transaction(event_manager): @@ -70,7 +70,7 @@ def test_line_orthogonal_property(diagram, undo_manager, event_manager): def test_line_horizontal_property(diagram, undo_manager, event_manager): with Transaction(event_manager): - line = LinePresentation(diagram) + line = diagram.create(LinePresentation) line.insert_handle(0, Handle()) with Transaction(event_manager): @@ -99,7 +99,7 @@ def test_line_horizontal_property(diagram, undo_manager, event_manager): def test_matrix_operation(action, diagram, undo_manager, event_manager): with Transaction(event_manager): - line = LinePresentation(diagram) + line = diagram.create(LinePresentation) line.matrix.translate(10, 0) original = tuple(line.matrix) diff --git a/gaphor/services/undomanager.py b/gaphor/services/undomanager.py index fb8ab5134c..012470a3b6 100644 --- a/gaphor/services/undomanager.py +++ b/gaphor/services/undomanager.py @@ -265,14 +265,9 @@ def _action_executed(self, event=None): self.event_manager.handle(ActionEnabled("win.edit-redo", self.can_redo())) self.event_manager.handle(UndoManagerStateChanged(self)) - def deep_lookup(self, id: str) -> Element: + def lookup(self, id: str) -> Element: element: Optional[Element] = self.element_factory.lookup(id) if not element: - for diagram in self.element_factory.select(Diagram): - presentation: Element - for presentation in diagram.ownedPresentation: - if presentation.id == id: - return presentation raise ValueError(f"Element with id {id} not found in model") return element @@ -309,7 +304,7 @@ def undo_reversible_event(self, event: RevertibeEvent): element_id = event.element.id def b_undo_reversible_event(): - element = self.deep_lookup(element_id) + element = self.lookup(element_id) event.revert(element) b_undo_reversible_event.__doc__ = ( @@ -325,7 +320,7 @@ def undo_create_element_event(self, event: ElementCreated): element_id = event.element.id def d_undo_create_event(): - element = self.deep_lookup(element_id) + element = self.lookup(element_id) element.unlink() d_undo_create_event.__doc__ = f"Undo create element {event.element}." @@ -348,7 +343,7 @@ def save_func(name, value): event.element.save(save_func) def b_undo_delete_event(): - diagram: Diagram = self.deep_lookup(diagram_id) # type: ignore[assignment] + diagram: Diagram = self.lookup(diagram_id) # type: ignore[assignment] element = diagram.create_as(element_type, element_id) for name, ser in data.items(): for value in deserialize(ser, lambda ref: None): @@ -374,7 +369,7 @@ def undo_attribute_change_event(self, event: AttributeUpdated): value = event.old_value def c_undo_attribute_change_event(): - element = self.deep_lookup(element_id) + element = self.lookup(element_id) attribute._set(element, value) c_undo_attribute_change_event.__doc__ = ( @@ -393,8 +388,8 @@ def undo_association_set_event(self, event: AssociationSet): value_id = event.old_value and event.old_value.id def c_undo_association_set_event(): - element = self.deep_lookup(element_id) - value = value_id and self.deep_lookup(value_id) + element = self.lookup(element_id) + value = value_id and self.lookup(value_id) association._set(element, value, from_opposite=True) c_undo_association_set_event.__doc__ = ( @@ -413,8 +408,8 @@ def undo_association_add_event(self, event: AssociationAdded): value_id = event.new_value.id def c_undo_association_add_event(): - element = self.deep_lookup(element_id) - value = self.deep_lookup(value_id) + element = self.lookup(element_id) + value = self.lookup(value_id) association._del(element, value, from_opposite=True) c_undo_association_add_event.__doc__ = ( @@ -433,8 +428,8 @@ def undo_association_delete_event(self, event: AssociationDeleted): value_id = event.old_value.id def c_undo_association_delete_event(): - element = self.deep_lookup(element_id) - value = self.deep_lookup(value_id) + element = self.lookup(element_id) + value = self.lookup(value_id) association._set(element, value, from_opposite=True) c_undo_association_delete_event.__doc__ = (