What if UI components are just services

Management of services can be arranged centrally and DI works without a hassle.
This commit is contained in:
Arjan Molenaar
2019-06-03 21:47:01 +02:00
parent e5fd6797cd
commit 6048cf331f
14 changed files with 111 additions and 78 deletions

View File

@ -1,7 +1,8 @@
import abc
from gaphor.abc import Service
class UIComponent(metaclass=abc.ABCMeta):
class UIComponent(Service):
"""
A user interface component.
"""
@ -26,3 +27,9 @@ class UIComponent(metaclass=abc.ABCMeta):
Close the UI component. The component can decide to hide or destroy the UI
components.
"""
def shutdown(self):
"""
Shut down this component. It's not supposed to be opened again.
"""
self.close()

View File

@ -18,9 +18,6 @@ log = logging.getLogger(__name__)
class ConsoleWindow(UIComponent, ActionProvider):
component_registry = inject("component_registry")
main_window = inject("main_window")
menu_xml = """
<ui>
<menubar name="mainwindow">
@ -35,7 +32,9 @@ class ConsoleWindow(UIComponent, ActionProvider):
size = (400, 400)
placement = "floating"
def __init__(self):
def __init__(self, component_registry, main_window):
self.component_registry = component_registry
self.main_window = main_window
self.action_group = build_action_group(self)
self.window = None
@ -63,8 +62,9 @@ class ConsoleWindow(UIComponent, ActionProvider):
@action(name="ConsoleWindow:close", stock_id="gtk-close", accel="<Primary><Shift>w")
def close(self, widget=None):
self.window.destroy()
self.window = None
if self.window:
self.window.destroy()
self.window = None
def construct(self):
window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
@ -93,6 +93,3 @@ class ConsoleWindow(UIComponent, ActionProvider):
window.connect("destroy", self.close)
return console
# vim:sw=4:et:ai

View File

@ -20,10 +20,6 @@ class ElementEditor(UIComponent, ActionProvider):
It will display the properties of the currently selected element in the
diagram."""
element_factory = inject("element_factory")
main_window = inject("main_window")
event_manager = inject("event_manager")
title = _("Element Editor")
size = (275, -1)
resizable = True
@ -39,11 +35,13 @@ class ElementEditor(UIComponent, ActionProvider):
</ui>
"""
def __init__(self):
def __init__(self, event_manager, element_factory, main_window):
"""Constructor. Build the action group for the element editor window.
This will place a button for opening the window in the toolbar.
The widget attribute is a PropertyEditor."""
self.event_manager = event_manager
self.element_factory = element_factory
self.main_window = main_window
self.action_group = build_action_group(self)
self.window = None
self.vbox = None
@ -90,7 +88,7 @@ class ElementEditor(UIComponent, ActionProvider):
window.connect("destroy", self.close)
def close(self, _widget=None):
def close(self):
"""Hide the element editor window and deactivate the toolbar button.
Both the widget and event parameters default to None and are
idempotent if set."""

View File

@ -91,19 +91,6 @@ class MainWindow(Service, ActionProvider):
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
)
def init_ui_components(self):
component_registry = self.component_registry
for ep in pkg_resources.iter_entry_points("gaphor.uicomponents"):
log.debug("found entry point uicomponent.%s" % ep.name)
cls = ep.load()
if not issubclass(cls, UIComponent):
raise NameError("Entry point %s doesn't provide UIComponent" % ep.name)
uicomp = cls()
uicomp.ui_name = ep.name
component_registry.register(uicomp, ep.name)
if isinstance(uicomp, ActionProvider):
self.action_manager.register_action_provider(uicomp)
def shutdown(self):
log.info("Shutting down")
if self.window:
@ -140,7 +127,6 @@ class MainWindow(Service, ActionProvider):
"""Open the main window.
"""
load_accel_map()
self.init_ui_components()
self.window = (
Gtk.ApplicationWindow.new(gtk_app)
@ -284,11 +270,6 @@ class Diagrams(UIComponent, ActionProvider):
title = _("Diagrams")
placement = ("left", "diagrams")
event_manager = inject("event_manager")
element_factory = inject("element_factory")
properties = inject("properties")
action_manager = inject("action_manager")
menu_xml = """
<ui>
<menubar name="mainwindow">
@ -301,7 +282,11 @@ class Diagrams(UIComponent, ActionProvider):
</ui>
"""
def __init__(self):
def __init__(self, event_manager, element_factory, action_manager, properties):
self.event_manager = event_manager
self.element_factory = element_factory
self.action_manager = action_manager
self.properties = properties
self._notebook = None
self.action_group = build_action_group(self)
self.action_group.get_action("diagram-drawing-style").set_active(
@ -330,8 +315,9 @@ class Diagrams(UIComponent, ActionProvider):
self.event_manager.unsubscribe(self._on_flush_model)
self.event_manager.unsubscribe(self._on_name_change)
self.event_manager.unsubscribe(self._on_show_diagram)
self._notebook.destroy()
self._notebook = None
if self._notebook:
self._notebook.destroy()
self._notebook = None
def get_current_diagram(self):
"""Returns the current page of the notebook.

View File

@ -278,10 +278,6 @@ class Namespace(UIComponent, ActionProvider):
title = _("Namespace")
placement = ("left", "diagrams")
event_manager = inject("event_manager")
element_factory = inject("element_factory")
action_manager = inject("action_manager")
menu_xml = """
<ui>
<menubar name="mainwindow">
@ -308,7 +304,10 @@ class Namespace(UIComponent, ActionProvider):
</ui>
"""
def __init__(self):
def __init__(self, event_manager, element_factory, action_manager):
self.event_manager = event_manager
self.element_factory = element_factory
self.action_manager = action_manager
self._namespace = None
self.action_group = build_action_group(self)
self.model = Gtk.TreeStore.new([object])

View File

@ -1,17 +1,30 @@
import pytest
from gaphor.ui.consolewindow import ConsoleWindow
from gaphor.tests.testcase import TestCase
import gaphor.services.componentregistry
class ConsoleWindowTestCase(TestCase):
class MainWindowStub:
def __init__(self):
self.window = None
services = TestCase.services + ["main_window", "action_manager", "properties"]
def test1(self):
from gi.repository import Gtk
@pytest.fixture
def component_registry():
return gaphor.services.componentregistry.ComponentRegistry()
window = ConsoleWindow()
assert (
len(window.action_group.list_actions()) == 2
), window.action_group.list_actions()
window.open()
window.close()
@pytest.fixture
def main_window():
return MainWindowStub()
def test_open_close(component_registry, main_window):
window = ConsoleWindow(component_registry, main_window)
window.open()
window.close()
assert (
len(window.action_group.list_actions()) == 2
), window.action_group.list_actions()

View File

@ -11,7 +11,15 @@ from gaphor.ui.mainwindow import DiagramPage
class DiagramPageTestCase(unittest.TestCase):
def setUp(self):
Application.init(
services=["element_factory", "main_window", "action_manager", "properties"]
services=[
"element_factory",
"main_window",
"action_manager",
"properties",
"namespace",
"diagrams",
"toolbox",
]
)
main_window = Application.get_service("main_window")
main_window.open()

View File

@ -13,7 +13,14 @@ logging.basicConfig(level=logging.DEBUG)
class DiagramItemConnectorTestCase(TestCase):
services = TestCase.services + ["main_window", "action_manager", "properties"]
services = TestCase.services + [
"main_window",
"action_manager",
"properties",
"namespace",
"diagrams",
"toolbox",
]
def setUp(self):
super(DiagramItemConnectorTestCase, self).setUp()

View File

@ -28,6 +28,9 @@ class DiagramItemConnectorTestCase(unittest.TestCase):
"properties_manager",
"action_manager",
"properties",
"namespace",
"diagrams",
"toolbox",
]
)
self.main_window = Application.get_service("main_window")
@ -76,6 +79,9 @@ class HandleToolTestCase(unittest.TestCase):
"properties_manager",
"action_manager",
"properties",
"namespace",
"diagrams",
"toolbox",
]
)
self.component_registry = Application.get_service("component_registry")

View File

@ -10,7 +10,15 @@ from gaphor.ui.abc import UIComponent
class MainWindowTestCase(unittest.TestCase):
def setUp(self):
Application.init(
services=["element_factory", "properties", "main_window", "action_manager"]
services=[
"element_factory",
"properties",
"main_window",
"action_manager",
"namespace",
"diagrams",
"toolbox",
]
)
self.component_registry = Application.get_service("component_registry")

View File

@ -1,29 +1,36 @@
import pytest
import gaphor.UML as UML
from gaphor.ui.namespace import Namespace
from gaphor.application import Application
import gaphor.services.eventmanager
import gaphor.services.componentregistry
import gaphor.services.actionmanager
@pytest.fixture
def application(services=["element_factory", "action_manager"]):
Application.init(services=services)
yield Application
Application.shutdown()
def event_manager():
return gaphor.services.eventmanager.EventManager()
@pytest.fixture
def element_factory(application):
return application.get_service("element_factory")
def element_factory(event_manager):
return UML.elementfactory.ElementFactory(event_manager)
@pytest.fixture
def component_registry(application):
return application.get_service("component_registry")
def component_registry():
return gaphor.services.componentregistry.ComponentRegistry()
@pytest.fixture
def namespace(application):
namespace = Namespace()
def action_manager(event_manager, component_registry):
return gaphor.services.actionmanager.ActionManager(
event_manager, component_registry
)
@pytest.fixture
def namespace(event_manager, element_factory, action_manager):
namespace = Namespace(event_manager, element_factory, action_manager)
namespace.init()
yield namespace
namespace.close()

View File

@ -32,10 +32,6 @@ class Toolbox(UIComponent, ActionProvider):
title = _("Toolbox")
placement = ("left", "diagrams")
event_manager = inject("event_manager")
main_window = inject("main_window")
properties = inject("properties")
menu_xml = """
<ui>
<menubar name="mainwindow">
@ -48,7 +44,12 @@ class Toolbox(UIComponent, ActionProvider):
</ui>
"""
def __init__(self, toolbox_actions=TOOLBOX_ACTIONS):
def __init__(
self, event_manager, main_window, properties, toolbox_actions=TOOLBOX_ACTIONS
):
self.event_manager = event_manager
self.main_window = main_window
self.properties = properties
self._toolbox = None
self._toolbox_actions = toolbox_actions
self.action_group = build_action_group(self)

View File

@ -67,8 +67,6 @@ gaphorconvert = 'gaphor.tools.gaphorconvert:main'
"pynsource" = "gaphor.plugins.pynsource:PyNSource"
"alignment" = "gaphor.plugins.alignment:Alignment"
"help" = "gaphor.services.helpservice:HelpService"
[tool.poetry.plugins."gaphor.uicomponents"]
"namespace" = "gaphor.ui.mainwindow:Namespace"
"toolbox" = "gaphor.ui.mainwindow:Toolbox"
"diagrams" = "gaphor.ui.mainwindow:Diagrams"

View File

@ -118,8 +118,6 @@ setup(
"pynsource = gaphor.plugins.pynsource:PyNSource",
"alignment = gaphor.plugins.alignment:Alignment",
"help = gaphor.services.helpservice:HelpService",
],
"gaphor.uicomponents": [
"namespace = gaphor.ui.mainwindow:Namespace",
"toolbox = gaphor.ui.mainwindow:Toolbox",
"diagrams = gaphor.ui.mainwindow:Diagrams",