src/eric7/Graphics/AssociationItem.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
19 19
20 class AssociationType(enum.Enum): 20 class AssociationType(enum.Enum):
21 """ 21 """
22 Class defining the association types. 22 Class defining the association types.
23 """ 23 """
24
24 NORMAL = 0 25 NORMAL = 0
25 GENERALISATION = 1 26 GENERALISATION = 1
26 IMPORTS = 2 27 IMPORTS = 2
27 28
28 29
29 class AssociationPointRegion(enum.Enum): 30 class AssociationPointRegion(enum.Enum):
30 """ 31 """
31 Class defining the regions for an association end point. 32 Class defining the regions for an association end point.
32 """ 33 """
34
33 NO_REGION = 0 35 NO_REGION = 0
34 WEST = 1 36 WEST = 1
35 NORTH = 2 37 NORTH = 2
36 EAST = 3 38 EAST = 3
37 SOUTH = 4 39 SOUTH = 4
43 45
44 46
45 class AssociationItem(EricArrowItem): 47 class AssociationItem(EricArrowItem):
46 """ 48 """
47 Class implementing a graphics item for an association between two items. 49 Class implementing a graphics item for an association between two items.
48 50
49 The association is drawn as an arrow starting at the first items and 51 The association is drawn as an arrow starting at the first items and
50 ending at the second. 52 ending at the second.
51 """ 53 """
52 def __init__(self, itemA, itemB, assocType=AssociationType.NORMAL, 54
53 topToBottom=False, colors=None, parent=None): 55 def __init__(
56 self,
57 itemA,
58 itemB,
59 assocType=AssociationType.NORMAL,
60 topToBottom=False,
61 colors=None,
62 parent=None,
63 ):
54 """ 64 """
55 Constructor 65 Constructor
56 66
57 @param itemA first widget of the association 67 @param itemA first widget of the association
58 @type UMLItem 68 @type UMLItem
59 @param itemB second widget of the association 69 @param itemB second widget of the association
60 @type UMLItem 70 @type UMLItem
61 @param assocType type of the association 71 @param assocType type of the association
72 arrowType = EricArrowType.NORMAL 82 arrowType = EricArrowType.NORMAL
73 arrowFilled = True 83 arrowFilled = True
74 elif assocType == AssociationType.GENERALISATION: 84 elif assocType == AssociationType.GENERALISATION:
75 arrowType = EricArrowType.WIDE 85 arrowType = EricArrowType.WIDE
76 arrowFilled = False 86 arrowFilled = False
77 87
78 EricArrowItem.__init__(self, QPointF(0, 0), QPointF(100, 100), 88 EricArrowItem.__init__(
79 arrowFilled, arrowType, colors, parent) 89 self,
80 90 QPointF(0, 0),
91 QPointF(100, 100),
92 arrowFilled,
93 arrowType,
94 colors,
95 parent,
96 )
97
81 self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False) 98 self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
82 self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False) 99 self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
83 100
84 if topToBottom: 101 if topToBottom:
85 self.calculateEndingPoints = ( 102 self.calculateEndingPoints = self.__calculateEndingPoints_topToBottom
86 self.__calculateEndingPoints_topToBottom
87 )
88 else: 103 else:
89 #- self.calculateEndingPoints = self.__calculateEndingPoints_center 104 # - self.calculateEndingPoints = self.__calculateEndingPoints_center
90 self.calculateEndingPoints = self.__calculateEndingPoints_rectangle 105 self.calculateEndingPoints = self.__calculateEndingPoints_rectangle
91 106
92 self.itemA = itemA 107 self.itemA = itemA
93 self.itemB = itemB 108 self.itemB = itemB
94 self.assocType = assocType 109 self.assocType = assocType
95 self.topToBottom = topToBottom 110 self.topToBottom = topToBottom
96 111
97 self.regionA = AssociationPointRegion.NO_REGION 112 self.regionA = AssociationPointRegion.NO_REGION
98 self.regionB = AssociationPointRegion.NO_REGION 113 self.regionB = AssociationPointRegion.NO_REGION
99 114
100 self.calculateEndingPoints() 115 self.calculateEndingPoints()
101 116
102 self.itemA.addAssociation(self) 117 self.itemA.addAssociation(self)
103 self.itemB.addAssociation(self) 118 self.itemB.addAssociation(self)
104 119
105 def __mapRectFromItem(self, item): 120 def __mapRectFromItem(self, item):
106 """ 121 """
107 Private method to map item's rectangle to this item's coordinate 122 Private method to map item's rectangle to this item's coordinate
108 system. 123 system.
109 124
110 @param item reference to the item to be mapped 125 @param item reference to the item to be mapped
111 @type QGraphicsRectItem 126 @type QGraphicsRectItem
112 @return item's rectangle in local coordinates 127 @return item's rectangle in local coordinates
113 @rtype QRectF 128 @rtype QRectF
114 """ 129 """
115 rect = item.rect() 130 rect = item.rect()
116 tl = self.mapFromItem(item, rect.topLeft()) 131 tl = self.mapFromItem(item, rect.topLeft())
117 return QRectF(tl.x(), tl.y(), rect.width(), rect.height()) 132 return QRectF(tl.x(), tl.y(), rect.width(), rect.height())
118 133
119 def __calculateEndingPoints_topToBottom(self): 134 def __calculateEndingPoints_topToBottom(self):
120 """ 135 """
121 Private method to calculate the ending points of the association item. 136 Private method to calculate the ending points of the association item.
122 137
123 The ending points are calculated from the top center of the lower item 138 The ending points are calculated from the top center of the lower item
124 to the bottom center of the upper item. 139 to the bottom center of the upper item.
125 """ 140 """
126 if self.itemA is None or self.itemB is None: 141 if self.itemA is None or self.itemB is None:
127 return 142 return
128 143
129 self.prepareGeometryChange() 144 self.prepareGeometryChange()
130 145
131 rectA = self.__mapRectFromItem(self.itemA) 146 rectA = self.__mapRectFromItem(self.itemA)
132 rectB = self.__mapRectFromItem(self.itemB) 147 rectB = self.__mapRectFromItem(self.itemB)
133 midA = QPointF(rectA.x() + rectA.width() / 2.0, 148 midA = QPointF(
134 rectA.y() + rectA.height() / 2.0) 149 rectA.x() + rectA.width() / 2.0, rectA.y() + rectA.height() / 2.0
135 midB = QPointF(rectB.x() + rectB.width() / 2.0, 150 )
136 rectB.y() + rectB.height() / 2.0) 151 midB = QPointF(
152 rectB.x() + rectB.width() / 2.0, rectB.y() + rectB.height() / 2.0
153 )
137 if midA.y() > midB.y(): 154 if midA.y() > midB.y():
138 startP = QPointF(rectA.x() + rectA.width() / 2.0, rectA.y()) 155 startP = QPointF(rectA.x() + rectA.width() / 2.0, rectA.y())
139 endP = QPointF(rectB.x() + rectB.width() / 2.0, 156 endP = QPointF(rectB.x() + rectB.width() / 2.0, rectB.y() + rectB.height())
140 rectB.y() + rectB.height())
141 else: 157 else:
142 startP = QPointF(rectA.x() + rectA.width() / 2.0, 158 startP = QPointF(
143 rectA.y() + rectA.height()) 159 rectA.x() + rectA.width() / 2.0, rectA.y() + rectA.height()
160 )
144 endP = QPointF(rectB.x() + rectB.width() / 2.0, rectB.y()) 161 endP = QPointF(rectB.x() + rectB.width() / 2.0, rectB.y())
145 self.setPoints(startP.x(), startP.y(), endP.x(), endP.y()) 162 self.setPoints(startP.x(), startP.y(), endP.x(), endP.y())
146 163
147 def __calculateEndingPoints_center(self): 164 def __calculateEndingPoints_center(self):
148 """ 165 """
149 Private method to calculate the ending points of the association item. 166 Private method to calculate the ending points of the association item.
150 167
151 The ending points are calculated from the centers of the 168 The ending points are calculated from the centers of the
152 two associated items. 169 two associated items.
153 """ 170 """
154 if self.itemA is None or self.itemB is None: 171 if self.itemA is None or self.itemB is None:
155 return 172 return
156 173
157 self.prepareGeometryChange() 174 self.prepareGeometryChange()
158 175
159 rectA = self.__mapRectFromItem(self.itemA) 176 rectA = self.__mapRectFromItem(self.itemA)
160 rectB = self.__mapRectFromItem(self.itemB) 177 rectB = self.__mapRectFromItem(self.itemB)
161 midA = QPointF(rectA.x() + rectA.width() / 2.0, 178 midA = QPointF(
162 rectA.y() + rectA.height() / 2.0) 179 rectA.x() + rectA.width() / 2.0, rectA.y() + rectA.height() / 2.0
163 midB = QPointF(rectB.x() + rectB.width() / 2.0, 180 )
164 rectB.y() + rectB.height() / 2.0) 181 midB = QPointF(
182 rectB.x() + rectB.width() / 2.0, rectB.y() + rectB.height() / 2.0
183 )
165 startP = self.__findRectIntersectionPoint(self.itemA, midA, midB) 184 startP = self.__findRectIntersectionPoint(self.itemA, midA, midB)
166 endP = self.__findRectIntersectionPoint(self.itemB, midB, midA) 185 endP = self.__findRectIntersectionPoint(self.itemB, midB, midA)
167 186
168 if ( 187 if startP.x() != -1 and startP.y() != -1 and endP.x() != -1 and endP.y() != -1:
169 startP.x() != -1 and 188 # __IGNORE_WARNING_C111__
170 startP.y() != -1 and
171 endP.x() != -1 and
172 endP.y() != -1
173 ):
174 self.setPoints(startP.x(), startP.y(), endP.x(), endP.y()) 189 self.setPoints(startP.x(), startP.y(), endP.x(), endP.y())
175 190
176 def __calculateEndingPoints_rectangle(self): 191 def __calculateEndingPoints_rectangle(self):
177 r""" 192 r"""
178 Private method to calculate the ending points of the association item. 193 Private method to calculate the ending points of the association item.
179 194
180 The ending points are calculated by the following method. 195 The ending points are calculated by the following method.
181 196
182 For each item the diagram is divided in four Regions by its diagonals 197 For each item the diagram is divided in four Regions by its diagonals
183 as indicated below 198 as indicated below
184 <pre> 199 <pre>
185 +------------------------------+ 200 +------------------------------+
186 | \ Region 2 / | 201 | \ Region 2 / |
195 | |--------| | 210 | |--------| |
196 | / \ | 211 | / \ |
197 | / Region 4 \ | 212 | / Region 4 \ |
198 +------------------------------+ 213 +------------------------------+
199 </pre> 214 </pre>
200 215
201 Each diagonal is defined by two corners of the bounding rectangle. 216 Each diagonal is defined by two corners of the bounding rectangle.
202 217
203 To calculate the start point we have to find out in which 218 To calculate the start point we have to find out in which
204 region (defined by itemA's diagonals) is itemB's TopLeft corner 219 region (defined by itemA's diagonals) is itemB's TopLeft corner
205 (lets call it region M). After that the start point will be 220 (lets call it region M). After that the start point will be
206 the middle point of rectangle's side contained in region M. 221 the middle point of rectangle's side contained in region M.
207 222
208 To calculate the end point we repeat the above but in the opposite 223 To calculate the end point we repeat the above but in the opposite
209 direction (from itemB to itemA) 224 direction (from itemB to itemA)
210 """ 225 """
211 if self.itemA is None or self.itemB is None: 226 if self.itemA is None or self.itemB is None:
212 return 227 return
213 228
214 self.prepareGeometryChange() 229 self.prepareGeometryChange()
215 230
216 rectA = self.__mapRectFromItem(self.itemA) 231 rectA = self.__mapRectFromItem(self.itemA)
217 rectB = self.__mapRectFromItem(self.itemB) 232 rectB = self.__mapRectFromItem(self.itemB)
218 233
219 xA = rectA.x() + rectA.width() / 2.0 234 xA = rectA.x() + rectA.width() / 2.0
220 yA = rectA.y() + rectA.height() / 2.0 235 yA = rectA.y() + rectA.height() / 2.0
221 xB = rectB.x() + rectB.width() / 2.0 236 xB = rectB.x() + rectB.width() / 2.0
222 yB = rectB.y() + rectB.height() / 2.0 237 yB = rectB.y() + rectB.height() / 2.0
223 238
224 # find itemA region 239 # find itemA region
225 rc = QRectF(xA, yA, rectA.width(), rectA.height()) 240 rc = QRectF(xA, yA, rectA.width(), rectA.height())
226 self.regionA = self.__findPointRegion(rc, xB, yB) 241 self.regionA = self.__findPointRegion(rc, xB, yB)
227 # move some regions to the standard ones 242 # move some regions to the standard ones
228 if self.regionA == AssociationPointRegion.NORTH_WEST: 243 if self.regionA == AssociationPointRegion.NORTH_WEST:
231 self.regionA = AssociationPointRegion.EAST 246 self.regionA = AssociationPointRegion.EAST
232 elif self.regionA == AssociationPointRegion.SOUTH_EAST: 247 elif self.regionA == AssociationPointRegion.SOUTH_EAST:
233 self.regionA = AssociationPointRegion.SOUTH 248 self.regionA = AssociationPointRegion.SOUTH
234 elif self.regionA in ( 249 elif self.regionA in (
235 AssociationPointRegion.SOUTH_WEST, 250 AssociationPointRegion.SOUTH_WEST,
236 AssociationPointRegion.CENTER 251 AssociationPointRegion.CENTER,
237 ): 252 ):
238 self.regionA = AssociationPointRegion.WEST 253 self.regionA = AssociationPointRegion.WEST
239 254
240 self.__updateEndPoint(self.regionA, True) 255 self.__updateEndPoint(self.regionA, True)
241 256
242 # now do the same for itemB 257 # now do the same for itemB
243 rc = QRectF(xB, yB, rectB.width(), rectB.height()) 258 rc = QRectF(xB, yB, rectB.width(), rectB.height())
244 self.regionB = self.__findPointRegion(rc, xA, yA) 259 self.regionB = self.__findPointRegion(rc, xA, yA)
245 # move some regions to the standard ones 260 # move some regions to the standard ones
246 if self.regionB == AssociationPointRegion.NORTH_WEST: 261 if self.regionB == AssociationPointRegion.NORTH_WEST:
249 self.regionB = AssociationPointRegion.EAST 264 self.regionB = AssociationPointRegion.EAST
250 elif self.regionB == AssociationPointRegion.SOUTH_EAST: 265 elif self.regionB == AssociationPointRegion.SOUTH_EAST:
251 self.regionB = AssociationPointRegion.SOUTH 266 self.regionB = AssociationPointRegion.SOUTH
252 elif self.regionB in ( 267 elif self.regionB in (
253 AssociationPointRegion.SOUTH_WEST, 268 AssociationPointRegion.SOUTH_WEST,
254 AssociationPointRegion.CENTER 269 AssociationPointRegion.CENTER,
255 ): 270 ):
256 self.regionB = AssociationPointRegion.WEST 271 self.regionB = AssociationPointRegion.WEST
257 272
258 self.__updateEndPoint(self.regionB, False) 273 self.__updateEndPoint(self.regionB, False)
259 274
260 def __findPointRegion(self, rect, posX, posY): 275 def __findPointRegion(self, rect, posX, posY):
261 """ 276 """
262 Private method to find out, which region of rectangle rect contains 277 Private method to find out, which region of rectangle rect contains
263 the point (PosX, PosY) and returns the region number. 278 the point (PosX, PosY) and returns the region number.
264 279
265 @param rect rectangle to calculate the region for 280 @param rect rectangle to calculate the region for
266 @type QRectF 281 @type QRectF
267 @param posX x position of point 282 @param posX x position of point
268 @type float 283 @type float
269 @param posY y position of point 284 @param posY y position of point
286 y = rect.y() 301 y = rect.y()
287 slope2 = w / h 302 slope2 = w / h
288 slope1 = -slope2 303 slope1 = -slope2
289 b1 = x + w / 2.0 - y * slope1 304 b1 = x + w / 2.0 - y * slope1
290 b2 = x + w / 2.0 - y * slope2 305 b2 = x + w / 2.0 - y * slope2
291 306
292 eval1 = slope1 * posY + b1 307 eval1 = slope1 * posY + b1
293 eval2 = slope2 * posY + b2 308 eval2 = slope2 * posY + b2
294 309
295 result = AssociationPointRegion.NO_REGION 310 result = AssociationPointRegion.NO_REGION
296 311
297 # inside region 1 312 # inside region 1
298 if eval1 > posX and eval2 > posX: 313 if eval1 > posX and eval2 > posX:
299 result = AssociationPointRegion.WEST 314 result = AssociationPointRegion.WEST
300 315
301 #inside region 2 316 # inside region 2
302 elif eval1 > posX and eval2 < posX: 317 elif eval1 > posX and eval2 < posX:
303 result = AssociationPointRegion.NORTH 318 result = AssociationPointRegion.NORTH
304 319
305 # inside region 3 320 # inside region 3
306 elif eval1 < posX and eval2 < posX: 321 elif eval1 < posX and eval2 < posX:
307 result = AssociationPointRegion.EAST 322 result = AssociationPointRegion.EAST
308 323
309 # inside region 4 324 # inside region 4
310 elif eval1 < posX and eval2 > posX: 325 elif eval1 < posX and eval2 > posX:
311 result = AssociationPointRegion.SOUTH 326 result = AssociationPointRegion.SOUTH
312 327
313 # inside region 5 328 # inside region 5
314 elif eval1 == posX and eval2 < posX: 329 elif eval1 == posX and eval2 < posX:
315 result = AssociationPointRegion.NORTH_WEST 330 result = AssociationPointRegion.NORTH_WEST
316 331
317 # inside region 6 332 # inside region 6
318 elif eval1 < posX and eval2 == posX: 333 elif eval1 < posX and eval2 == posX:
319 result = AssociationPointRegion.NORTH_EAST 334 result = AssociationPointRegion.NORTH_EAST
320 335
321 # inside region 7 336 # inside region 7
322 elif eval1 == posX and eval2 > posX: 337 elif eval1 == posX and eval2 > posX:
323 result = AssociationPointRegion.SOUTH_EAST 338 result = AssociationPointRegion.SOUTH_EAST
324 339
325 # inside region 8 340 # inside region 8
326 elif eval1 > posX and eval2 == posX: 341 elif eval1 > posX and eval2 == posX:
327 result = AssociationPointRegion.SOUTH_WEST 342 result = AssociationPointRegion.SOUTH_WEST
328 343
329 # inside region 9 344 # inside region 9
330 elif eval1 == posX and eval2 == posX: 345 elif eval1 == posX and eval2 == posX:
331 result = AssociationPointRegion.CENTER 346 result = AssociationPointRegion.CENTER
332 347
333 return result 348 return result
334 349
335 def __updateEndPoint(self, region, isWidgetA): 350 def __updateEndPoint(self, region, isWidgetA):
336 """ 351 """
337 Private method to update an endpoint. 352 Private method to update an endpoint.
338 353
339 @param region the region for the endpoint 354 @param region the region for the endpoint
340 @type AssociationPointRegion 355 @type AssociationPointRegion
341 @param isWidgetA flag indicating update for itemA is done 356 @param isWidgetA flag indicating update for itemA is done
342 @type bool 357 @type bool
343 """ 358 """
344 if region == AssociationPointRegion.NO_REGION: 359 if region == AssociationPointRegion.NO_REGION:
345 return 360 return
346 361
347 rect = ( 362 rect = (
348 self.__mapRectFromItem(self.itemA) 363 self.__mapRectFromItem(self.itemA)
349 if isWidgetA else 364 if isWidgetA
350 self.__mapRectFromItem(self.itemB) 365 else self.__mapRectFromItem(self.itemB)
351 ) 366 )
352 x = rect.x() 367 x = rect.x()
353 y = rect.y() 368 y = rect.y()
354 ww = rect.width() 369 ww = rect.width()
355 wh = rect.height() 370 wh = rect.height()
356 ch = wh / 2.0 371 ch = wh / 2.0
357 cw = ww / 2.0 372 cw = ww / 2.0
358 373
359 if region == AssociationPointRegion.WEST: 374 if region == AssociationPointRegion.WEST:
360 px = x 375 px = x
361 py = y + ch 376 py = y + ch
362 elif region == AssociationPointRegion.NORTH: 377 elif region == AssociationPointRegion.NORTH:
363 px = x + cw 378 px = x + cw
364 py = y 379 py = y
365 elif region == AssociationPointRegion.EAST: 380 elif region == AssociationPointRegion.EAST:
366 px = x + ww 381 px = x + ww
367 py = y + ch 382 py = y + ch
368 elif region in ( 383 elif region in (AssociationPointRegion.SOUTH, AssociationPointRegion.CENTER):
369 AssociationPointRegion.SOUTH,
370 AssociationPointRegion.CENTER
371 ):
372 px = x + cw 384 px = x + cw
373 py = y + wh 385 py = y + wh
374 386
375 if isWidgetA: 387 if isWidgetA:
376 self.setStartPoint(px, py) 388 self.setStartPoint(px, py)
377 else: 389 else:
378 self.setEndPoint(px, py) 390 self.setEndPoint(px, py)
379 391
380 def __findRectIntersectionPoint(self, item, p1, p2): 392 def __findRectIntersectionPoint(self, item, p1, p2):
381 """ 393 """
382 Private method to find the intersection point of a line with a 394 Private method to find the intersection point of a line with a
383 rectangle. 395 rectangle.
384 396
385 @param item item to check against 397 @param item item to check against
386 @type UMLItem 398 @type UMLItem
387 @param p1 first point of the line 399 @param p1 first point of the line
388 @type QPointF 400 @type QPointF
389 @param p2 second point of the line 401 @param p2 second point of the line
394 rect = self.__mapRectFromItem(item) 406 rect = self.__mapRectFromItem(item)
395 lines = [ 407 lines = [
396 QLineF(rect.topLeft(), rect.topRight()), 408 QLineF(rect.topLeft(), rect.topRight()),
397 QLineF(rect.topLeft(), rect.bottomLeft()), 409 QLineF(rect.topLeft(), rect.bottomLeft()),
398 QLineF(rect.bottomRight(), rect.bottomLeft()), 410 QLineF(rect.bottomRight(), rect.bottomLeft()),
399 QLineF(rect.bottomRight(), rect.topRight()) 411 QLineF(rect.bottomRight(), rect.topRight()),
400 ] 412 ]
401 intersectLine = QLineF(p1, p2) 413 intersectLine = QLineF(p1, p2)
402 intersectPoint = QPointF(0, 0) 414 intersectPoint = QPointF(0, 0)
403 for line in lines: 415 for line in lines:
404 if ( 416 if (
405 intersectLine.intersect(line, intersectPoint) == 417 intersectLine.intersect(line, intersectPoint)
406 QLineF.IntersectType.BoundedIntersection 418 == QLineF.IntersectType.BoundedIntersection
407 ): 419 ):
408 return intersectPoint 420 return intersectPoint
409 return QPointF(-1.0, -1.0) 421 return QPointF(-1.0, -1.0)
410 422
411 def __findIntersection(self, p1, p2, p3, p4): 423 def __findIntersection(self, p1, p2, p3, p4):
412 """ 424 """
413 Private method to calculate the intersection point of two lines. 425 Private method to calculate the intersection point of two lines.
414 426
415 The first line is determined by the points p1 and p2, the second 427 The first line is determined by the points p1 and p2, the second
416 line by p3 and p4. If the intersection point is not contained in 428 line by p3 and p4. If the intersection point is not contained in
417 the segment p1p2, then it returns (-1.0, -1.0). 429 the segment p1p2, then it returns (-1.0, -1.0).
418 430
419 For the function's internal calculations remember:<br /> 431 For the function's internal calculations remember:<br />
420 QT coordinates start with the point (0,0) as the topleft corner 432 QT coordinates start with the point (0,0) as the topleft corner
421 and x-values increase from left to right and y-values increase 433 and x-values increase from left to right and y-values increase
422 from top to bottom; it means the visible area is quadrant I in 434 from top to bottom; it means the visible area is quadrant I in
423 the regular XY coordinate system 435 the regular XY coordinate system
424 436
425 <pre> 437 <pre>
426 Quadrant II | Quadrant I 438 Quadrant II | Quadrant I
427 -----------------|----------------- 439 -----------------|-----------------
428 Quadrant III | Quadrant IV 440 Quadrant III | Quadrant IV
429 </pre> 441 </pre>
430 442
431 In order for the linear function calculations to work in this method 443 In order for the linear function calculations to work in this method
432 we must switch x and y values (x values become y values and viceversa) 444 we must switch x and y values (x values become y values and viceversa)
433 445
434 @param p1 first point of first line 446 @param p1 first point of first line
435 @type QPointF 447 @type QPointF
436 @param p2 second point of first line 448 @param p2 second point of first line
437 @type QPointF 449 @type QPointF
438 @param p3 first point of second line 450 @param p3 first point of second line
448 y2 = p2.x() 460 y2 = p2.x()
449 x3 = p3.y() 461 x3 = p3.y()
450 y3 = p3.x() 462 y3 = p3.x()
451 x4 = p4.y() 463 x4 = p4.y()
452 y4 = p4.x() 464 y4 = p4.x()
453 465
454 # line 1 is the line between (x1, y1) and (x2, y2) 466 # line 1 is the line between (x1, y1) and (x2, y2)
455 # line 2 is the line between (x3, y3) and (x4, y4) 467 # line 2 is the line between (x3, y3) and (x4, y4)
456 no_line1 = True # it is false, if line 1 is a linear function 468 no_line1 = True # it is false, if line 1 is a linear function
457 no_line2 = True # it is false, if line 2 is a linear function 469 no_line2 = True # it is false, if line 2 is a linear function
458 slope1 = 0.0 470 slope1 = 0.0
459 slope2 = 0.0 471 slope2 = 0.0
460 b1 = 0.0 472 b1 = 0.0
461 b2 = 0.0 473 b2 = 0.0
462 474
463 if x2 != x1: 475 if x2 != x1:
464 slope1 = (y2 - y1) / (x2 - x1) 476 slope1 = (y2 - y1) / (x2 - x1)
465 b1 = y1 - slope1 * x1 477 b1 = y1 - slope1 * x1
466 no_line1 = False 478 no_line1 = False
467 if x4 != x3: 479 if x4 != x3:
468 slope2 = (y4 - y3) / (x4 - x3) 480 slope2 = (y4 - y3) / (x4 - x3)
469 b2 = y3 - slope2 * x3 481 b2 = y3 - slope2 * x3
470 no_line2 = False 482 no_line2 = False
471 483
472 pt = QPointF() 484 pt = QPointF()
473 # if either line is not a function 485 # if either line is not a function
474 if no_line1 and no_line2: 486 if no_line1 and no_line2:
475 # if the lines are not the same one 487 # if the lines are not the same one
476 if x1 != x3: 488 if x1 != x3:
508 else: 520 else:
509 if not (y3 <= pt.x() and pt.x() <= y4): 521 if not (y3 <= pt.x() and pt.x() <= y4):
510 pt.setX(-1.0) 522 pt.setX(-1.0)
511 pt.setY(-1.0) 523 pt.setY(-1.0)
512 return pt 524 return pt
513 525
514 if slope1 == slope2: 526 if slope1 == slope2:
515 pt.setX(-1.0) 527 pt.setX(-1.0)
516 pt.setY(-1.0) 528 pt.setY(-1.0)
517 return pt 529 return pt
518 530
519 pt.setY((b2 - b1) / (slope1 - slope2)) 531 pt.setY((b2 - b1) / (slope1 - slope2))
520 pt.setX(slope1 * pt.y() + b1) 532 pt.setX(slope1 * pt.y() + b1)
521 # the intersection point must be inside the segment (x1, y1) (x2, y2) 533 # the intersection point must be inside the segment (x1, y1) (x2, y2)
522 if x2 >= x1 and y2 >= y1: 534 if x2 >= x1 and y2 >= y1:
523 if not ((x1 <= pt.y() and pt.y() <= x2) and 535 if not (
524 (y1 <= pt.x() and pt.x() <= y2)): 536 (x1 <= pt.y() and pt.y() <= x2) and (y1 <= pt.x() and pt.x() <= y2)
537 ):
525 pt.setX(-1.0) 538 pt.setX(-1.0)
526 pt.setY(-1.0) 539 pt.setY(-1.0)
527 elif x2 < x1 and y2 >= y1: 540 elif x2 < x1 and y2 >= y1:
528 if not ((x2 <= pt.y() and pt.y() <= x1) and 541 if not (
529 (y1 <= pt.x() and pt.x() <= y2)): 542 (x2 <= pt.y() and pt.y() <= x1) and (y1 <= pt.x() and pt.x() <= y2)
543 ):
530 pt.setX(-1.0) 544 pt.setX(-1.0)
531 pt.setY(-1.0) 545 pt.setY(-1.0)
532 elif x2 >= x1 and y2 < y1: 546 elif x2 >= x1 and y2 < y1:
533 if not ((x1 <= pt.y() and pt.y() <= x2) and 547 if not (
534 (y2 <= pt.x() and pt.x() <= y1)): 548 (x1 <= pt.y() and pt.y() <= x2) and (y2 <= pt.x() and pt.x() <= y1)
549 ):
535 pt.setX(-1.0) 550 pt.setX(-1.0)
536 pt.setY(-1.0) 551 pt.setY(-1.0)
537 else: 552 else:
538 if not ((x2 <= pt.y() and pt.y() <= x1) and 553 if not (
539 (y2 <= pt.x() and pt.x() <= y1)): 554 (x2 <= pt.y() and pt.y() <= x1) and (y2 <= pt.x() and pt.x() <= y1)
555 ):
540 pt.setX(-1.0) 556 pt.setX(-1.0)
541 pt.setY(-1.0) 557 pt.setY(-1.0)
542 558
543 return pt 559 return pt
544 560
545 def widgetMoved(self): 561 def widgetMoved(self):
546 """ 562 """
547 Public method to recalculate the association after a widget was moved. 563 Public method to recalculate the association after a widget was moved.
548 """ 564 """
549 self.calculateEndingPoints() 565 self.calculateEndingPoints()
550 566
551 def unassociate(self): 567 def unassociate(self):
552 """ 568 """
553 Public method to unassociate from the widgets. 569 Public method to unassociate from the widgets.
554 """ 570 """
555 self.itemA.removeAssociation(self) 571 self.itemA.removeAssociation(self)
556 self.itemB.removeAssociation(self) 572 self.itemB.removeAssociation(self)
557 573
558 @classmethod 574 @classmethod
559 def parseAssociationItemDataString(cls, data): 575 def parseAssociationItemDataString(cls, data):
560 """ 576 """
561 Class method to parse the given persistence data. 577 Class method to parse the given persistence data.
562 578
563 @param data persisted data to be parsed 579 @param data persisted data to be parsed
564 @type str 580 @type str
565 @return tuple with the IDs of the source and destination items, 581 @return tuple with the IDs of the source and destination items,
566 the association type and a flag indicating to associate from top 582 the association type and a flag indicating to associate from top
567 to bottom 583 to bottom
580 dst = int(value) 596 dst = int(value)
581 elif key == "type": 597 elif key == "type":
582 assocType = AssociationType(int(value)) 598 assocType = AssociationType(int(value))
583 elif key == "topToBottom": 599 elif key == "topToBottom":
584 topToBottom = Utilities.toBool(value) 600 topToBottom = Utilities.toBool(value)
585 601
586 return src, dst, assocType, topToBottom 602 return src, dst, assocType, topToBottom
587 603
588 def toDict(self): 604 def toDict(self):
589 """ 605 """
590 Public method to collect data to be persisted. 606 Public method to collect data to be persisted.
591 607
592 @return dictionary containing data to be persisted 608 @return dictionary containing data to be persisted
593 @rtype dict 609 @rtype dict
594 """ 610 """
595 return { 611 return {
596 "src": self.itemA.getId(), 612 "src": self.itemA.getId(),
597 "dst": self.itemB.getId(), 613 "dst": self.itemB.getId(),
598 "type": self.assocType.value, 614 "type": self.assocType.value,
599 "topToBottom": self.topToBottom, 615 "topToBottom": self.topToBottom,
600 } 616 }
601 617
602 @classmethod 618 @classmethod
603 def fromDict(cls, data, umlItems, colors=None): 619 def fromDict(cls, data, umlItems, colors=None):
604 """ 620 """
605 Class method to create an association item from persisted data. 621 Class method to create an association item from persisted data.
606 622
607 @param data dictionary containing the persisted data as generated 623 @param data dictionary containing the persisted data as generated
608 by toDict() 624 by toDict()
609 @type dict 625 @type dict
610 @param umlItems list of UML items 626 @param umlItems list of UML items
611 @type list of UMLItem 627 @type list of UMLItem
613 @type tuple of (QColor, QColor) 629 @type tuple of (QColor, QColor)
614 @return created association item 630 @return created association item
615 @rtype AssociationItem 631 @rtype AssociationItem
616 """ 632 """
617 try: 633 try:
618 return cls(umlItems[data["src"]], 634 return cls(
619 umlItems[data["dst"]], 635 umlItems[data["src"]],
620 assocType=AssociationType(data["type"]), 636 umlItems[data["dst"]],
621 topToBottom=data["topToBottom"], 637 assocType=AssociationType(data["type"]),
622 colors=colors) 638 topToBottom=data["topToBottom"],
639 colors=colors,
640 )
623 except (KeyError, ValueError): 641 except (KeyError, ValueError):
624 return None 642 return None

eric ide

mercurial