Move presentation creation to element factory
Alongside all other model elements
This commit is contained in:
parent
bee26ddd32
commit
3466c2f2d2
@ -49,7 +49,7 @@ def test_association_item_connect(connected_association, element_factory):
|
||||
asc, c1, c2 = connected_association
|
||||
|
||||
# Diagram, Class *2, Property *2, Association
|
||||
assert len(element_factory.lselect()) == 6
|
||||
assert len(element_factory.lselect()) == 9
|
||||
assert asc.head_subject is not None
|
||||
assert asc.tail_subject is not None
|
||||
|
||||
|
@ -152,8 +152,16 @@ def test_copy_remove_paste_items_with_connections(diagram, element_factory):
|
||||
new_items = copy_clear_and_paste(
|
||||
{gen_cls_item, assoc_item, spc_cls_item}, diagram, element_factory
|
||||
)
|
||||
new_cls1 = next(element_factory.select(lambda e: e.name == "Gen"))
|
||||
new_cls2 = next(element_factory.select(lambda e: e.name == "Spc"))
|
||||
new_cls1 = next(
|
||||
element_factory.select(
|
||||
lambda e: isinstance(e, UML.NamedElement) and e.name == "Gen"
|
||||
)
|
||||
)
|
||||
new_cls2 = next(
|
||||
element_factory.select(
|
||||
lambda e: isinstance(e, UML.NamedElement) and e.name == "Spc"
|
||||
)
|
||||
)
|
||||
new_assoc = next(element_factory.select(UML.Association))
|
||||
|
||||
assert new_assoc.memberEnd[0].type is new_cls1
|
||||
|
@ -18,7 +18,10 @@ class UMLModelingLanguage(ModelingLanguage):
|
||||
return uml_toolbox_actions
|
||||
|
||||
def lookup_element(self, name):
|
||||
return getattr(uml, name, None)
|
||||
element_type = getattr(uml, name, None)
|
||||
if not element_type:
|
||||
element_type = self.lookup_diagram_item(name)
|
||||
return element_type
|
||||
|
||||
def lookup_diagram_item(self, name):
|
||||
return getattr(diagramitems, name, None)
|
||||
|
@ -38,8 +38,16 @@ def test_cut_paste_of_stereotype(diagram, element_factory):
|
||||
)
|
||||
|
||||
copy_clear_and_paste({m_cls_item, st_cls_item, ext_item}, diagram, element_factory)
|
||||
new_m_cls = next(element_factory.select(lambda e: e.name == "Class"))
|
||||
new_st_cls = next(element_factory.select(lambda e: e.name == "Stereotype"))
|
||||
new_m_cls = next(
|
||||
element_factory.select(
|
||||
lambda e: isinstance(e, UML.NamedElement) and e.name == "Class"
|
||||
)
|
||||
)
|
||||
new_st_cls = next(
|
||||
element_factory.select(
|
||||
lambda e: isinstance(e, UML.NamedElement) and e.name == "Stereotype"
|
||||
)
|
||||
)
|
||||
new_ext = next(element_factory.select(UML.Extension))
|
||||
|
||||
assert new_m_cls in new_ext.memberEnd[:].type
|
||||
|
@ -8,13 +8,14 @@ import logging
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from functools import lru_cache
|
||||
from typing import Iterable, Iterator, Optional, Sequence, Set, Union
|
||||
from typing import Iterable, Iterator, Optional, Sequence, Set, Type, TypeVar, Union
|
||||
|
||||
import gaphas
|
||||
from typing_extensions import Protocol, runtime_checkable
|
||||
|
||||
from gaphor.core.modeling.collection import collection
|
||||
from gaphor.core.modeling.element import Element, Id, RepositoryProtocol
|
||||
from gaphor.core.modeling.event import AssociationDeleted, ElementCreated
|
||||
from gaphor.core.modeling.event import AssociationDeleted
|
||||
from gaphor.core.modeling.presentation import Presentation
|
||||
from gaphor.core.modeling.properties import (
|
||||
association,
|
||||
@ -197,18 +198,6 @@ class StyledItem:
|
||||
)
|
||||
|
||||
|
||||
class PseudoCanvas:
|
||||
"""A "pseudo" canvas implementation, for storing items."""
|
||||
|
||||
def __init__(self, diagram: Diagram):
|
||||
self.diagram = diagram
|
||||
|
||||
def save(self, save_func):
|
||||
for item in self.diagram.ownedPresentation:
|
||||
if not item.parent:
|
||||
save_func(item)
|
||||
|
||||
|
||||
class Diagram(Element):
|
||||
"""Diagrams may contain model elements and can be owned by a Package."""
|
||||
|
||||
@ -252,7 +241,6 @@ class Diagram(Element):
|
||||
"""Apply the supplied save function to this diagram and the canvas."""
|
||||
|
||||
super().save(save_func)
|
||||
save_func("canvas", PseudoCanvas(self))
|
||||
|
||||
def postload(self):
|
||||
"""Handle post-load functionality for the diagram."""
|
||||
@ -270,17 +258,10 @@ class Diagram(Element):
|
||||
return self.create_as(type, str(uuid.uuid1()), parent, subject)
|
||||
|
||||
def create_as(self, type, id, parent=None, subject=None):
|
||||
if not (type and issubclass(type, Presentation)):
|
||||
raise TypeError(
|
||||
f"Type {type} can not be added to a diagram as it is not a diagram item"
|
||||
)
|
||||
# Avoid events that reference this element before its created-event is emitted.
|
||||
with self.model.block_events():
|
||||
item = type(diagram=self, id=id)
|
||||
assert isinstance(
|
||||
item, gaphas.Item
|
||||
), f"Type {type} does not comply with Item protocol"
|
||||
self.model.handle(ElementCreated(self.model, item, self))
|
||||
assert isinstance(self.model, PresentationRepositoryProtocol)
|
||||
item = self.model.create_as(type, id, diagram=self)
|
||||
if not isinstance(item, gaphas.Item):
|
||||
raise TypeError(f"Type {type} does not comply with Item protocol")
|
||||
if subject:
|
||||
item.subject = subject
|
||||
if parent:
|
||||
@ -388,3 +369,12 @@ class Diagram(Element):
|
||||
Presentation.diagram = association(
|
||||
"diagram", Diagram, upper=1, opposite="ownedPresentation"
|
||||
)
|
||||
|
||||
|
||||
P = TypeVar("P", bound=Presentation)
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class PresentationRepositoryProtocol(Protocol):
|
||||
def create_as(self, type: Type[P], id: str, diagram: Diagram) -> P:
|
||||
...
|
||||
|
@ -35,6 +35,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
T = TypeVar("T", bound=Element)
|
||||
P = TypeVar("P", bound=Presentation)
|
||||
|
||||
|
||||
class ElementFactory(Service):
|
||||
@ -66,18 +67,33 @@ class ElementFactory(Service):
|
||||
"""Create a new model element of type ``type``."""
|
||||
return self.create_as(type, str(uuid.uuid1()))
|
||||
|
||||
def create_as(self, type: Type[T], id: str) -> T:
|
||||
def create_as(self, type: Type[T], id: str, diagram: Diagram = None) -> T:
|
||||
"""Create a new model element of type 'type' with 'id' as its ID.
|
||||
|
||||
This method should only be used when loading models, since it
|
||||
does not emit an ElementCreated event.
|
||||
"""
|
||||
if not type or not issubclass(type, Element) or issubclass(type, Presentation):
|
||||
if not type:
|
||||
raise TypeError(f"Type {type} not defined")
|
||||
elif issubclass(type, Presentation):
|
||||
if not diagram:
|
||||
raise TypeError("Presentation types require a diagram")
|
||||
|
||||
# Avoid events that reference this element before its created-event is emitted.
|
||||
with self.block_events():
|
||||
item = type(diagram=diagram, id=id)
|
||||
self._elements[id] = item
|
||||
self.handle(ElementCreated(self, item, diagram))
|
||||
return item
|
||||
elif issubclass(type, Element):
|
||||
if diagram:
|
||||
raise TypeError("Element types require no diagram")
|
||||
obj = type(id, self)
|
||||
self._elements[id] = obj
|
||||
self.handle(ElementCreated(self, obj))
|
||||
return obj
|
||||
else:
|
||||
raise TypeError(f"Type {type} is not a valid model element")
|
||||
obj = type(id, self)
|
||||
self._elements[id] = obj
|
||||
self.handle(ElementCreated(self, obj))
|
||||
return obj
|
||||
|
||||
def size(self) -> int:
|
||||
"""Return the amount of elements currently in the factory."""
|
||||
@ -182,6 +198,8 @@ class ElementFactory(Service):
|
||||
del self._elements[element.id]
|
||||
except KeyError:
|
||||
return
|
||||
if isinstance(event.element, Presentation):
|
||||
return
|
||||
event = ElementDeleted(self, event.element)
|
||||
if self.event_manager and not self._block_events:
|
||||
self.event_manager.handle(event)
|
||||
|
@ -38,7 +38,7 @@ def test_canvas_is_saved():
|
||||
saved_keys = []
|
||||
diagram.save(lambda name, val: saved_keys.append(name))
|
||||
|
||||
assert "canvas" in saved_keys
|
||||
assert "canvas" not in saved_keys
|
||||
|
||||
|
||||
def test_canvas_item_is_created(element_factory):
|
||||
|
@ -212,7 +212,8 @@ def _paste_all(copy_data: CopyData, diagram, lookup) -> Set[Presentation]:
|
||||
return new_elements[ref]
|
||||
|
||||
looked_up = lookup(ref)
|
||||
if looked_up:
|
||||
# TODO: Presentation element should be created anyhow
|
||||
if looked_up and not isinstance(looked_up, Presentation):
|
||||
return looked_up
|
||||
|
||||
elif ref in copy_data.elements:
|
||||
|
@ -14,7 +14,7 @@ from functools import partial
|
||||
|
||||
from gaphor import application
|
||||
from gaphor.core.modeling.collection import collection
|
||||
from gaphor.core.modeling.diagram import Diagram, PseudoCanvas
|
||||
from gaphor.core.modeling.diagram import Diagram
|
||||
from gaphor.core.modeling.element import Element
|
||||
from gaphor.core.modeling.presentation import Presentation
|
||||
from gaphor.core.modeling.stylesheet import StyleSheet
|
||||
@ -137,10 +137,6 @@ def save_element(name, value, writer):
|
||||
save_reference(name, value)
|
||||
elif isinstance(value, collection):
|
||||
save_collection(name, value)
|
||||
elif isinstance(value, PseudoCanvas):
|
||||
writer.startElement("canvas", {})
|
||||
value.save(save_diagram)
|
||||
writer.endElement("canvas")
|
||||
else:
|
||||
save_value(name, value)
|
||||
|
||||
@ -184,6 +180,29 @@ def load_elements_generator(elements, factory, modeling_language, gaphor_version
|
||||
def _load_elements_and_canvasitems(
|
||||
elements, factory, modeling_language, gaphor_version, update_status_queue
|
||||
):
|
||||
def create_element(elem):
|
||||
if elem.element:
|
||||
return
|
||||
if version_lower_than(gaphor_version, (2, 1, 0)):
|
||||
elem = upgrade_element_owned_comment_to_comment(elem)
|
||||
if version_lower_than(gaphor_version, (2, 3, 0)):
|
||||
elem = upgrade_package_owned_classifier_to_owned_type(elem)
|
||||
elem = upgrade_implementation_to_interface_realization(elem)
|
||||
elem = upgrade_feature_parameters_to_owned_parameter(elem)
|
||||
elem = upgrade_parameter_owner_formal_param(elem)
|
||||
if version_lower_than(gaphor_version, (2, 5, 0)):
|
||||
elem = upgrade_diagram_element(elem)
|
||||
|
||||
cls = modeling_language.lookup_element(elem.type)
|
||||
assert cls, f"Type {elem.type} can not be loaded: no such element"
|
||||
if issubclass(cls, Presentation):
|
||||
diagram_id = elem.references["diagram"]
|
||||
diagram_elem = elements[diagram_id]
|
||||
create_element(diagram_elem)
|
||||
elem.element = factory.create_as(cls, id, diagram_elem.element)
|
||||
else:
|
||||
elem.element = factory.create_as(cls, id)
|
||||
|
||||
def create_canvasitems(diagram, canvasitems, parent=None):
|
||||
"""Diagram is a Core Diagram, items is a list of
|
||||
parser.canvasitem's."""
|
||||
@ -208,20 +227,10 @@ def _load_elements_and_canvasitems(
|
||||
for id, elem in list(elements.items()):
|
||||
yield from update_status_queue()
|
||||
if isinstance(elem, parser.element):
|
||||
if version_lower_than(gaphor_version, (2, 1, 0)):
|
||||
elem = upgrade_element_owned_comment_to_comment(elem)
|
||||
if version_lower_than(gaphor_version, (2, 3, 0)):
|
||||
elem = upgrade_package_owned_classifier_to_owned_type(elem)
|
||||
elem = upgrade_implementation_to_interface_realization(elem)
|
||||
elem = upgrade_feature_parameters_to_owned_parameter(elem)
|
||||
elem = upgrade_parameter_owner_formal_param(elem)
|
||||
if version_lower_than(gaphor_version, (2, 5, 0)):
|
||||
elem = upgrade_diagram_element(elem)
|
||||
|
||||
cls = modeling_language.lookup_element(elem.type)
|
||||
assert cls, f"Type {elem.type} can not be loaded: no such element"
|
||||
elem.element = factory.create_as(cls, id)
|
||||
if isinstance(elem.element, Diagram):
|
||||
create_element(elem)
|
||||
if version_lower_than(gaphor_version, (2, 5, 0)) and isinstance(
|
||||
elem.element, Diagram
|
||||
):
|
||||
assert elem.canvas
|
||||
create_canvasitems(elem.element, elem.canvas.canvasitems)
|
||||
elif not isinstance(elem, parser.canvasitem):
|
||||
|
@ -68,8 +68,8 @@ class TestStorage:
|
||||
|
||||
assert "<Diagram " in out.data
|
||||
assert "<Comment " in out.data
|
||||
assert "<canvas>" in out.data
|
||||
assert ' type="CommentItem"' in out.data, out.data
|
||||
assert "<canvas>" not in out.data
|
||||
assert "<CommentItem " in out.data, out.data
|
||||
|
||||
def test_load_uml(self, case):
|
||||
"""Test loading of a freshly saved model."""
|
||||
@ -101,7 +101,7 @@ class TestStorage:
|
||||
data = case.save()
|
||||
case.load(data)
|
||||
|
||||
assert len(case.element_factory.lselect()) == 6
|
||||
assert len(case.element_factory.lselect()) == 9
|
||||
assert len(case.element_factory.lselect(UML.Package)) == 1
|
||||
assert len(case.element_factory.lselect(UML.Diagram)) == 1
|
||||
d = case.element_factory.lselect(UML.Diagram)[0]
|
||||
@ -163,7 +163,7 @@ class TestStorage:
|
||||
data = case.save()
|
||||
case.load(data)
|
||||
|
||||
assert len(case.element_factory.lselect()) == 5
|
||||
assert len(case.element_factory.lselect()) == 8
|
||||
assert len(case.element_factory.lselect(UML.Package)) == 1
|
||||
assert len(case.element_factory.lselect(UML.Diagram)) == 1
|
||||
d = case.element_factory.lselect(UML.Diagram)[0]
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
from gaphor.core.modeling import Element
|
||||
from gaphor.core.modeling.collection import collection
|
||||
from gaphor.core.modeling.diagram import PseudoCanvas
|
||||
|
||||
|
||||
def orphan_references(factory):
|
||||
@ -37,8 +36,6 @@ def orphan_references(factory):
|
||||
verify_reference(name, value)
|
||||
elif isinstance(value, collection):
|
||||
verify_collection(name, value)
|
||||
elif isinstance(value, PseudoCanvas):
|
||||
value.save(verify_canvas)
|
||||
|
||||
def verify_canvas(value):
|
||||
elements.add(value.id)
|
||||
|
@ -32,7 +32,7 @@ def test_placement(diagram, page, element_factory):
|
||||
page.view.request_update([box])
|
||||
|
||||
diagram.create(CommentItem, subject=element_factory.create(Comment))
|
||||
assert len(element_factory.lselect()) == 2
|
||||
assert len(element_factory.lselect()) == 4
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "gaphor"
|
||||
version = "2.4.2"
|
||||
version = "2.5.0"
|
||||
description = "Gaphor is the simple modeling tool written in Python."
|
||||
authors = [
|
||||
"Arjan J. Molenaar <gaphor@gmail.com>",
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<gaphor xmlns="http://gaphor.sourceforge.net/model" version="3.0" gaphor-version="2.4.1">
|
||||
<gaphor xmlns="http://gaphor.sourceforge.net/model" version="3.0" gaphor-version="2.5.0">
|
||||
<Package id="DCE:658E43CC-3A1D-11DC-8B61-000D93868322">
|
||||
<name>
|
||||
<val>New model</val>
|
||||
@ -24,8 +24,8 @@
|
||||
<ref refid="DCE:68ECCFB2-3A1D-11DC-8B61-000D93868322"/>
|
||||
</reflist>
|
||||
</ownedPresentation>
|
||||
<canvas>
|
||||
<item id="DCE:6719E9C4-3A1D-11DC-8B61-000D93868322" type="Line">
|
||||
</Diagram>
|
||||
<Line id="DCE:6719E9C4-3A1D-11DC-8B61-000D93868322">
|
||||
<diagram>
|
||||
<ref refid="DCE:658E851A-3A1D-11DC-8B61-000D93868322"/>
|
||||
</diagram>
|
||||
@ -41,8 +41,8 @@
|
||||
<points>
|
||||
<val>[(0.0, 0.0), (93.0, -2.0), (87.0, 83.0)]</val>
|
||||
</points>
|
||||
</item>
|
||||
<item id="DCE:680E2142-3A1D-11DC-8B61-000D93868322" type="Box">
|
||||
</Line>
|
||||
<Box id="DCE:680E2142-3A1D-11DC-8B61-000D93868322">
|
||||
<matrix>
|
||||
<val>(1.0, 0.0, 0.0, 1.0, 215.0, 121.0)</val>
|
||||
</matrix>
|
||||
@ -55,8 +55,8 @@
|
||||
<diagram>
|
||||
<ref refid="DCE:658E851A-3A1D-11DC-8B61-000D93868322"/>
|
||||
</diagram>
|
||||
</item>
|
||||
<item id="DCE:68ECCFB2-3A1D-11DC-8B61-000D93868322" type="Ellipse">
|
||||
</Box>
|
||||
<Ellipse id="DCE:68ECCFB2-3A1D-11DC-8B61-000D93868322">
|
||||
<matrix>
|
||||
<val>(1.0, 0.0, 0.0, 1.0, 332.0, 137.0)</val>
|
||||
</matrix>
|
||||
@ -69,9 +69,7 @@
|
||||
<diagram>
|
||||
<ref refid="DCE:658E851A-3A1D-11DC-8B61-000D93868322"/>
|
||||
</diagram>
|
||||
</item>
|
||||
</canvas>
|
||||
</Diagram>
|
||||
</Ellipse>
|
||||
<StyleSheet id="d8c43f36-c545-11ea-9af2-f5ca580d221e">
|
||||
<styleSheet>
|
||||
<val>diagram {
|
||||
|
@ -92,7 +92,7 @@ def set_up_class_and_association(event_manager, element_factory):
|
||||
connect(a, a.tail, ci2)
|
||||
|
||||
# Diagram, Association, 2x Class, Property, LiteralSpecification
|
||||
assert 9 == len(element_factory.lselect())
|
||||
assert 12 == len(element_factory.lselect())
|
||||
assert 18 == len(diagram.connections.solver.constraints)
|
||||
|
||||
return diagram, ci1, ci2, a
|
||||
|
Loading…
Reference in New Issue
Block a user