5 |
5 |
6 """ |
6 """ |
7 Module implementing a graphics item for an association between two items. |
7 Module implementing a graphics item for an association between two items. |
8 """ |
8 """ |
9 |
9 |
|
10 import enum |
|
11 |
10 from PyQt5.QtCore import QPointF, QRectF, QLineF |
12 from PyQt5.QtCore import QPointF, QRectF, QLineF |
11 from PyQt5.QtWidgets import QGraphicsItem |
13 from PyQt5.QtWidgets import QGraphicsItem |
12 |
14 |
13 from E5Graphics.E5ArrowItem import E5ArrowItem, E5ArrowType |
15 from E5Graphics.E5ArrowItem import E5ArrowItem, E5ArrowType |
14 |
16 |
15 import Utilities |
17 import Utilities |
16 |
18 |
17 |
19 |
18 # TODO: convert to Enum |
20 class AssociationType(enum.Enum): |
19 Normal = 0 |
21 """ |
20 Generalisation = 1 |
22 Class defining the association types. |
21 Imports = 2 |
23 """ |
22 |
24 NORMAL = 0 |
23 # TODO: convert to Enum |
25 GENERALISATION = 1 |
24 NoRegion = 0 |
26 IMPORTS = 2 |
25 West = 1 |
27 |
26 North = 2 |
28 |
27 East = 3 |
29 class AssociationPointRegion(enum.Enum): |
28 South = 4 |
30 """ |
29 NorthWest = 5 |
31 Class defining the regions for an association end point. |
30 NorthEast = 6 |
32 """ |
31 SouthEast = 7 |
33 NO_REGION = 0 |
32 SouthWest = 8 |
34 WEST = 1 |
33 Center = 9 |
35 NORTH = 2 |
|
36 EAST = 3 |
|
37 SOUTH = 4 |
|
38 NORTH_WEST = 5 |
|
39 NORTH_EAST = 6 |
|
40 SOUTH_EAST = 7 |
|
41 SOUTH_WEST = 8 |
|
42 CENTER = 9 |
34 |
43 |
35 |
44 |
36 class AssociationItem(E5ArrowItem): |
45 class AssociationItem(E5ArrowItem): |
37 """ |
46 """ |
38 Class implementing a graphics item for an association between two items. |
47 Class implementing a graphics item for an association between two items. |
39 |
48 |
40 The association is drawn as an arrow starting at the first items and |
49 The association is drawn as an arrow starting at the first items and |
41 ending at the second. |
50 ending at the second. |
42 """ |
51 """ |
43 def __init__(self, itemA, itemB, assocType=Normal, topToBottom=False, |
52 def __init__(self, itemA, itemB, assocType=AssociationType.NORMAL, |
44 colors=None, parent=None): |
53 topToBottom=False, colors=None, parent=None): |
45 """ |
54 """ |
46 Constructor |
55 Constructor |
47 |
56 |
48 @param itemA first widget of the association |
57 @param itemA first widget of the association |
|
58 @type UMLItem |
49 @param itemB second widget of the association |
59 @param itemB second widget of the association |
50 @param assocType type of the association. This must be one of |
60 @type UMLItem |
51 <ul> |
61 @param assocType type of the association |
52 <li>Normal (default)</li> |
62 @type AssociationType |
53 <li>Generalisation</li> |
|
54 <li>Imports</li> |
|
55 </ul> |
|
56 @param topToBottom flag indicating to draw the association |
63 @param topToBottom flag indicating to draw the association |
57 from item A top to item B bottom |
64 from item A top to item B bottom |
58 @type bool |
65 @type bool |
59 @param colors tuple containing the foreground and background colors |
66 @param colors tuple containing the foreground and background colors |
60 @type tuple of (QColor, QColor) |
67 @type tuple of (QColor, QColor) |
61 @param parent reference to the parent object |
68 @param parent reference to the parent object |
62 @type QGraphicsItem |
69 @type QGraphicsItem |
63 """ |
70 """ |
64 if assocType in (Normal, Imports): |
71 if assocType in (AssociationType.NORMAL, AssociationType.IMPORTS): |
65 arrowType = E5ArrowType.NORMAL |
72 arrowType = E5ArrowType.NORMAL |
66 arrowFilled = True |
73 arrowFilled = True |
67 elif assocType == Generalisation: |
74 elif assocType == AssociationType.GENERALISATION: |
68 arrowType = E5ArrowType.WIDE |
75 arrowType = E5ArrowType.WIDE |
69 arrowFilled = False |
76 arrowFilled = False |
70 |
77 |
71 E5ArrowItem.__init__(self, QPointF(0, 0), QPointF(100, 100), |
78 E5ArrowItem.__init__(self, QPointF(0, 0), QPointF(100, 100), |
72 arrowFilled, arrowType, colors, parent) |
79 arrowFilled, arrowType, colors, parent) |
214 |
221 |
215 # find itemA region |
222 # find itemA region |
216 rc = QRectF(xA, yA, rectA.width(), rectA.height()) |
223 rc = QRectF(xA, yA, rectA.width(), rectA.height()) |
217 self.regionA = self.__findPointRegion(rc, xB, yB) |
224 self.regionA = self.__findPointRegion(rc, xB, yB) |
218 # move some regions to the standard ones |
225 # move some regions to the standard ones |
219 if self.regionA == NorthWest: |
226 if self.regionA == AssociationPointRegion.NORTH_WEST: |
220 self.regionA = North |
227 self.regionA = AssociationPointRegion.NORTH |
221 elif self.regionA == NorthEast: |
228 elif self.regionA == AssociationPointRegion.NORTH_EAST: |
222 self.regionA = East |
229 self.regionA = AssociationPointRegion.EAST |
223 elif self.regionA == SouthEast: |
230 elif self.regionA == AssociationPointRegion.SOUTH_EAST: |
224 self.regionA = South |
231 self.regionA = AssociationPointRegion.SOUTH |
225 elif self.regionA in (SouthWest, Center): |
232 elif self.regionA in ( |
226 self.regionA = West |
233 AssociationPointRegion.SOUTH_WEST, |
|
234 AssociationPointRegion.CENTER |
|
235 ): |
|
236 self.regionA = AssociationPointRegion.WEST |
227 |
237 |
228 self.__updateEndPoint(self.regionA, True) |
238 self.__updateEndPoint(self.regionA, True) |
229 |
239 |
230 # now do the same for itemB |
240 # now do the same for itemB |
231 rc = QRectF(xB, yB, rectB.width(), rectB.height()) |
241 rc = QRectF(xB, yB, rectB.width(), rectB.height()) |
232 self.regionB = self.__findPointRegion(rc, xA, yA) |
242 self.regionB = self.__findPointRegion(rc, xA, yA) |
233 # move some regions to the standard ones |
243 # move some regions to the standard ones |
234 if self.regionB == NorthWest: |
244 if self.regionB == AssociationPointRegion.NORTH_WEST: |
235 self.regionB = North |
245 self.regionB = AssociationPointRegion.NORTH |
236 elif self.regionB == NorthEast: |
246 elif self.regionB == AssociationPointRegion.NORTH_EAST: |
237 self.regionB = East |
247 self.regionB = AssociationPointRegion.EAST |
238 elif self.regionB == SouthEast: |
248 elif self.regionB == AssociationPointRegion.SOUTH_EAST: |
239 self.regionB = South |
249 self.regionB = AssociationPointRegion.SOUTH |
240 elif self.regionB in (SouthWest, Center): |
250 elif self.regionB in ( |
241 self.regionB = West |
251 AssociationPointRegion.SOUTH_WEST, |
|
252 AssociationPointRegion.CENTER |
|
253 ): |
|
254 self.regionB = AssociationPointRegion.WEST |
242 |
255 |
243 self.__updateEndPoint(self.regionB, False) |
256 self.__updateEndPoint(self.regionB, False) |
244 |
257 |
245 def __findPointRegion(self, rect, posX, posY): |
258 def __findPointRegion(self, rect, posX, posY): |
246 """ |
259 """ |
271 b2 = x + w / 2.0 - y * slope2 |
284 b2 = x + w / 2.0 - y * slope2 |
272 |
285 |
273 eval1 = slope1 * posY + b1 |
286 eval1 = slope1 * posY + b1 |
274 eval2 = slope2 * posY + b2 |
287 eval2 = slope2 * posY + b2 |
275 |
288 |
276 result = NoRegion |
289 result = AssociationPointRegion.NO_REGION |
277 |
290 |
278 # inside region 1 |
291 # inside region 1 |
279 if eval1 > posX and eval2 > posX: |
292 if eval1 > posX and eval2 > posX: |
280 result = West |
293 result = AssociationPointRegion.WEST |
281 |
294 |
282 #inside region 2 |
295 #inside region 2 |
283 elif eval1 > posX and eval2 < posX: |
296 elif eval1 > posX and eval2 < posX: |
284 result = North |
297 result = AssociationPointRegion.NORTH |
285 |
298 |
286 # inside region 3 |
299 # inside region 3 |
287 elif eval1 < posX and eval2 < posX: |
300 elif eval1 < posX and eval2 < posX: |
288 result = East |
301 result = AssociationPointRegion.EAST |
289 |
302 |
290 # inside region 4 |
303 # inside region 4 |
291 elif eval1 < posX and eval2 > posX: |
304 elif eval1 < posX and eval2 > posX: |
292 result = South |
305 result = AssociationPointRegion.SOUTH |
293 |
306 |
294 # inside region 5 |
307 # inside region 5 |
295 elif eval1 == posX and eval2 < posX: |
308 elif eval1 == posX and eval2 < posX: |
296 result = NorthWest |
309 result = AssociationPointRegion.NORTH_WEST |
297 |
310 |
298 # inside region 6 |
311 # inside region 6 |
299 elif eval1 < posX and eval2 == posX: |
312 elif eval1 < posX and eval2 == posX: |
300 result = NorthEast |
313 result = AssociationPointRegion.NORTH_EAST |
301 |
314 |
302 # inside region 7 |
315 # inside region 7 |
303 elif eval1 == posX and eval2 > posX: |
316 elif eval1 == posX and eval2 > posX: |
304 result = SouthEast |
317 result = AssociationPointRegion.SOUTH_EAST |
305 |
318 |
306 # inside region 8 |
319 # inside region 8 |
307 elif eval1 > posX and eval2 == posX: |
320 elif eval1 > posX and eval2 == posX: |
308 result = SouthWest |
321 result = AssociationPointRegion.SOUTH_WEST |
309 |
322 |
310 # inside region 9 |
323 # inside region 9 |
311 elif eval1 == posX and eval2 == posX: |
324 elif eval1 == posX and eval2 == posX: |
312 result = Center |
325 result = AssociationPointRegion.CENTER |
313 |
326 |
314 return result |
327 return result |
315 |
328 |
316 def __updateEndPoint(self, region, isWidgetA): |
329 def __updateEndPoint(self, region, isWidgetA): |
317 """ |
330 """ |
318 Private method to update an endpoint. |
331 Private method to update an endpoint. |
319 |
332 |
320 @param region the region for the endpoint (integer) |
333 @param region the region for the endpoint (integer) |
321 @param isWidgetA flag indicating update for itemA is done (boolean) |
334 @param isWidgetA flag indicating update for itemA is done (boolean) |
322 """ |
335 """ |
323 if region == NoRegion: |
336 if region == AssociationPointRegion.NO_REGION: |
324 return |
337 return |
325 |
338 |
326 rect = ( |
339 rect = ( |
327 self.__mapRectFromItem(self.itemA) |
340 self.__mapRectFromItem(self.itemA) |
328 if isWidgetA else |
341 if isWidgetA else |
532 @return persistence data (string) |
548 @return persistence data (string) |
533 """ |
549 """ |
534 entries = [ |
550 entries = [ |
535 "src={0}".format(self.itemA.getId()), |
551 "src={0}".format(self.itemA.getId()), |
536 "dst={0}".format(self.itemB.getId()), |
552 "dst={0}".format(self.itemB.getId()), |
537 "type={0}".format(self.assocType), |
553 "type={0}".format(self.assocType.value), |
538 "topToBottom={0}".format(self.topToBottom) |
554 "topToBottom={0}".format(self.topToBottom) |
539 ] |
555 ] |
540 return ", ".join(entries) |
556 return ", ".join(entries) |
541 |
557 |
542 @classmethod |
558 @classmethod |
549 the association type and a flag indicating to associate from top |
565 the association type and a flag indicating to associate from top |
550 to bottom (integer, integer, integer, boolean) |
566 to bottom (integer, integer, integer, boolean) |
551 """ |
567 """ |
552 src = -1 |
568 src = -1 |
553 dst = -1 |
569 dst = -1 |
554 assocType = Normal |
570 assocType = AssociationType.NORMAL |
555 topToBottom = False |
571 topToBottom = False |
556 for entry in data.split(", "): |
572 for entry in data.split(", "): |
557 if "=" in entry: |
573 if "=" in entry: |
558 key, value = entry.split("=", 1) |
574 key, value = entry.split("=", 1) |
559 if key == "src": |
575 if key == "src": |
560 src = int(value) |
576 src = int(value) |
561 elif key == "dst": |
577 elif key == "dst": |
562 dst = int(value) |
578 dst = int(value) |
563 elif key == "type": |
579 elif key == "type": |
564 assocType = int(value) |
580 assocType = AssociationType(int(value)) |
565 elif key == "topToBottom": |
581 elif key == "topToBottom": |
566 topToBottom = Utilities.toBool(value) |
582 topToBottom = Utilities.toBool(value) |
567 |
583 |
568 return src, dst, assocType, topToBottom |
584 return src, dst, assocType, topToBottom |