diff --git a/examples/list_classes.py b/examples/list_classes.py index 0738b67f50..30812f6c59 100644 --- a/examples/list_classes.py +++ b/examples/list_classes.py @@ -8,7 +8,7 @@ import sys import gaphor.UML as UML -from gaphor.application import Application +from gaphor.application import Session # Setup command line options. usage = "usage: %prog [options] file.gaphor" @@ -33,7 +33,7 @@ model = args[0] # Create the Gaphor application object. -session = Application.new_session() +session = Session() # Get services we need. element_factory = session.get_service("element_factory") diff --git a/gaphor/UML/tests/test_properties.py b/gaphor/UML/tests/test_properties.py index 85742a474d..95143af607 100644 --- a/gaphor/UML/tests/test_properties.py +++ b/gaphor/UML/tests/test_properties.py @@ -1,6 +1,5 @@ import pytest -from gaphor.application import Application from gaphor.core import event_handler from gaphor.UML.element import Element from gaphor.UML.event import AssociationUpdated diff --git a/gaphor/application.py b/gaphor/application.py index dd0bd3f3dd..c75143901d 100644 --- a/gaphor/application.py +++ b/gaphor/application.py @@ -65,6 +65,9 @@ def __call__(self, appservices=None): assert not self._services_by_name uninitialized_services = load_services("gaphor.appservices", appservices) self._services_by_name = init_services(uninitialized_services, application=self) + + transaction.subscribers.add(self._transaction_proxy) + return self def new_session(self, services=None): @@ -74,6 +77,7 @@ def new_session(self, services=None): session = Session() self.sessions.add(session) self.active_session = session + return session def has_sessions(self): @@ -92,6 +96,8 @@ def shutdown(self): This is mainly for testing purposes. """ + transaction.subscribers.discard(self._transaction_proxy) + while self.sessions: self.shutdown_session(self.sessions.pop()) @@ -112,6 +118,7 @@ def quit(self): if self.active_session == session: logger.info("Window not closed, abort quit operation") return + self.shutdown() # def all(self, base: Type[T]) -> Iterator[Tuple[T, str]]: def all(self, base): @@ -119,6 +126,10 @@ def all(self, base): (n, c) for n, c in self._services_by_name.items() if isinstance(c, base) ) + def _transaction_proxy(self, event): + if self.active_session: + self.active_session.event_manager.handle(event) + class Session: """ @@ -141,12 +152,6 @@ def __init__(self, services=None): self.component_registry.register(name, srv) self.event_manager.handle(ServiceInitializedEvent(name, srv)) - transaction.subscribers.add(self._transaction_proxy) - - def _transaction_proxy(self, event): - if self is Application.active_session: - self.event_manager.handle(event) - def get_service(self, name): if not self.component_registry: raise NotInitializedError("Session is no longer alive") @@ -154,8 +159,6 @@ def get_service(self, name): return self.component_registry.get_service(name) def shutdown(self): - transaction.subscribers.discard(self._transaction_proxy) - if self.component_registry: for name, _srv in self.component_registry.all(Service): self.shutdown_service(name) diff --git a/gaphor/services/session.py b/gaphor/services/session.py index fc12acc565..aac923122c 100644 --- a/gaphor/services/session.py +++ b/gaphor/services/session.py @@ -1,5 +1,4 @@ from gaphor.abc import Service -from gaphor.application import Application class Session(Service): diff --git a/gaphor/transaction.txt b/gaphor/transaction.txt index e0194963a8..3c868008b9 100644 --- a/gaphor/transaction.txt +++ b/gaphor/transaction.txt @@ -8,7 +8,8 @@ Transaction support is located in module gaphor.transaction: Do some basic initialization, so event emission will work: - >>> session = Application.new_session(services=['event_manager']) + >>> application = Application() + >>> session = application.new_session(services=['event_manager']) >>> event_manager = session.get_service('event_manager') The Transaction class is used mainly to signal the begin and end of a transaction. This is done by the TransactionBegin, TransactionCommit and TransactionRollback events: @@ -115,4 +116,4 @@ All transactions are marked for rollback once an exception is raised: Cleanup: - >>> Application.shutdown() + >>> application.shutdown() diff --git a/gaphor/ui/__init__.py b/gaphor/ui/__init__.py index 971d68587a..93ba55dcd8 100644 --- a/gaphor/ui/__init__.py +++ b/gaphor/ui/__init__.py @@ -10,7 +10,7 @@ import gi -from gaphor.application import Application, Session, _Application +from gaphor.application import Application, _Application from gaphor.core import event_handler from gaphor.event import ActiveSessionChanged, SessionShutdown from gaphor.ui.actiongroup import apply_application_actions diff --git a/gaphor/ui/tests/test_diagrampage.py b/gaphor/ui/tests/test_diagrampage.py index bcb98899b7..013f10ed44 100644 --- a/gaphor/ui/tests/test_diagrampage.py +++ b/gaphor/ui/tests/test_diagrampage.py @@ -1,63 +1,75 @@ -import unittest - +import pytest from gaphas.examples import Box from gaphor import UML -from gaphor.application import Application +from gaphor.application import Session from gaphor.diagram.general.comment import CommentItem from gaphor.ui.mainwindow import DiagramPage -class DiagramPageTestCase(unittest.TestCase): - def setUp(self): - session = Application.new_session( - services=[ - "event_manager", - "component_registry", - "element_factory", - "main_window", - "properties", - "namespace", - "diagrams", - "toolbox", - "elementeditor", - "export_menu", - "tools_menu", - ] - ) - main_window = session.get_service("main_window") - main_window.open() - self.element_factory = session.get_service("element_factory") - self.diagram = self.element_factory.create(UML.Diagram) - self.page = DiagramPage( - self.diagram, - session.get_service("event_manager"), - self.element_factory, - session.get_service("properties"), - ) - self.page.construct() - assert self.page.diagram == self.diagram - assert self.page.view.canvas == self.diagram.canvas - assert len(self.element_factory.lselect()) == 1 - - def tearDown(self): - self.page.close() - del self.page - self.diagram.unlink() - del self.diagram - Application.shutdown() - assert len(self.element_factory.lselect()) == 0 - - def test_creation(self): - pass - - def test_placement(self): - box = Box() - self.diagram.canvas.add(box) - self.diagram.canvas.update_now() - self.page.view.request_update([box]) - - self.diagram.create( - CommentItem, subject=self.element_factory.create(UML.Comment) - ) - assert len(self.element_factory.lselect()) == 2 +@pytest.fixture +def session(): + session = Session( + services=[ + "event_manager", + "component_registry", + "element_factory", + "main_window", + "properties", + "namespace", + "diagrams", + "toolbox", + "elementeditor", + "export_menu", + "tools_menu", + ] + ) + yield session + session.shutdown() + + +@pytest.fixture +def main_window(session): + main_window = session.get_service("main_window") + main_window.open() + + +@pytest.fixture +def element_factory(session): + return session.get_service("element_factory") + + +@pytest.fixture +def diagram(element_factory): + diagram = element_factory.create(UML.Diagram) + yield diagram + diagram.unlink() + + +@pytest.fixture +def page(session, diagram, element_factory): + page = DiagramPage( + diagram, + session.get_service("event_manager"), + element_factory, + session.get_service("properties"), + ) + page.construct() + assert page.diagram == diagram + assert page.view.canvas == diagram.canvas + yield page + page.close() + + +def test_creation(page, element_factory): + assert len(element_factory.lselect()) == 1 + + +def test_placement(diagram, page, element_factory): + box = Box() + diagram.canvas.add(box) + diagram.canvas.update_now() + page.view.request_update([box]) + + diagram.create(CommentItem, subject=element_factory.create(UML.Comment)) + assert len(element_factory.lselect()) == 2 diff --git a/gaphor/ui/tests/test_handletool.py b/gaphor/ui/tests/test_handletool.py index 5535fef1f2..a7ee98805e 100644 --- a/gaphor/ui/tests/test_handletool.py +++ b/gaphor/ui/tests/test_handletool.py @@ -9,7 +9,7 @@ from gi.repository import Gdk, Gtk from gaphor import UML -from gaphor.application import Application +from gaphor.application import Session from gaphor.diagram.connectors import IConnect from gaphor.diagram.diagramtools import ConnectHandleTool, DiagramItemConnector from gaphor.diagram.general.comment import CommentItem @@ -21,7 +21,7 @@ @pytest.fixture def session(): - session = Application.new_session( + session = Session( services=[ "event_manager", "component_registry", @@ -53,6 +53,12 @@ def event_manager(session): return session.get_service("event_manager") +@pytest.fixture +def main_window(session): + main_window = session.get_service("main_window") + yield main_window + + @pytest.fixture def diagram(element_factory): return element_factory.create(UML.Diagram) @@ -95,232 +101,112 @@ def test_connect(diagram, comment, commentline): assert cinfo, cinfo -class HandleToolTestCase(unittest.TestCase): +def current_diagram_view(session): + """ + Get a view for the current diagram. """ - Handle connection tool integration tests. + component_registry = session.get_service("component_registry") + view = component_registry.get(UIComponent, "diagrams").get_current_view() + + # realize view, forces bounding box recalculation + while Gtk.events_pending(): + Gtk.main_iteration() + + return view + + +def test_iconnect(session, event_manager, element_factory): + """ + Test basic glue functionality using CommentItem and CommentLine + items. + """ + diagram = element_factory.create(UML.Diagram) + event_manager.handle(DiagramOpened(diagram)) + comment = diagram.create(CommentItem, subject=element_factory.create(UML.Comment)) + + actor = diagram.create(ActorItem, subject=element_factory.create(UML.Actor)) + actor.matrix.translate(200, 200) + diagram.canvas.update_matrix(actor) + + line = diagram.create(CommentLineItem) + + view = current_diagram_view(session) + assert view, "View should be available here" + comment_bb = view.get_item_bounding_box(comment) + + # select handle: + handle = line.handles()[-1] + tool = ConnectHandleTool(view=view) + + tool.grab_handle(line, handle) + handle.pos = (comment_bb.x, comment_bb.y) + item = tool.glue(line, handle, handle.pos) + assert item is not None + + tool.connect(line, handle, handle.pos) + cinfo = diagram.canvas.get_connection(handle) + assert cinfo.constraint is not None + assert cinfo.connected is actor, cinfo.connected + + Connector(line, handle).disconnect() + + cinfo = diagram.canvas.get_connection(handle) + + assert cinfo is None + + +def test_connect_comment_and_actor(session, event_manager, element_factory): + """Test connect/disconnect on comment and actor using comment-line. """ + diagram = element_factory.create(UML.Diagram) + event_manager.handle(DiagramOpened(diagram)) + comment = diagram.create(CommentItem, subject=element_factory.create(UML.Comment)) + + line = diagram.create(CommentLineItem) + + view = current_diagram_view(session) + assert view, "View should be available here" + + tool = ConnectHandleTool(view) + + # Connect one end to the Comment: + handle = line.handles()[0] + tool.grab_handle(line, handle) + + handle.pos = (0, 0) + sink = tool.glue(line, handle, handle.pos) + assert sink is not None + assert sink.item is comment + + tool.connect(line, handle, handle.pos) + cinfo = diagram.canvas.get_connection(handle) + assert cinfo is not None, None + assert cinfo.item is line + assert cinfo.connected is comment + + # Connect the other end to the Actor: + actor = diagram.create(ActorItem, subject=element_factory.create(UML.Actor)) + + handle = line.handles()[-1] + tool.grab_handle(line, handle) + + handle.pos = (0, 0) + sink = tool.glue(line, handle, handle.pos) + assert sink, f"No sink at {handle.pos}" + assert sink.item is actor + tool.connect(line, handle, handle.pos) + + cinfo = view.canvas.get_connection(handle) + assert cinfo.item is line + assert cinfo.connected is actor + + # Try to connect far away from any item will only do a full disconnect + assert len(comment.subject.annotatedElement) == 1, comment.subject.annotatedElement + assert actor.subject in comment.subject.annotatedElement + + sink = tool.glue(line, handle, (500, 500)) + assert sink is None, sink + tool.connect(line, handle, (500, 500)) - def setUp(self): - self.session = Application.new_session( - services=[ - "event_manager", - "component_registry", - "element_factory", - "main_window", - "properties_manager", - "properties", - "namespace", - "diagrams", - "toolbox", - "elementeditor", - "export_menu", - "tools_menu", - ] - ) - self.component_registry = self.session.get_service("component_registry") - self.event_manager = self.session.get_service("event_manager") - - self.main_window = self.session.get_service("main_window") - self.main_window.open() - - def shutDown(self): - Application.shutdown() - - def get_diagram_view(self, diagram): - """ - Get a view for diagram. - """ - view = self.component_registry.get(UIComponent, "diagrams").get_current_view() - - # realize view, forces bounding box recalculation - while Gtk.events_pending(): - Gtk.main_iteration() - - return view - - def test_iconnect(self): - """ - Test basic glue functionality using CommentItem and CommentLine - items. - """ - element_factory = self.session.get_service("element_factory") - diagram = element_factory.create(UML.Diagram) - self.event_manager.handle(DiagramOpened(diagram)) - comment = diagram.create( - CommentItem, subject=element_factory.create(UML.Comment) - ) - - actor = diagram.create(ActorItem, subject=element_factory.create(UML.Actor)) - actor.matrix.translate(200, 200) - diagram.canvas.update_matrix(actor) - - line = diagram.create(CommentLineItem) - - view = self.get_diagram_view(diagram) - assert view, "View should be available here" - comment_bb = view.get_item_bounding_box(comment) - - # select handle: - handle = line.handles()[-1] - tool = ConnectHandleTool(view=view) - - tool.grab_handle(line, handle) - handle.pos = (comment_bb.x, comment_bb.y) - item = tool.glue(line, handle, handle.pos) - assert item is not None - - tool.connect(line, handle, handle.pos) - cinfo = diagram.canvas.get_connection(handle) - assert cinfo.constraint is not None - assert cinfo.connected is actor, cinfo.connected - - Connector(line, handle).disconnect() - - cinfo = diagram.canvas.get_connection(handle) - - assert cinfo is None - - def test_connect_comment_and_actor(self): - """Test connect/disconnect on comment and actor using comment-line. - """ - element_factory = self.session.get_service("element_factory") - diagram = element_factory.create(UML.Diagram) - self.event_manager.handle(DiagramOpened(diagram)) - comment = diagram.create( - CommentItem, subject=element_factory.create(UML.Comment) - ) - - line = diagram.create(CommentLineItem) - - view = self.get_diagram_view(diagram) - assert view, "View should be available here" - - tool = ConnectHandleTool(view) - - # Connect one end to the Comment: - handle = line.handles()[0] - tool.grab_handle(line, handle) - - handle.pos = (0, 0) - sink = tool.glue(line, handle, handle.pos) - assert sink is not None - assert sink.item is comment - - tool.connect(line, handle, handle.pos) - cinfo = diagram.canvas.get_connection(handle) - assert cinfo is not None, None - assert cinfo.item is line - assert cinfo.connected is comment - - # Connect the other end to the Actor: - actor = diagram.create(ActorItem, subject=element_factory.create(UML.Actor)) - - handle = line.handles()[-1] - tool.grab_handle(line, handle) - - handle.pos = (0, 0) - sink = tool.glue(line, handle, handle.pos) - assert sink, f"No sink at {handle.pos}" - assert sink.item is actor - tool.connect(line, handle, handle.pos) - - cinfo = view.canvas.get_connection(handle) - assert cinfo.item is line - assert cinfo.connected is actor - - # Try to connect far away from any item will only do a full disconnect - self.assertEqual( - len(comment.subject.annotatedElement), 1, comment.subject.annotatedElement - ) - assert actor.subject in comment.subject.annotatedElement - - sink = tool.glue(line, handle, (500, 500)) - assert sink is None, sink - tool.connect(line, handle, (500, 500)) - - cinfo = view.canvas.get_connection(handle) - assert cinfo is None - - def skiptest_connect_3(self): - """Test connecting through events (button press/release, motion). - """ - element_factory = self.session.get_service("element_factory") - diagram = element_factory.create(UML.Diagram) - - comment = diagram.create( - CommentItem, subject=element_factory.create(UML.Comment) - ) - # self.assertEqual(30, comment.height) - # self.assertEqual(100, comment.width) - - actor = diagram.create(ActorItem, subject=element_factory.create(UML.Actor)) - actor.matrix.translate(200, 200) - diagram.canvas.update_matrix(actor) - # assert actor.height == 60, actor.height - # assert actor.width == 38, actor.width - - line = diagram.create(CommentLineItem) - assert line.handles()[0].pos, (0.0, 0.0) - assert line.handles()[-1].pos, (10.0, 10.0) - - view = self.get_diagram_view(diagram) - assert view, "View should be available here" - - tool = ConnectHandleTool(view) - - tool.on_button_press(Gdk.Event(x=0, y=0, state=0)) - tool.on_button_release(Gdk.Event(x=0, y=0, state=0)) - - handle = line.handles()[0] - assert (0.0, 0.0) == view.canvas.get_matrix_i2c(line).transform_point( - *handle.pos - ) - cinfo = diagram.canvas.get_connection(handle) - assert cinfo.connected is comment - # self.assertTrue(handle.connected_to is comment, 'c = ' + str(handle.connected_to)) - # self.assertTrue(handle.connection_data is not None) - - # Grab the second handle and drag it to the actor - - tool.on_button_press(Gdk.Event(x=10, y=10, state=0)) - tool.on_motion_notify(Gdk.Event(x=200, y=200, state=0xFFFF)) - tool.on_button_release(Gdk.Event(x=200, y=200, state=0)) - - handle = line.handles()[-1] - assert (200, 200) == view.canvas.get_matrix_i2c(line).transform_point( - handle.x, handle.y - ) - cinfo = diagram.canvas.get_connection(handle) - assert cinfo.connected is actor - # self.assertTrue(handle.connection_data is not None) - self.assertTrue(actor.subject in comment.subject.annotatedElement) - - # Press, release, nothing should change - - tool.on_button_press(Gdk.Event(x=200, y=200, state=0)) - tool.on_motion_notify(Gdk.Event(x=200, y=200, state=0xFFFF)) - tool.on_button_release(Gdk.Event(x=200, y=200, state=0)) - - handle = line.handles()[-1] - assert (200, 200) == view.canvas.get_matrix_i2c(line).transform_point( - handle.x, handle.y - ) - cinfo = diagram.canvas.get_connection(handle) - assert cinfo.connected is actor - # self.assertTrue(handle.connection_data is not None) - self.assertTrue(actor.subject in comment.subject.annotatedElement) - - # Move second handle away from the actor. Should remove connection - - tool.on_button_press(Gdk.Event(x=200, y=200, state=0)) - tool.on_motion_notify(Gdk.Event(x=500, y=500, state=0xFFFF)) - tool.on_button_release(Gdk.Event(x=500, y=500, state=0)) - - handle = line.handles()[-1] - assert (500, 500) == view.canvas.get_matrix_i2c(line).transform_point( - handle.x, handle.y - ) - cinfo = diagram.canvas.get_connection(handle) - assert cinfo is None - # self.assertTrue(handle.connection_data is None) - self.assertEqual(len(comment.subject.annotatedElement), 0) + cinfo = view.canvas.get_connection(handle) + assert cinfo is None diff --git a/gaphor/ui/tests/test_lifecycle.py b/gaphor/ui/tests/test_lifecycle.py index fa97e55f80..85405b04a3 100644 --- a/gaphor/ui/tests/test_lifecycle.py +++ b/gaphor/ui/tests/test_lifecycle.py @@ -15,8 +15,9 @@ def quit(self): @pytest.fixture def application(): - yield Application - Application.shutdown() + application = Application() + yield application + application.shutdown() def two_sessions(application, gtk_app=GtkApplicationStub()): diff --git a/tests/test_issue_53.py b/tests/test_issue_53.py index 25e93f9a5b..0afaf25530 100644 --- a/tests/test_issue_53.py +++ b/tests/test_issue_53.py @@ -1,16 +1,15 @@ import pytest from gaphor import UML -from gaphor.application import Application, distribution +from gaphor.application import Session, distribution from gaphor.storage.storage import load @pytest.fixture def session(): - application = Application() - session = application.new_session() + session = Session() yield session - application.shutdown() + session.shutdown() @pytest.fixture