|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2004 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the UMLItem base class. |
|
8 """ |
|
9 |
|
10 from PyQt6.QtCore import Qt, QSizeF |
|
11 from PyQt6.QtGui import QColor, QPen |
|
12 from PyQt6.QtWidgets import QGraphicsItem, QGraphicsRectItem, QStyle |
|
13 |
|
14 import Preferences |
|
15 |
|
16 |
|
17 class UMLModel: |
|
18 """ |
|
19 Class implementing the UMLModel base class. |
|
20 """ |
|
21 def __init__(self, name): |
|
22 """ |
|
23 Constructor |
|
24 |
|
25 @param name package name |
|
26 @type str |
|
27 """ |
|
28 self.name = name |
|
29 |
|
30 def getName(self): |
|
31 """ |
|
32 Public method to retrieve the model name. |
|
33 |
|
34 @return model name |
|
35 @rtype str |
|
36 """ |
|
37 return self.name |
|
38 |
|
39 |
|
40 class UMLItem(QGraphicsRectItem): |
|
41 """ |
|
42 Class implementing the UMLItem base class. |
|
43 """ |
|
44 ItemType = "UMLItem" |
|
45 |
|
46 def __init__(self, model=None, x=0, y=0, rounded=False, colors=None, |
|
47 parent=None): |
|
48 """ |
|
49 Constructor |
|
50 |
|
51 @param model UML model containing the item data |
|
52 @type UMLModel |
|
53 @param x x-coordinate |
|
54 @type int |
|
55 @param y y-coordinate |
|
56 @type int |
|
57 @param rounded flag indicating a rounded corner |
|
58 @type bool |
|
59 @param colors tuple containing the foreground and background colors |
|
60 @type tuple of (QColor, QColor) |
|
61 @param parent reference to the parent object |
|
62 @type QGraphicsItem |
|
63 """ |
|
64 super().__init__(parent) |
|
65 self.model = model |
|
66 |
|
67 if colors is None: |
|
68 self._colors = (QColor(Qt.GlobalColor.black), |
|
69 QColor(Qt.GlobalColor.white)) |
|
70 else: |
|
71 self._colors = colors |
|
72 self.setPen(QPen(self._colors[0])) |
|
73 |
|
74 self.font = Preferences.getGraphics("Font") |
|
75 self.margin = 5 |
|
76 self.associations = [] |
|
77 self.shouldAdjustAssociations = False |
|
78 self.__id = -1 |
|
79 |
|
80 self.setRect(x, y, 60, 30) |
|
81 |
|
82 if rounded: |
|
83 p = self.pen() |
|
84 p.setCapStyle(Qt.PenCapStyle.RoundCap) |
|
85 p.setJoinStyle(Qt.PenJoinStyle.RoundJoin) |
|
86 |
|
87 self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True) |
|
88 self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True) |
|
89 self.setFlag( |
|
90 QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges, True) |
|
91 |
|
92 def getName(self): |
|
93 """ |
|
94 Public method to retrieve the item name. |
|
95 |
|
96 @return item name |
|
97 @rtype str |
|
98 """ |
|
99 if self.model: |
|
100 return self.model.name |
|
101 else: |
|
102 return "" |
|
103 |
|
104 def setSize(self, width, height): |
|
105 """ |
|
106 Public method to set the rectangles size. |
|
107 |
|
108 @param width width of the rectangle |
|
109 @type float |
|
110 @param height height of the rectangle |
|
111 @type float |
|
112 """ |
|
113 rect = self.rect() |
|
114 rect.setSize(QSizeF(width, height)) |
|
115 self.setRect(rect) |
|
116 |
|
117 def addAssociation(self, assoc): |
|
118 """ |
|
119 Public method to add an association to this widget. |
|
120 |
|
121 @param assoc association to be added |
|
122 @type AssociationWidget |
|
123 """ |
|
124 if assoc and assoc not in self.associations: |
|
125 self.associations.append(assoc) |
|
126 |
|
127 def removeAssociation(self, assoc): |
|
128 """ |
|
129 Public method to remove an association to this widget. |
|
130 |
|
131 @param assoc association to be removed |
|
132 @type AssociationWidget |
|
133 """ |
|
134 if assoc and assoc in self.associations: |
|
135 self.associations.remove(assoc) |
|
136 |
|
137 def removeAssociations(self): |
|
138 """ |
|
139 Public method to remove all associations of this widget. |
|
140 """ |
|
141 for assoc in self.associations[:]: |
|
142 assoc.unassociate() |
|
143 assoc.hide() |
|
144 del assoc |
|
145 |
|
146 def adjustAssociations(self): |
|
147 """ |
|
148 Public method to adjust the associations to widget movements. |
|
149 """ |
|
150 if self.shouldAdjustAssociations: |
|
151 for assoc in self.associations: |
|
152 assoc.widgetMoved() |
|
153 self.shouldAdjustAssociations = False |
|
154 |
|
155 def moveBy(self, dx, dy): |
|
156 """ |
|
157 Public overriden method to move the widget relative. |
|
158 |
|
159 @param dx relative movement in x-direction |
|
160 @type float |
|
161 @param dy relative movement in y-direction |
|
162 @type float |
|
163 """ |
|
164 super().moveBy(dx, dy) |
|
165 self.adjustAssociations() |
|
166 |
|
167 def setPos(self, x, y): |
|
168 """ |
|
169 Public overriden method to set the items position. |
|
170 |
|
171 @param x absolute x-position |
|
172 @type float |
|
173 @param y absolute y-position |
|
174 @type float |
|
175 """ |
|
176 super().setPos(x, y) |
|
177 self.adjustAssociations() |
|
178 |
|
179 def itemChange(self, change, value): |
|
180 """ |
|
181 Public method called when an items state changes. |
|
182 |
|
183 @param change the item's change |
|
184 @type QGraphicsItem.GraphicsItemChange |
|
185 @param value the value of the change |
|
186 @return adjusted values |
|
187 """ |
|
188 if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange: |
|
189 # 1. remember to adjust associations |
|
190 self.shouldAdjustAssociations = True |
|
191 |
|
192 # 2. ensure the new position is inside the scene |
|
193 scene = self.scene() |
|
194 if scene: |
|
195 rect = scene.sceneRect() |
|
196 if not rect.contains(value): |
|
197 # keep the item inside the scene |
|
198 value.setX(min(rect.right(), max(value.x(), rect.left()))) |
|
199 value.setY(min(rect.bottom(), max(value.y(), rect.top()))) |
|
200 return value |
|
201 |
|
202 return QGraphicsItem.itemChange(self, change, value) |
|
203 |
|
204 def paint(self, painter, option, widget=None): |
|
205 """ |
|
206 Public method to paint the item in local coordinates. |
|
207 |
|
208 @param painter reference to the painter object |
|
209 @type QPainter |
|
210 @param option style options |
|
211 @type QStyleOptionGraphicsItem |
|
212 @param widget optional reference to the widget painted on |
|
213 @type QWidget |
|
214 """ |
|
215 pen = self.pen() |
|
216 if ( |
|
217 (option.state & QStyle.StateFlag.State_Selected) == |
|
218 QStyle.StateFlag.State_Selected |
|
219 ): |
|
220 pen.setWidth(2) |
|
221 else: |
|
222 pen.setWidth(1) |
|
223 |
|
224 painter.setPen(pen) |
|
225 painter.setBrush(self.brush()) |
|
226 painter.drawRect(self.rect()) |
|
227 self.adjustAssociations() |
|
228 |
|
229 def setId(self, itemId): |
|
230 """ |
|
231 Public method to assign an ID to the item. |
|
232 |
|
233 @param itemId assigned ID |
|
234 @type int |
|
235 """ |
|
236 self.__id = itemId |
|
237 |
|
238 def getId(self): |
|
239 """ |
|
240 Public method to get the item ID. |
|
241 |
|
242 @return ID of the item |
|
243 @rtype int |
|
244 """ |
|
245 return self.__id |
|
246 |
|
247 def getItemType(self): |
|
248 """ |
|
249 Public method to get the item's type. |
|
250 |
|
251 @return item type |
|
252 @rtype str |
|
253 """ |
|
254 return self.ItemType |
|
255 |
|
256 def parseItemDataString(self, version, data): |
|
257 """ |
|
258 Public method to parse the given persistence data. |
|
259 |
|
260 @param version version of the data |
|
261 @type str |
|
262 @param data persisted data to be parsed |
|
263 @type str |
|
264 @return flag indicating success |
|
265 @rtype bool |
|
266 """ |
|
267 return True |
|
268 |
|
269 def toDict(self): |
|
270 """ |
|
271 Public method to collect data to be persisted. |
|
272 |
|
273 @return dictionary containing data to be persisted |
|
274 @rtype dict |
|
275 """ |
|
276 return { |
|
277 "id": self.getId(), |
|
278 "x": self.x(), |
|
279 "y": self.y(), |
|
280 "type": self.getItemType(), |
|
281 "model_name": self.model.getName(), |
|
282 } |
|
283 |
|
284 @classmethod |
|
285 def fromDict(cls, data, colors=None): |
|
286 """ |
|
287 Class method to create a generic UML item from persisted data. |
|
288 |
|
289 @param data dictionary containing the persisted data as generated |
|
290 by toDict() |
|
291 @type dict |
|
292 @param colors tuple containing the foreground and background colors |
|
293 @type tuple of (QColor, QColor) |
|
294 @return created UML item |
|
295 @rtype UMLItem |
|
296 """ |
|
297 try: |
|
298 model = UMLModel(data["model_name"]) |
|
299 itm = cls(model=model, |
|
300 x=0, |
|
301 y=0, |
|
302 colors=colors) |
|
303 itm.setPos(data["x"], data["y"]) |
|
304 itm.setId(data["id"]) |
|
305 return itm |
|
306 except KeyError: |
|
307 return None |