src/eric7/Graphics/AssociationItem.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
diff -r e9e7eca7efee -r bf71ee032bb4 src/eric7/Graphics/AssociationItem.py
--- a/src/eric7/Graphics/AssociationItem.py	Wed Jul 13 11:16:20 2022 +0200
+++ b/src/eric7/Graphics/AssociationItem.py	Wed Jul 13 14:55:47 2022 +0200
@@ -21,6 +21,7 @@
     """
     Class defining the association types.
     """
+
     NORMAL = 0
     GENERALISATION = 1
     IMPORTS = 2
@@ -30,6 +31,7 @@
     """
     Class defining the regions for an association end point.
     """
+
     NO_REGION = 0
     WEST = 1
     NORTH = 2
@@ -45,15 +47,23 @@
 class AssociationItem(EricArrowItem):
     """
     Class implementing a graphics item for an association between two items.
-    
+
     The association is drawn as an arrow starting at the first items and
     ending at the second.
     """
-    def __init__(self, itemA, itemB, assocType=AssociationType.NORMAL,
-                 topToBottom=False, colors=None, parent=None):
+
+    def __init__(
+        self,
+        itemA,
+        itemB,
+        assocType=AssociationType.NORMAL,
+        topToBottom=False,
+        colors=None,
+        parent=None,
+    ):
         """
         Constructor
-        
+
         @param itemA first widget of the association
         @type UMLItem
         @param itemB second widget of the association
@@ -74,39 +84,44 @@
         elif assocType == AssociationType.GENERALISATION:
             arrowType = EricArrowType.WIDE
             arrowFilled = False
-        
-        EricArrowItem.__init__(self, QPointF(0, 0), QPointF(100, 100),
-                               arrowFilled, arrowType, colors, parent)
-        
+
+        EricArrowItem.__init__(
+            self,
+            QPointF(0, 0),
+            QPointF(100, 100),
+            arrowFilled,
+            arrowType,
+            colors,
+            parent,
+        )
+
         self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
         self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
-        
+
         if topToBottom:
-            self.calculateEndingPoints = (
-                self.__calculateEndingPoints_topToBottom
-            )
+            self.calculateEndingPoints = self.__calculateEndingPoints_topToBottom
         else:
-            #- self.calculateEndingPoints = self.__calculateEndingPoints_center
+            # - self.calculateEndingPoints = self.__calculateEndingPoints_center
             self.calculateEndingPoints = self.__calculateEndingPoints_rectangle
-        
+
         self.itemA = itemA
         self.itemB = itemB
         self.assocType = assocType
         self.topToBottom = topToBottom
-        
+
         self.regionA = AssociationPointRegion.NO_REGION
         self.regionB = AssociationPointRegion.NO_REGION
-        
+
         self.calculateEndingPoints()
-        
+
         self.itemA.addAssociation(self)
         self.itemB.addAssociation(self)
-        
+
     def __mapRectFromItem(self, item):
         """
         Private method to map item's rectangle to this item's coordinate
         system.
-        
+
         @param item reference to the item to be mapped
         @type QGraphicsRectItem
         @return item's rectangle in local coordinates
@@ -115,70 +130,70 @@
         rect = item.rect()
         tl = self.mapFromItem(item, rect.topLeft())
         return QRectF(tl.x(), tl.y(), rect.width(), rect.height())
-        
+
     def __calculateEndingPoints_topToBottom(self):
         """
         Private method to calculate the ending points of the association item.
-        
+
         The ending points are calculated from the top center of the lower item
         to the bottom center of the upper item.
         """
         if self.itemA is None or self.itemB is None:
             return
-        
+
         self.prepareGeometryChange()
-        
+
         rectA = self.__mapRectFromItem(self.itemA)
         rectB = self.__mapRectFromItem(self.itemB)
-        midA = QPointF(rectA.x() + rectA.width() / 2.0,
-                       rectA.y() + rectA.height() / 2.0)
-        midB = QPointF(rectB.x() + rectB.width() / 2.0,
-                       rectB.y() + rectB.height() / 2.0)
+        midA = QPointF(
+            rectA.x() + rectA.width() / 2.0, rectA.y() + rectA.height() / 2.0
+        )
+        midB = QPointF(
+            rectB.x() + rectB.width() / 2.0, rectB.y() + rectB.height() / 2.0
+        )
         if midA.y() > midB.y():
             startP = QPointF(rectA.x() + rectA.width() / 2.0, rectA.y())
-            endP = QPointF(rectB.x() + rectB.width() / 2.0,
-                           rectB.y() + rectB.height())
+            endP = QPointF(rectB.x() + rectB.width() / 2.0, rectB.y() + rectB.height())
         else:
-            startP = QPointF(rectA.x() + rectA.width() / 2.0,
-                             rectA.y() + rectA.height())
+            startP = QPointF(
+                rectA.x() + rectA.width() / 2.0, rectA.y() + rectA.height()
+            )
             endP = QPointF(rectB.x() + rectB.width() / 2.0, rectB.y())
         self.setPoints(startP.x(), startP.y(), endP.x(), endP.y())
-    
+
     def __calculateEndingPoints_center(self):
         """
         Private method to calculate the ending points of the association item.
-        
+
         The ending points are calculated from the centers of the
         two associated items.
         """
         if self.itemA is None or self.itemB is None:
             return
-        
+
         self.prepareGeometryChange()
-        
+
         rectA = self.__mapRectFromItem(self.itemA)
         rectB = self.__mapRectFromItem(self.itemB)
-        midA = QPointF(rectA.x() + rectA.width() / 2.0,
-                       rectA.y() + rectA.height() / 2.0)
-        midB = QPointF(rectB.x() + rectB.width() / 2.0,
-                       rectB.y() + rectB.height() / 2.0)
+        midA = QPointF(
+            rectA.x() + rectA.width() / 2.0, rectA.y() + rectA.height() / 2.0
+        )
+        midB = QPointF(
+            rectB.x() + rectB.width() / 2.0, rectB.y() + rectB.height() / 2.0
+        )
         startP = self.__findRectIntersectionPoint(self.itemA, midA, midB)
         endP = self.__findRectIntersectionPoint(self.itemB, midB, midA)
-        
-        if (
-            startP.x() != -1 and
-            startP.y() != -1 and
-            endP.x() != -1 and
-            endP.y() != -1
-        ):
+
+        if startP.x() != -1 and startP.y() != -1 and endP.x() != -1 and endP.y() != -1:
+            # __IGNORE_WARNING_C111__
             self.setPoints(startP.x(), startP.y(), endP.x(), endP.y())
-        
+
     def __calculateEndingPoints_rectangle(self):
         r"""
         Private method to calculate the ending points of the association item.
-        
+
         The ending points are calculated by the following method.
-        
+
         For each item the diagram is divided in four Regions by its diagonals
         as indicated below
         <pre>
@@ -197,30 +212,30 @@
             |        /  Region 4  \        |
             +------------------------------+
         </pre>
-        
+
         Each diagonal is defined by two corners of the bounding rectangle.
-        
+
         To calculate the start point  we have to find out in which
         region (defined by itemA's diagonals) is itemB's TopLeft corner
         (lets call it region M). After that the start point will be
         the middle point of rectangle's side contained in region M.
-        
+
         To calculate the end point we repeat the above but in the opposite
         direction (from itemB to itemA)
         """
         if self.itemA is None or self.itemB is None:
             return
-        
+
         self.prepareGeometryChange()
-        
+
         rectA = self.__mapRectFromItem(self.itemA)
         rectB = self.__mapRectFromItem(self.itemB)
-        
+
         xA = rectA.x() + rectA.width() / 2.0
         yA = rectA.y() + rectA.height() / 2.0
         xB = rectB.x() + rectB.width() / 2.0
         yB = rectB.y() + rectB.height() / 2.0
-        
+
         # find itemA region
         rc = QRectF(xA, yA, rectA.width(), rectA.height())
         self.regionA = self.__findPointRegion(rc, xB, yB)
@@ -233,12 +248,12 @@
             self.regionA = AssociationPointRegion.SOUTH
         elif self.regionA in (
             AssociationPointRegion.SOUTH_WEST,
-            AssociationPointRegion.CENTER
+            AssociationPointRegion.CENTER,
         ):
             self.regionA = AssociationPointRegion.WEST
-        
+
         self.__updateEndPoint(self.regionA, True)
-        
+
         # now do the same for itemB
         rc = QRectF(xB, yB, rectB.width(), rectB.height())
         self.regionB = self.__findPointRegion(rc, xA, yA)
@@ -251,17 +266,17 @@
             self.regionB = AssociationPointRegion.SOUTH
         elif self.regionB in (
             AssociationPointRegion.SOUTH_WEST,
-            AssociationPointRegion.CENTER
+            AssociationPointRegion.CENTER,
         ):
             self.regionB = AssociationPointRegion.WEST
-        
+
         self.__updateEndPoint(self.regionB, False)
-        
+
     def __findPointRegion(self, rect, posX, posY):
         """
         Private method to find out, which region of rectangle rect contains
         the point (PosX, PosY) and returns the region number.
-        
+
         @param rect rectangle to calculate the region for
         @type QRectF
         @param posX x position of point
@@ -288,54 +303,54 @@
         slope1 = -slope2
         b1 = x + w / 2.0 - y * slope1
         b2 = x + w / 2.0 - y * slope2
-        
+
         eval1 = slope1 * posY + b1
         eval2 = slope2 * posY + b2
-        
+
         result = AssociationPointRegion.NO_REGION
-        
+
         # inside region 1
         if eval1 > posX and eval2 > posX:
             result = AssociationPointRegion.WEST
-        
-        #inside region 2
+
+        # inside region 2
         elif eval1 > posX and eval2 < posX:
             result = AssociationPointRegion.NORTH
-        
+
         # inside region 3
         elif eval1 < posX and eval2 < posX:
             result = AssociationPointRegion.EAST
-        
+
         # inside region 4
         elif eval1 < posX and eval2 > posX:
             result = AssociationPointRegion.SOUTH
-        
+
         # inside region 5
         elif eval1 == posX and eval2 < posX:
             result = AssociationPointRegion.NORTH_WEST
-        
+
         # inside region 6
         elif eval1 < posX and eval2 == posX:
             result = AssociationPointRegion.NORTH_EAST
-        
+
         # inside region 7
         elif eval1 == posX and eval2 > posX:
             result = AssociationPointRegion.SOUTH_EAST
-        
+
         # inside region 8
         elif eval1 > posX and eval2 == posX:
             result = AssociationPointRegion.SOUTH_WEST
-        
+
         # inside region 9
         elif eval1 == posX and eval2 == posX:
             result = AssociationPointRegion.CENTER
-        
+
         return result
-        
+
     def __updateEndPoint(self, region, isWidgetA):
         """
         Private method to update an endpoint.
-        
+
         @param region the region for the endpoint
         @type AssociationPointRegion
         @param isWidgetA flag indicating update for itemA is done
@@ -343,11 +358,11 @@
         """
         if region == AssociationPointRegion.NO_REGION:
             return
-        
+
         rect = (
             self.__mapRectFromItem(self.itemA)
-            if isWidgetA else
-            self.__mapRectFromItem(self.itemB)
+            if isWidgetA
+            else self.__mapRectFromItem(self.itemB)
         )
         x = rect.x()
         y = rect.y()
@@ -355,7 +370,7 @@
         wh = rect.height()
         ch = wh / 2.0
         cw = ww / 2.0
-        
+
         if region == AssociationPointRegion.WEST:
             px = x
             py = y + ch
@@ -365,23 +380,20 @@
         elif region == AssociationPointRegion.EAST:
             px = x + ww
             py = y + ch
-        elif region in (
-            AssociationPointRegion.SOUTH,
-            AssociationPointRegion.CENTER
-        ):
+        elif region in (AssociationPointRegion.SOUTH, AssociationPointRegion.CENTER):
             px = x + cw
             py = y + wh
-        
+
         if isWidgetA:
             self.setStartPoint(px, py)
         else:
             self.setEndPoint(px, py)
-        
+
     def __findRectIntersectionPoint(self, item, p1, p2):
         """
         Private method to find the intersection point of a line with a
         rectangle.
-        
+
         @param item item to check against
         @type UMLItem
         @param p1 first point of the line
@@ -396,41 +408,41 @@
             QLineF(rect.topLeft(), rect.topRight()),
             QLineF(rect.topLeft(), rect.bottomLeft()),
             QLineF(rect.bottomRight(), rect.bottomLeft()),
-            QLineF(rect.bottomRight(), rect.topRight())
+            QLineF(rect.bottomRight(), rect.topRight()),
         ]
         intersectLine = QLineF(p1, p2)
         intersectPoint = QPointF(0, 0)
         for line in lines:
             if (
-                intersectLine.intersect(line, intersectPoint) ==
-                QLineF.IntersectType.BoundedIntersection
+                intersectLine.intersect(line, intersectPoint)
+                == QLineF.IntersectType.BoundedIntersection
             ):
                 return intersectPoint
         return QPointF(-1.0, -1.0)
-        
+
     def __findIntersection(self, p1, p2, p3, p4):
         """
         Private method to calculate the intersection point of two lines.
-        
+
         The first line is determined by the points p1 and p2, the second
         line by p3 and p4. If the intersection point is not contained in
         the segment p1p2, then it returns (-1.0, -1.0).
-        
+
         For the function's internal calculations remember:<br />
         QT coordinates start with the point (0,0) as the topleft corner
         and x-values increase from left to right and y-values increase
         from top to bottom; it means the visible area is quadrant I in
         the regular XY coordinate system
-        
+
         <pre>
             Quadrant II     |   Quadrant I
            -----------------|-----------------
             Quadrant III    |   Quadrant IV
         </pre>
-        
+
         In order for the linear function calculations to work in this method
         we must switch x and y values (x values become y values and viceversa)
-        
+
         @param p1 first point of first line
         @type QPointF
         @param p2 second point of first line
@@ -450,16 +462,16 @@
         y3 = p3.x()
         x4 = p4.y()
         y4 = p4.x()
-        
+
         # line 1 is the line between (x1, y1) and (x2, y2)
         # line 2 is the line between (x3, y3) and (x4, y4)
-        no_line1 = True    # it is false, if line 1 is a linear function
-        no_line2 = True    # it is false, if line 2 is a linear function
+        no_line1 = True  # it is false, if line 1 is a linear function
+        no_line2 = True  # it is false, if line 2 is a linear function
         slope1 = 0.0
         slope2 = 0.0
         b1 = 0.0
         b2 = 0.0
-        
+
         if x2 != x1:
             slope1 = (y2 - y1) / (x2 - x1)
             b1 = y1 - slope1 * x1
@@ -468,7 +480,7 @@
             slope2 = (y4 - y3) / (x4 - x3)
             b2 = y3 - slope2 * x3
             no_line2 = False
-        
+
         pt = QPointF()
         # if either line is not a function
         if no_line1 and no_line2:
@@ -510,56 +522,60 @@
                     pt.setX(-1.0)
                     pt.setY(-1.0)
             return pt
-        
+
         if slope1 == slope2:
             pt.setX(-1.0)
             pt.setY(-1.0)
             return pt
-        
+
         pt.setY((b2 - b1) / (slope1 - slope2))
         pt.setX(slope1 * pt.y() + b1)
         # the intersection point must be inside the segment (x1, y1) (x2, y2)
         if x2 >= x1 and y2 >= y1:
-            if not ((x1 <= pt.y() and pt.y() <= x2) and
-                    (y1 <= pt.x() and pt.x() <= y2)):
+            if not (
+                (x1 <= pt.y() and pt.y() <= x2) and (y1 <= pt.x() and pt.x() <= y2)
+            ):
                 pt.setX(-1.0)
                 pt.setY(-1.0)
         elif x2 < x1 and y2 >= y1:
-            if not ((x2 <= pt.y() and pt.y() <= x1) and
-                    (y1 <= pt.x() and pt.x() <= y2)):
+            if not (
+                (x2 <= pt.y() and pt.y() <= x1) and (y1 <= pt.x() and pt.x() <= y2)
+            ):
                 pt.setX(-1.0)
                 pt.setY(-1.0)
         elif x2 >= x1 and y2 < y1:
-            if not ((x1 <= pt.y() and pt.y() <= x2) and
-                    (y2 <= pt.x() and pt.x() <= y1)):
+            if not (
+                (x1 <= pt.y() and pt.y() <= x2) and (y2 <= pt.x() and pt.x() <= y1)
+            ):
                 pt.setX(-1.0)
                 pt.setY(-1.0)
         else:
-            if not ((x2 <= pt.y() and pt.y() <= x1) and
-                    (y2 <= pt.x() and pt.x() <= y1)):
+            if not (
+                (x2 <= pt.y() and pt.y() <= x1) and (y2 <= pt.x() and pt.x() <= y1)
+            ):
                 pt.setX(-1.0)
                 pt.setY(-1.0)
-        
+
         return pt
-        
+
     def widgetMoved(self):
         """
         Public method to recalculate the association after a widget was moved.
         """
         self.calculateEndingPoints()
-        
+
     def unassociate(self):
         """
         Public method to unassociate from the widgets.
         """
         self.itemA.removeAssociation(self)
         self.itemB.removeAssociation(self)
-    
+
     @classmethod
     def parseAssociationItemDataString(cls, data):
         """
         Class method to parse the given persistence data.
-        
+
         @param data persisted data to be parsed
         @type str
         @return tuple with the IDs of the source and destination items,
@@ -582,13 +598,13 @@
                     assocType = AssociationType(int(value))
                 elif key == "topToBottom":
                     topToBottom = Utilities.toBool(value)
-        
+
         return src, dst, assocType, topToBottom
-    
+
     def toDict(self):
         """
         Public method to collect data to be persisted.
-        
+
         @return dictionary containing data to be persisted
         @rtype dict
         """
@@ -598,12 +614,12 @@
             "type": self.assocType.value,
             "topToBottom": self.topToBottom,
         }
-    
+
     @classmethod
     def fromDict(cls, data, umlItems, colors=None):
         """
         Class method to create an association item from persisted data.
-        
+
         @param data dictionary containing the persisted data as generated
             by toDict()
         @type dict
@@ -615,10 +631,12 @@
         @rtype AssociationItem
         """
         try:
-            return cls(umlItems[data["src"]],
-                       umlItems[data["dst"]],
-                       assocType=AssociationType(data["type"]),
-                       topToBottom=data["topToBottom"],
-                       colors=colors)
+            return cls(
+                umlItems[data["src"]],
+                umlItems[data["dst"]],
+                assocType=AssociationType(data["type"]),
+                topToBottom=data["topToBottom"],
+                colors=colors,
+            )
         except (KeyError, ValueError):
             return None

eric ide

mercurial