class ArtifactItem(ElementPresentation, Classified): def __init__(self, id=None, model=None): super().__init__(id, model) self.watch("show_stereotypes", self.update_shapes) self.watch("subject[NamedElement].name") self.watch("subject.appliedStereotype", self.update_shapes) self.watch("subject.appliedStereotype.classifier.name") self.watch("subject.appliedStereotype.slot", self.update_shapes) self.watch("subject.appliedStereotype.slot.definingFeature.name") self.watch("subject.appliedStereotype.slot.value", self.update_shapes) show_stereotypes: attribute[int] = attribute("show_stereotypes", int) def update_shapes(self, event=None): self.shape = Box( Box( Text(text=lambda: UML.model.stereotypes_str(self.subject),), EditableText( text=lambda: self.subject.name or "", style={"font-weight": FontWeight.BOLD}, ), style={"padding": (4, 34, 4, 4), "min-height": 44}, draw=draw_artifact_icon, ), *(self.show_stereotypes and stereotype_compartments(self.subject) or []), style={ "min-width": 100, "min-height": 50, "vertical-align": VerticalAlign.TOP, }, draw=draw_border )
class ComponentItem(ElementPresentation, Classified): def __init__(self, diagram, id=None): super().__init__(diagram, id) self.watch("show_stereotypes", self.update_shapes) self.watch("subject[NamedElement].name") self.watch("subject.appliedStereotype", self.update_shapes) self.watch("subject.appliedStereotype.classifier.name") self.watch("subject.appliedStereotype.slot", self.update_shapes) self.watch("subject.appliedStereotype.slot.definingFeature.name") self.watch("subject.appliedStereotype.slot.value", self.update_shapes) self.watch("subject[Classifier].useCase", self.update_shapes) show_stereotypes: attribute[int] = attribute("show_stereotypes", int) def update_shapes(self, event=None): self.shape = Box( Box( Text(text=lambda: UML.model.stereotypes_str(self.subject), ), EditableText( text=lambda: self.subject.name or "", style={"font-weight": FontWeight.BOLD}, ), style={"padding": (4, 32, 4, 4)}, draw=draw_component_icon, ), *(self.show_stereotypes and stereotype_compartments(self.subject) or []), style={ "vertical-align": VerticalAlign.TOP if self.diagram and self.children else VerticalAlign.MIDDLE, }, draw=draw_border)
class StyleSheet(Element): _compiled_style_sheet: CompiledStyleSheet def __init__(self, id=None, model=None): super().__init__(id, model) self.compile_style_sheet() styleSheet: attribute[str] = attribute("styleSheet", str, "") def compile_style_sheet(self) -> None: self._compiled_style_sheet = CompiledStyleSheet(self.styleSheet) def match(self, node: StyleNode) -> Style: return self._compiled_style_sheet.match(node) def postload(self): super().postload() self.compile_style_sheet() def handle(self, event): # Ensure compiled style sheet is always up to date: if (isinstance(event, AttributeUpdated) and event.property is StyleSheet.styleSheet): self.compile_style_sheet() super().handle(event)
class StyleSheet(Element): def __init__(self, id=None, model=None): super().__init__(id, model) self._style = read_style_py() styleSheet: attribute[str] = attribute("styleSheet", str) def item_style(self, item): return self._style
class NodeItem(ElementPresentation, Classified): """ Representation of node or device from UML Deployment package. """ def __init__(self, id=None, model=None): super().__init__(id, model) self.watch("show_stereotypes", self.update_shapes) self.watch("subject[NamedElement].name") self.watch("subject.appliedStereotype", self.update_shapes) self.watch("subject.appliedStereotype.classifier.name") self.watch("subject.appliedStereotype.slot", self.update_shapes) self.watch("subject.appliedStereotype.slot.definingFeature.name") self.watch("subject.appliedStereotype.slot.value", self.update_shapes) self.watch("subject[Node].ownedConnector", self.update_shapes) self.watch("subject[Node].deployment", self.update_shapes) show_stereotypes: attribute[int] = attribute("show_stereotypes", int) def update_shapes(self, event=None): self.shape = Box( Box( Text( text=lambda: UML.model.stereotypes_str( self.subject, isinstance(self.subject, UML.Device) and ("device", ) or (), ), style={ "min-width": 0, "min-height": 0 }, ), EditableText( text=lambda: self.subject.name or "", style={"font-weight": FontWeight.BOLD}, ), style={"padding": (4, 4, 4, 4)}, ), *(self.show_stereotypes and stereotype_compartments(self.subject) or []), style={ "min-width": 100, "min-height": 50, "vertical-align": VerticalAlign.TOP if self.canvas and self.canvas.get_children(self) else VerticalAlign.MIDDLE, }, draw=draw_node) def postload(self): self.update_shapes() super().postload()
class PropertyItem(ElementPresentation[UML.Property], Named): def __init__(self, diagram, id=None): super().__init__(diagram, id) self.watch("show_stereotypes", self.update_shapes) self.watch("subject[Property].name") self.watch("subject[Property].type.name") self.watch("subject[Property].lowerValue") self.watch("subject[Property].upperValue") self.watch("subject.appliedStereotype", self.update_shapes) self.watch("subject.appliedStereotype.classifier.name") self.watch("subject.appliedStereotype.slot", self.update_shapes) self.watch("subject.appliedStereotype.slot.definingFeature.name") self.watch("subject.appliedStereotype.slot.value", self.update_shapes) self.watch("subject[Property].aggregation", self.update_shapes) show_stereotypes: attribute[int] = attribute("show_stereotypes", int) def alignment(self) -> VerticalAlign: if self.diagram and self.children: return VerticalAlign.TOP else: return VerticalAlign.MIDDLE def dash(self) -> Sequence[Union[int, float]]: if self.subject and self.subject.aggregation != "composite": return (7.0, 5.0) else: return () def update_shapes(self, event=None): self.shape = Box( Box( Text( text=lambda: UML.model.stereotypes_str(self.subject), ), EditableText( text=lambda: format_property( self.subject, type=True, multiplicity=True ) or "", style={"font-weight": FontWeight.BOLD}, ), style={"padding": (12, 4, 12, 4), "min-height": 44}, ), *(self.show_stereotypes and stereotype_compartments(self.subject) or []), style={ "min-width": 100, "min-height": 50, "vertical-align": self.alignment(), "dash-style": self.dash(), }, draw=draw_border )
def test_attributes(): class A(Element): a: attribute[str] A.a = attribute("a", str, "default") a = A() assert a.a == "default", a.a a.a = "bar" assert a.a == "bar", a.a del a.a assert a.a == "default" try: a.a = 1 except AttributeError: pass # ok else: assert 0, "should not set integer"
class ObjectNodeItem(ElementPresentation, Named): """Representation of object node. Object node is ordered and has upper bound specification. Ordering information can be hidden by user. """ def __init__(self, diagram, id=None): super().__init__( diagram, id, shape=IconBox( Box( Text(text=lambda: stereotypes_str(self.subject), ), EditableText(text=lambda: self.subject.name or ""), style={ "min-width": 50, "min-height": 30, "padding": (5, 10, 5, 10), }, draw=draw_border, ), Text(text=lambda: self.subject.upperBound not in (None, "", DEFAULT_UPPER_BOUND) and f"{{ upperBound = {self.subject.upperBound} }}" or "", ), Text(text=lambda: self.show_ordering and self.subject.ordering and f"{{ ordering = {self.subject.ordering} }}" or "", ), ), ) self.watch("subject[NamedElement].name") self.watch("subject.appliedStereotype.classifier.name") self.watch("subject[ObjectNode].upperBound") self.watch("subject[ObjectNode].ordering") self.watch("show_ordering") show_ordering: attribute[int] = attribute("show_ordering", int, default=False) def load(self, name, value): if name == "show-ordering": name = "show_ordering" super().load(name, value)
def test_derivedunion_listmixins(): class A(Element): a: relation_many[A] b: relation_many[A] u: relation_many[A] name: attribute[str] A.a = association("a", A) A.b = association("b", A) A.u = derivedunion("u", A, 0, "*", A.a, A.b) A.name = attribute("name", str, "default") a = A() a.a = A() a.a = A() a.b = A() a.a[0].name = "foo" a.a[1].name = "bar" a.b[0].name = "baz" assert list(a.a[:].name) == ["foo", "bar"] assert sorted(list(a.u[:].name)) == ["bar", "baz", "foo"]
pass class View(Class): pass class Viewpoint(Class): concernList: relation_many[Comment] language: attribute[str] presentation: attribute[str] purpose: attribute[str] stakeholder: relation_many[Stakeholder] Heritage.isSubstitutable = attribute("isSubstitutable", int) ObjectNode.ordering = enumeration("ordering", ("unordered", "ordered", "LIFO", "FIFO"), "FIFO") Heritage.general = association("general", Classifier, lower=1, upper=1) Classifier.heritage = association("heritage", Heritage, composite=True, opposite="specific") Heritage.specific = association("specific", Classifier, lower=1, upper=1, opposite="heritage") Classifier.general = derived("general", Classifier, 0, "*", lambda self: [g.general for g in self.heritage])
class A(Element): attr = attribute("attr", bytes, default="default")
ownerContainer: relation_one[C4Container] owningContainer: relation_many[C4Container] technology: attribute[str] type: attribute[str] class C4Database(C4Container): pass class C4Person(Actor): description: attribute[str] location: attribute[str] C4Container.description = attribute("description", str) C4Container.location = attribute("location", str) C4Container.ownerContainer = association("ownerContainer", C4Container, upper=1, opposite="owningContainer") C4Container.owningContainer = association("owningContainer", C4Container, composite=True, opposite="ownerContainer") C4Container.technology = attribute("technology", str) C4Container.type = attribute("type", str) C4Person.description = attribute("description", str) C4Person.location = attribute("location", str) C4Container.namespace.subsets.add( C4Container.ownerContainer) # type: ignore[attr-defined]
pass class Viewpoint(Class): concernList: relation_many[Comment] language: attribute[str] presentation: attribute[str] purpose: attribute[str] stakeholder: relation_many[Stakeholder] class _Refine: pass AbstractRequirement.externalId = attribute("externalId", str) AbstractRequirement.text = attribute("text", str) AdjuntProperty.principal = association("principal", Element, upper=1) Block.isEncapsulated = attribute("isEncapsulated", int) BoundReference.boundend = association("boundend", ConnectorEnd, upper=1) ChangeSructuralFeatureEvent.structuralFeature = association( "structuralFeature", StructuralFeature, upper=1 ) ConnectorProperty.connector = association("connector", Connector, upper=1) DirectedFeature.featureDirection = enumeration( "kind", ("required", "providedRequired", "provided"), "required" ) DirectedRelationshipPropertyPath.sourceContext = association( "sourceContext", Classifier, upper=1 ) DirectedRelationshipPropertyPath.sourcePropertyPath = association(
# 55: override Diagram # defined in gaphor.core.modeling.diagram # 46: override Presentation # defined in gaphor.core.modeling.presentation class Comment(Element): body: attribute[str] annotatedElement: relation_many[Element] # 40: override StyleSheet # defined in gaphor.core.modeling.presentation NamedElement.name = attribute("name", str) Comment.body = attribute("body", str) # 43: override StyleSheet.styleSheet # defined in gaphor.core.modeling.presentation # 52: override Presentation.subject # defined in gaphor.core.modeling.presentation # 49: override Element.presentation # defined in gaphor.core.modeling.presentation Comment.annotatedElement = association("annotatedElement", Element, opposite="ownedComment") Element.ownedComment = association("ownedComment", Comment,
class LinePresentation(gaphas.Line, HandlePositionUpdate, Presentation[S]): def __init__( self, diagram: Diagram, id=None, style: Style = {}, shape_head=None, shape_middle=None, shape_tail=None, ): super().__init__(connections=diagram.connections, diagram=diagram, id=id) # type: ignore[call-arg] self.style = style self.shape_head = shape_head self.shape_middle = shape_middle self.shape_tail = shape_tail self.fuzziness = 2 self._shape_head_rect = None self._shape_middle_rect = None self._shape_tail_rect = None self.watch("orthogonal", self._on_orthogonal).watch( "horizontal", self._on_horizontal ) self.watch_handle(self.head) self.watch_handle(self.tail) head = property(lambda self: self._handles[0]) tail = property(lambda self: self._handles[-1]) orthogonal: attribute[int] = attribute("orthogonal", int, 0) horizontal: attribute[int] = attribute("horizontal", int, 0) def insert_handle(self, index: int, handle: Handle) -> None: super().insert_handle(index, handle) self.watch_handle(handle) def remove_handle(self, handle: Handle) -> None: self.remove_watch_handle(handle) super().remove_handle(handle) def pre_update(self, context): pass def post_update(self, context): def shape_bounds(shape, align): if shape: size = shape.size(context) x, y = text_point_at_line(points, size, align) return Rectangle(x, y, *size) points = [h.pos for h in self.handles()] self._shape_head_rect = shape_bounds(self.shape_head, TextAlign.LEFT) self._shape_middle_rect = shape_bounds(self.shape_middle, TextAlign.CENTER) self._shape_tail_rect = shape_bounds(self.shape_tail, TextAlign.RIGHT) def point(self, x, y): """Given a point (x, y) return the distance to the diagram item.""" d0 = super().point(x, y) ds = [ distance_rectangle_point(shape, (x, y)) for shape in ( self._shape_head_rect, self._shape_middle_rect, self._shape_tail_rect, ) if shape ] return min(d0, *ds) if ds else d0 def draw(self, context): def draw_line_end(end_handle, second_handle, draw): pos, p1 = end_handle.pos, second_handle.pos angle = atan2(p1.y - pos.y, p1.x - pos.x) cr = context.cairo cr.save() try: cr.translate(*pos) cr.rotate(angle) draw(context) finally: cr.restore() style = combined_style(context.style, self.style) context = replace(context, style=style) cr = context.cairo cr.set_line_width(self.line_width) cr.set_dash(style.get("dash-style", ()), 0) stroke = style["color"] if stroke: cr.set_source_rgba(*stroke) handles = self._handles draw_line_end(handles[0], handles[1], self.draw_head) for h in self._handles[1:-1]: cr.line_to(*h.pos) draw_line_end(handles[-1], handles[-2], self.draw_tail) draw_highlight(context) cr.stroke() for shape, rect in ( (self.shape_head, self._shape_head_rect), (self.shape_middle, self._shape_middle_rect), (self.shape_tail, self._shape_tail_rect), ): if shape: shape.draw(context, rect) def save(self, save_func): def save_connection(name, handle): c = self._connections.get_connection(handle) if c: save_func(name, c.connected) super().save(save_func) save_func("matrix", tuple(self.matrix)) points = [tuple(map(float, h.pos)) for h in self.handles()] save_func("points", points) save_connection("head-connection", self.head) save_connection("tail-connection", self.tail) def load(self, name, value): if name == "matrix": self.matrix.set(*ast.literal_eval(value)) elif name == "points": points = ast.literal_eval(value) for _ in range(len(points) - 2): h = Handle((0, 0)) self._handles.insert(1, h) self.watch_handle(h) for i, p in enumerate(points): self.handles()[i].pos = p self._update_ports() elif name in ("head_connection", "head-connection"): self._load_head_connection = value elif name in ("tail_connection", "tail-connection"): self._load_tail_connection = value else: super().load(name, value) def postload(self): if self.orthogonal: self._set_orthogonal(self.orthogonal) if hasattr(self, "_load_head_connection"): postload_connect(self, self.head, self._load_head_connection) assert self._connections.get_connection(self.head) del self._load_head_connection if hasattr(self, "_load_tail_connection"): postload_connect(self, self.tail, self._load_tail_connection) assert self._connections.get_connection(self.tail) del self._load_tail_connection super().postload() def _on_orthogonal(self, event): self._set_orthogonal(event.new_value) def _on_horizontal(self, event): self._set_horizontal(event.new_value)
class ClassItem(ElementPresentation[UML.Class], Classified): """This item visualizes a Class instance. A ClassItem contains two compartments: one for attributes and one for operations. """ def __init__(self, id=None, model=None): super().__init__(id, model) self.watch("show_stereotypes", self.update_shapes).watch( "show_attributes", self.update_shapes).watch( "show_operations", self.update_shapes).watch("subject[NamedElement].name").watch( "subject[NamedElement].namespace.name").watch( "subject[Classifier].isAbstract", self.update_shapes) attribute_watches(self, "Class") operation_watches(self, "Class") stereotype_watches(self) show_stereotypes: attribute[int] = attribute("show_stereotypes", int) show_attributes: attribute[int] = attribute("show_attributes", int, default=True) show_operations: attribute[int] = attribute("show_operations", int, default=True) def additional_stereotypes(self): if isinstance(self.subject, UML.Stereotype): return ["stereotype"] elif UML.model.is_metaclass(self.subject): return ["metaclass"] else: return () def update_shapes(self, event=None): self.shape = Box( Box( Text(text=lambda: UML.model.stereotypes_str( self.subject, self.additional_stereotypes()), ), EditableText( text=lambda: self.subject.name or "", style={ "font-weight": FontWeight.BOLD, "font-style": FontStyle.ITALIC if self.subject and self.subject.isAbstract else FontStyle.NORMAL, }, ), Text( text=lambda: from_package_str(self), style={ "font-size": 10, "min-width": 0, "min-height": 0 }, ), style={"padding": (12, 4, 12, 4)}, ), *(self.show_attributes and self.subject and [attributes_compartment(self.subject)] or []), *(self.show_operations and self.subject and [operations_compartment(self.subject)] or []), *(self.show_stereotypes and stereotype_compartments(self.subject) or []), style={ "min-width": 100, "min-height": 50, "vertical-align": VerticalAlign.TOP, }, draw=draw_border, )
class InterfaceItem(ElementPresentation, Classified): """Interface item supporting class box, folded notations and assembly connector icon mode. When in folded mode, provided (ball) notation is used by default. """ RADIUS_PROVIDED = 10 RADIUS_REQUIRED = 14 def __init__(self, id=None, model=None): super().__init__(id, model) self._folded = Folded.NONE self.side = Side.N handles = self.handles() h_nw = handles[NW] h_ne = handles[NE] h_sw = handles[SW] h_se = handles[SE] def is_folded(): return self._folded != Folded.NONE # edge of element define default element ports self._ports = [ InterfacePort(h_nw.pos, h_ne.pos, is_folded, Side.N), InterfacePort(h_ne.pos, h_se.pos, is_folded, Side.E), InterfacePort(h_se.pos, h_sw.pos, is_folded, Side.S), InterfacePort(h_sw.pos, h_nw.pos, is_folded, Side.W), ] self.watch("show_stereotypes", self.update_shapes).watch( "show_attributes", self.update_shapes ).watch("show_operations", self.update_shapes).watch( "subject[NamedElement].name" ).watch("subject[NamedElement].namespace.name").watch( "subject.appliedStereotype", self.update_shapes ).watch("subject.appliedStereotype.classifier.name").watch( "subject.appliedStereotype.slot", self.update_shapes).watch( "subject.appliedStereotype.slot.definingFeature.name").watch( "subject.appliedStereotype.slot.value", self.update_shapes).watch( "subject[Interface].supplierDependency", self.update_shapes) attribute_watches(self, "Interface") operation_watches(self, "Interface") show_stereotypes: attribute[int] = attribute("show_stereotypes", int) show_attributes: attribute[int] = attribute("show_attributes", int, default=True) show_operations: attribute[int] = attribute("show_operations", int, default=True) def load(self, name, value): if name == "folded": self._folded = Folded(ast.literal_eval(value)) else: super().load(name, value) def save(self, save_func): super().save(save_func) save_func("folded", self._folded.value) def _set_folded(self, folded): """Set folded notation. :param folded: Folded state, see Folded.* enum. """ if self._folded == folded: return self._folded = folded if folded == Folded.NONE: movable = True else: if self._folded == Folded.PROVIDED: icon_size = self.RADIUS_PROVIDED * 2 else: # required interface or assembly icon mode icon_size = self.RADIUS_REQUIRED * 2 self.min_width, self.min_height = icon_size, icon_size self.width, self.height = icon_size, icon_size # update only h_se handle - rest of handles should be updated by # constraints h_nw = self._handles[NW] h_se = self._handles[SE] h_se.pos.x = h_nw.pos.x + self.min_width h_se.pos.y = h_nw.pos.y + self.min_height movable = False for h in self._handles: h.movable = movable self.update_shapes() folded = property( lambda s: s._folded, _set_folded, doc="Check or set folded notation, see Folded.* enum.", ) def pre_update(self, context): assert isinstance(self.canvas, Canvas) connected_items = [ c.item for c in self.canvas.get_connections(connected=self) ] connectors = any( map(lambda i: isinstance(i.subject, UML.Connector), connected_items)) if connectors or self._folded != Folded.NONE: provided = connectors or any( map(lambda i: isinstance(i.subject, UML.Implementation), connected_items)) required = any( map(lambda i: isinstance(i.subject, UML.Usage), connected_items)) if required and provided: self.folded = Folded.ASSEMBLY elif required: self.folded = Folded.REQUIRED else: self.folded = Folded.PROVIDED self.update_shapes(connectors=connectors) super().pre_update(context) def update_shapes(self, event=None, connectors=None): if self._folded == Folded.NONE: self.shape = self.class_shape() else: self.shape = self.ball_and_socket_shape(connectors) def class_shape(self): return Box( Box( Text(text=lambda: UML.model.stereotypes_str( self.subject, ("interface", )), ), EditableText( text=lambda: self.subject.name or "", style={"font-weight": FontWeight.BOLD}, ), Text( text=lambda: from_package_str(self), style={ "font-size": 10, "min-width": 0, "min-height": 0 }, ), style={"padding": (12, 4, 12, 4)}, ), *(self.show_attributes and self.subject and [attributes_compartment(self.subject)] or []), *(self.show_operations and self.subject and [operations_compartment(self.subject)] or []), *(self.show_stereotypes and stereotype_compartments(self.subject) or []), style={ "min-width": 100, "min-height": 50, "vertical-align": VerticalAlign.TOP, }, draw=draw_border, ) def ball_and_socket_shape(self, connectors=None): assert self.canvas if connectors is None: # distinguish between None and [] connected_items = [ c.item for c in self.canvas.get_connections(connected=self) ] connectors = any( map(lambda i: isinstance(i.subject, UML.Connector), connected_items)) return IconBox( Box( style={ "min-width": self.min_width, "min-height": self.min_height }, draw=self.draw_interface_ball_and_socket, ), Text(text=lambda: UML.model.stereotypes_str(self.subject), ), EditableText( text=lambda: self.subject.name or "", style={ "font-weight": FontWeight.NORMAL if connectors else FontWeight.BOLD }, ), ) def draw_interface_ball_and_socket(self, _box, context, _bounding_box): cr = context.cairo h_nw = self._handles[NW] cx, cy = (h_nw.pos.x + self.width / 2, h_nw.pos.y + self.height / 2) if self._folded in (Folded.REQUIRED, Folded.ASSEMBLY): r = self.RADIUS_REQUIRED if self.side == Side.N: x, y = r * 2, r elif self.side == Side.E: x, y = r, r * 2 elif self.side == Side.S: x, y = 0, r elif self.side == Side.W: x, y = r, 0 cr.move_to(x, y) cr.arc_negative(cx, cy, self.RADIUS_REQUIRED, self.side.value, pi + self.side.value) if self._folded in (Folded.PROVIDED, Folded.ASSEMBLY): cr.move_to(cx + self.RADIUS_PROVIDED, cy) cr.arc(cx, cy, self.RADIUS_PROVIDED, 0, pi * 2) stroke(context)
class ClassItem(ElementPresentation[UML3.Class], Classified): """This item visualizes a Class instance. A ClassItem contains two compartments: one for attributes and one for operations. """ def __init__(self, id=None, model=None): super().__init__(id, model) self.watch("show_stereotypes", self.update_shapes).watch( "show_attributes", self.update_shapes ).watch("show_operations", self.update_shapes).watch( "subject[NamedElement].name" ).watch( "subject[NamedElement].namespace.name" ).watch( "subject[Classifier].isAbstract", self.update_shapes ) attribute_watches(self, "Class") operation_watches(self, "Class") stereotype_watches(self) show_stereotypes: attribute[int] = attribute("show_stereotypes", int) show_attributes: attribute[int] = attribute("show_attributes", int, default=True) show_operations: attribute[int] = attribute("show_operations", int, default=True) def additional_stereotypes(self): #aqui foram adicionados estereótipos da ontouml if isinstance(self.subject, UML3.StereotypeKind): return ["kind"] elif isinstance(self.subject, UML3.StereotypeSubkind): return ["subkind"] elif isinstance(self.subject, UML3.StereotypePhase): return ["phase"] elif isinstance(self.subject, UML3.StereotypeRole): return ["role"] elif isinstance(self.subject, UML3.StereotypeCollective): return ["collective"] elif isinstance(self.subject, UML3.StereotypeQuantity): return ["quantity"] elif isinstance(self.subject, UML3.StereotypeRelator): return ["relator"] elif isinstance(self.subject, UML3.StereotypeCategory): return ["category"] elif isinstance(self.subject, UML3.StereotypeRolemixin): return ["rolemixin"] elif isinstance(self.subject, UML3.StereotypeMixin): return ["mixin"] elif isinstance(self.subject, UML3.StereotypeMode): return ["mode"] elif isinstance(self.subject, UML3.StereotypePhasemixin): return ["phasemixin"] elif isinstance(self.subject, UML3.StereotypeQuality): return ["quality"] elif isinstance(self.subject, UML3.Material): return ["material"] else: return () def update_shapes(self, event=None): self.shape = Box( Box( Text( text=lambda: UML3.model.stereotypes_str( self.subject, self.additional_stereotypes() ), ), EditableText( text=lambda: self.subject.name or "", style={ "font-weight": FontWeight.BOLD, "font-style": FontStyle.ITALIC if self.subject and self.subject.isAbstract else FontStyle.NORMAL, }, ), Text( text=lambda: from_package_str(self), style={"font-size": 10, "min-width": 0, "min-height": 0}, ), style={"padding": (12, 4, 12, 4)}, ), *( self.show_attributes and self.subject and [attributes_compartment(self.subject)] or [] ), *( self.show_operations and self.subject and [operations_compartment(self.subject)] or [] ), *(self.show_stereotypes and stereotyperelator_compartments(self.subject) or []), style={ "min-width": 100, "min-height": 50, "vertical-align": VerticalAlign.TOP, }, draw=draw_border, )
class BlockItem(ElementPresentation[Block], Classified): def __init__(self, diagram, id=None): super().__init__(diagram, id) self.watch("show_stereotypes", self.update_shapes).watch( "show_parts", self.update_shapes).watch( "show_references", self.update_shapes).watch("subject[NamedElement].name").watch( "subject[NamedElement].namespace.name").watch( "subject[Classifier].isAbstract", self.update_shapes).watch( "subject[Class].ownedAttribute.aggregation", self.update_shapes) attribute_watches(self, "Block") stereotype_watches(self) show_stereotypes: attribute[int] = attribute("show_stereotypes", int) show_parts: attribute[int] = attribute("show_parts", int, default=False) show_references: attribute[int] = attribute("show_references", int, default=False) def update_shapes(self, event=None): self.shape = Box( Box( Text( text=lambda: stereotypes_str(self.subject, ["block"]), style={ "min-width": 0, "min-height": 0 }, ), EditableText( text=lambda: self.subject.name or "", width=lambda: self.width - 4, style={ "font-weight": FontWeight.BOLD, "font-style": FontStyle.ITALIC if self.subject and self.subject.isAbstract else FontStyle.NORMAL, }, ), Text( text=lambda: from_package_str(self), style={ "font-size": 10, "min-width": 0, "min-height": 0 }, ), style={"padding": (12, 4, 12, 4)}, ), *(self.show_parts and self.subject and [ self.block_compartment( gettext("parts"), lambda a: a.aggregation == "composite", ) ] or []), *(self.show_references and self.subject and [ self.block_compartment( gettext("references"), lambda a: a.aggregation != "composite", ) ] or []), *(self.show_stereotypes and stereotype_compartments(self.subject) or []), style={ "min-width": 100, "min-height": 50, "vertical-align": VerticalAlign.TOP, }, draw=draw_border, ) def block_compartment(self, name, predicate): # We need to fix the attribute value, since the for loop changes it. def lazy_format(attribute): return lambda: format_property(attribute) or gettext("unnamed") return Box( Text( text=name, style={ "padding": (0, 0, 4, 0), "font-size": 10, "font-style": FontStyle.ITALIC, }, ), *(Text(text=lazy_format(attribute), style={"text-align": TextAlign.LEFT}) for attribute in self.subject.ownedAttribute if predicate(attribute)), style={ "padding": (4, 4, 4, 4), "min-height": 8 }, draw=draw_top_separator, )
class AssociationItem(LinePresentation[UML.Association], Named): """AssociationItem represents associations. An AssociationItem has two AssociationEnd items. Each AssociationEnd item represents a Property (with Property.association == my association). """ def __init__(self, diagram, id=None): super().__init__(diagram, id) # AssociationEnds are really inseparable from the AssociationItem. # We give them the same id as the association item. self._head_end = AssociationEnd(owner=self, end="head") self._tail_end = AssociationEnd(owner=self, end="tail") # Direction depends on the ends that hold the ownedEnd attributes. self._dir_angle = 0 self._dir_pos = 0, 0 self.shape_middle = Box( Text( text=lambda: stereotypes_str(self.subject), ), EditableText(text=lambda: self.subject.name or ""), ) # For the association ends: base = "subject[Association].memberEnd[Property]" self.watch("subject[NamedElement].name").watch( "subject.appliedStereotype.classifier.name" ).watch(f"{base}.name", self.on_association_end_value).watch( f"{base}.aggregation", self.on_association_end_value ).watch( f"{base}.appliedStereotype.slot.definingFeature.name", self.on_association_end_value, ).watch( f"{base}.appliedStereotype.slot.value", self.on_association_end_value ).watch( f"{base}.classifier", self.on_association_end_value ).watch( f"{base}.visibility", self.on_association_end_value ).watch( f"{base}.lowerValue", self.on_association_end_value ).watch( f"{base}.upperValue", self.on_association_end_value ).watch( f"{base}.owningAssociation", self.on_association_end_value ).watch( f"{base}.type[Class].ownedAttribute", self.on_association_end_value ).watch( f"{base}.type[Interface].ownedAttribute", self.on_association_end_value ).watch( f"{base}.appliedStereotype.classifier", self.on_association_end_value ).watch( "subject[Association].memberEnd" ).watch( "subject[Association].ownedEnd" ).watch( "subject[Association].navigableOwnedEnd" ).watch( "show_direction" ) show_direction: attribute[int] = attribute("show_direction", int, default=False) def load(self, name, value): # end_head and end_tail were used in an older Gaphor version if name in ("head_end", "head-subject"): name = "head_subject" elif name in ("tail_end", "tail-subject"): name = "tail_subject" elif name == "show-direction": name = "show_direction" super().load(name, value) def postload(self): super().postload() self._head_end.set_text() self._tail_end.set_text() head_end = property(lambda self: self._head_end) tail_end = property(lambda self: self._tail_end) head_subject = association("head_subject", UML.Property, upper=1) tail_subject = association("tail_subject", UML.Property, upper=1) def invert_direction(self): """Invert the direction of the association, this is done by swapping the head and tail-ends subjects.""" if not self.subject: return self.subject.memberEnd.swap( self.subject.memberEnd[0], self.subject.memberEnd[1] ) self.request_update() def on_association_end_value(self, event): """Handle events and update text on association end.""" for end in (self._head_end, self._tail_end): end.set_text() self.request_update() def post_update(self, context): """Update the shapes and sub-items of the association.""" handles = self.handles() # Update line endings: head_subject = self.head_subject tail_subject = self.tail_subject # Update line ends using the aggregation and isNavigable values: if head_subject and tail_subject: if tail_subject.aggregation == "composite": self.draw_head = draw_head_composite elif tail_subject.aggregation == "shared": self.draw_head = draw_head_shared elif head_subject.navigability is True: self.draw_head = draw_head_navigable elif head_subject.navigability is False: self.draw_head = draw_head_none else: self.draw_head = draw_default_head if head_subject.aggregation == "composite": self.draw_tail = draw_tail_composite elif head_subject.aggregation == "shared": self.draw_tail = draw_tail_shared elif tail_subject.navigability is True: self.draw_tail = draw_tail_navigable elif tail_subject.navigability is False: self.draw_tail = draw_tail_none else: self.draw_tail = draw_default_tail if self.show_direction: inverted = self.tail_subject is self.subject.memberEnd[0] pos, angle = get_center_pos(self.handles(), inverted) self._dir_pos = pos self._dir_angle = angle else: self.draw_head = draw_default_head self.draw_tail = draw_default_tail # update relationship after self.set calls to avoid circural updates super().post_update(context) # Calculate alignment of the head name and multiplicity self._head_end.post_update(context, handles[0].pos, handles[1].pos) # Calculate alignment of the tail name and multiplicity self._tail_end.post_update(context, handles[-1].pos, handles[-2].pos) def point(self, x, y): """Returns the distance from the Association to the (mouse) cursor.""" return min( super().point(x, y), self._head_end.point(x, y), self._tail_end.point(x, y) ) def draw(self, context): super().draw(context) self._head_end.draw(context) self._tail_end.draw(context) if self.show_direction: with cairo_state(context.cairo) as cr: cr.translate(*self._dir_pos) cr.rotate(self._dir_angle) cr.move_to(0, 0) cr.line_to(6, 5) cr.line_to(0, 10) cr.fill()
class RequirementItem(ElementPresentation[Requirement], Classified): def __init__(self, connections, id=None, model=None): super().__init__(connections, id, model) self.watch("show_stereotypes", self.update_shapes).watch( "show_attributes", self.update_shapes).watch( "show_operations", self.update_shapes).watch("subject[NamedElement].name").watch( "subject[NamedElement].namespace.name").watch( "subject[Classifier].isAbstract", self.update_shapes).watch( "subject[AbstractRequirement].externalId", self.update_shapes).watch( "subject[AbstractRequirement].text", self.update_shapes) attribute_watches(self, "Requirement") operation_watches(self, "Requirement") stereotype_watches(self) show_stereotypes: attribute[int] = attribute("show_stereotypes", int) show_attributes: attribute[int] = attribute("show_attributes", int, default=False) show_operations: attribute[int] = attribute("show_operations", int, default=False) def update_shapes(self, event=None): self.shape = Box( Box( Text( text=lambda: stereotypes_str(self.subject, ["requirement"] ), style={ "min-width": 0, "min-height": 0 }, ), EditableText( text=lambda: self.subject.name or "", width=lambda: self.width - 4, style={ "font-weight": FontWeight.BOLD, "font-style": FontStyle.ITALIC if self.subject and self.subject.isAbstract else FontStyle.NORMAL, }, ), Text( text=lambda: from_package_str(self), style={ "font-size": 10, "min-width": 0, "min-height": 0 }, ), style={"padding": (12, 4, 12, 4)}, ), *(self.show_attributes and self.subject and [attributes_compartment(self.subject)] or []), *(self.show_operations and self.subject and [operations_compartment(self.subject)] or []), *(self.show_stereotypes and stereotype_compartments(self.subject) or []), self.id_and_text_compartment(), style={ "min-width": 100, "min-height": 50, "vertical-align": VerticalAlign.TOP, }, draw=draw_border, ) def id_and_text_compartment(self): subject = self.subject if subject and (subject.externalId or subject.text): return Box( *([ Text( text=lambda: f"Id: {subject.externalId}", style={"text-align": TextAlign.LEFT}, ) ] if subject and subject.externalId else []), *([ Text( text=lambda: f"Text: {subject.text}", width=lambda: self.width - 8, style={"text-align": TextAlign.LEFT}, ) ] if subject and subject.text else []), style={ "padding": (4, 4, 4, 4), "min-height": 8 }, draw=draw_top_separator, ) else: return Box()