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