|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a graphics item subclass for an arrow. |
|
8 """ |
|
9 |
|
10 import enum |
|
11 import math |
|
12 |
|
13 from PyQt6.QtCore import QPointF, QRectF, QSizeF, QLineF, Qt |
|
14 from PyQt6.QtGui import QPen, QPolygonF, QColor |
|
15 from PyQt6.QtWidgets import QAbstractGraphicsShapeItem, QGraphicsItem, QStyle |
|
16 |
|
17 ArrowheadAngleFactor = 0.26179938779914941 |
|
18 # That is: 0.5 * math.atan(math.sqrt(3.0) / 3.0) |
|
19 |
|
20 |
|
21 class EricArrowType(enum.Enum): |
|
22 """ |
|
23 Class defining the arrow types. |
|
24 """ |
|
25 NORMAL = 1 |
|
26 WIDE = 2 |
|
27 |
|
28 |
|
29 class EricArrowItem(QAbstractGraphicsShapeItem): |
|
30 """ |
|
31 Class implementing an arrow graphics item subclass. |
|
32 """ |
|
33 def __init__(self, origin=None, end=None, |
|
34 filled=False, arrowType=EricArrowType.NORMAL, colors=None, |
|
35 parent=None): |
|
36 """ |
|
37 Constructor |
|
38 |
|
39 @param origin origin of the arrow |
|
40 @type QPointF |
|
41 @param end end point of the arrow |
|
42 @type QPointF |
|
43 @param filled flag indicating a filled arrow head |
|
44 @type bool |
|
45 @param arrowType arrow type |
|
46 @type EricArrowType |
|
47 @param colors tuple containing the foreground and background colors |
|
48 @type tuple of (QColor, QColor) |
|
49 @param parent reference to the parent object |
|
50 @type QGraphicsItem |
|
51 """ |
|
52 super().__init__(parent) |
|
53 |
|
54 self._origin = QPointF() if origin is None else QPointF(origin) |
|
55 self._end = QPointF() if end is None else QPointF(end) |
|
56 self._filled = filled |
|
57 self.__type = arrowType |
|
58 |
|
59 if colors is None: |
|
60 self._colors = (QColor(Qt.GlobalColor.black), |
|
61 QColor(Qt.GlobalColor.white)) |
|
62 else: |
|
63 self._colors = colors |
|
64 |
|
65 self._halfLength = 13.0 |
|
66 |
|
67 self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True) |
|
68 self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True) |
|
69 |
|
70 def setPoints(self, xa, ya, xb, yb): |
|
71 """ |
|
72 Public method to set the start and end points of the line. |
|
73 |
|
74 <b>Note:</b> This method does not redraw the item. |
|
75 |
|
76 @param xa x-coordinate of the start point (float) |
|
77 @param ya y-coordinate of the start point (float) |
|
78 @param xb x-coordinate of the end point (float) |
|
79 @param yb y-coordinate of the end point (float) |
|
80 """ |
|
81 self._origin = QPointF(xa, ya) |
|
82 self._end = QPointF(xb, yb) |
|
83 |
|
84 def setStartPoint(self, x, y): |
|
85 """ |
|
86 Public method to set the start point. |
|
87 |
|
88 <b>Note:</b> This method does not redraw the item. |
|
89 |
|
90 @param x x-coordinate of the start point (float) |
|
91 @param y y-coordinate of the start point (float) |
|
92 """ |
|
93 self._origin = QPointF(x, y) |
|
94 |
|
95 def setEndPoint(self, x, y): |
|
96 """ |
|
97 Public method to set the end point. |
|
98 |
|
99 <b>Note:</b> This method does not redraw the item. |
|
100 |
|
101 @param x x-coordinate of the end point (float) |
|
102 @param y y-coordinate of the end point (float) |
|
103 """ |
|
104 self._end = QPointF(x, y) |
|
105 |
|
106 def boundingRect(self): |
|
107 """ |
|
108 Public method to return the bounding rectangle. |
|
109 |
|
110 @return bounding rectangle (QRectF) |
|
111 """ |
|
112 extra = self._halfLength / 2.0 |
|
113 return QRectF(self._origin, QSizeF(self._end.x() - self._origin.x(), |
|
114 self._end.y() - self._origin.y()) |
|
115 ).normalized().adjusted(-extra, -extra, extra, extra) |
|
116 |
|
117 def paint(self, painter, option, widget=None): |
|
118 """ |
|
119 Public method to paint the item in local coordinates. |
|
120 |
|
121 @param painter reference to the painter object (QPainter) |
|
122 @param option style options (QStyleOptionGraphicsItem) |
|
123 @param widget optional reference to the widget painted on (QWidget) |
|
124 """ |
|
125 width = 2 if ( |
|
126 (option.state & QStyle.StateFlag.State_Selected) == |
|
127 QStyle.StateFlag.State_Selected |
|
128 ) else 1 |
|
129 |
|
130 # draw the line first |
|
131 line = QLineF(self._origin, self._end) |
|
132 painter.setPen( |
|
133 QPen(self._colors[0], width, Qt.PenStyle.SolidLine, |
|
134 Qt.PenCapStyle.FlatCap, Qt.PenJoinStyle.MiterJoin)) |
|
135 painter.drawLine(line) |
|
136 |
|
137 # draw the arrow head |
|
138 arrowAngle = ( |
|
139 ArrowheadAngleFactor |
|
140 if self.__type == EricArrowType.NORMAL else |
|
141 2 * ArrowheadAngleFactor |
|
142 ) |
|
143 slope = math.atan2(line.dy(), line.dx()) |
|
144 |
|
145 # Calculate left arrow point |
|
146 arrowSlope = slope + arrowAngle |
|
147 a1 = QPointF(self._end.x() - self._halfLength * math.cos(arrowSlope), |
|
148 self._end.y() - self._halfLength * math.sin(arrowSlope)) |
|
149 |
|
150 # Calculate right arrow point |
|
151 arrowSlope = slope - arrowAngle |
|
152 a2 = QPointF(self._end.x() - self._halfLength * math.cos(arrowSlope), |
|
153 self._end.y() - self._halfLength * math.sin(arrowSlope)) |
|
154 |
|
155 if self._filled: |
|
156 painter.setBrush(self._colors[0]) |
|
157 else: |
|
158 painter.setBrush(self._colors[1]) |
|
159 polygon = QPolygonF() |
|
160 polygon.append(line.p2()) |
|
161 polygon.append(a1) |
|
162 polygon.append(a2) |
|
163 painter.drawPolygon(polygon) |