1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtGui/QCursor> |
5 | #include <QtWidgets/QGraphicsSceneMouseEvent> |
6 | #include <QtWidgets/QGraphicsSceneHoverEvent> |
7 | #include <QtWidgets/QGraphicsScene> |
8 | #include <QtCharts/QLegendMarker> |
9 | #include <QtCharts/QChart> |
10 | #include <private/legendlayout_p.h> |
11 | #include <private/legendmoveresizehandler_p.h> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | LegendMoveResizeHandler::LegendMoveResizeHandler(QLegend *legend) : |
16 | m_legend(legend) |
17 | { |
18 | m_legend->setAcceptHoverEvents(true); |
19 | m_legend->setCursor(Qt::ArrowCursor); |
20 | } |
21 | |
22 | LegendMoveResizeHandler::~LegendMoveResizeHandler() |
23 | { |
24 | } |
25 | |
26 | void LegendMoveResizeHandler::reset() |
27 | { |
28 | m_action = Action::Idle; |
29 | setMouseCursor(MousePosition::Nowhere); |
30 | } |
31 | |
32 | void LegendMoveResizeHandler::handleMousePressEvent(QGraphicsSceneMouseEvent *event) |
33 | { |
34 | determineMousePosition(fromPoint: event->pos()); |
35 | m_moveOffset = event->pos(); |
36 | m_action = Action::Pressed; |
37 | |
38 | // Since the legend does not have a proper frame, |
39 | // with a title bar, we have replaced the "top" from |
40 | // being a resize bar to being a move area. This |
41 | // is evident to the user as the mouse cursor |
42 | // changes to an open hand while over the top |
43 | // in hover mode, and a closed hand when the mouse |
44 | // button is pressed. |
45 | if (m_legend->isAttachedToChart()) { |
46 | m_action = Action::Idle; |
47 | } else if (m_mode == MousePosition::Top) { |
48 | m_action = Action::Moving; |
49 | setMouseCursor(); |
50 | } else if (m_mode != MousePosition::Nowhere) { |
51 | m_action = Action::Resizing; |
52 | } |
53 | } |
54 | |
55 | void LegendMoveResizeHandler::handleMouseMoveEvent(QGraphicsSceneMouseEvent *event) |
56 | { |
57 | QRectF geom = m_legend->geometry(); |
58 | QMarginsF reattachMargins{m_reattachThreshold, m_reattachThreshold, m_reattachThreshold, m_reattachThreshold}; |
59 | QRectF dragArea = m_legend->parentWidget()->geometry() - reattachMargins; |
60 | |
61 | if (m_action == Action::Moving) { |
62 | bool reattach = true; |
63 | QPointF toPoint = event->scenePos() - m_moveOffset; |
64 | |
65 | if (event->scenePos().x() <= dragArea.left()) |
66 | m_legend->setAlignment(Qt::AlignLeft); |
67 | else if (event->scenePos().x() >= dragArea.right()) |
68 | m_legend->setAlignment(Qt::AlignRight); |
69 | else if (event->scenePos().y() <= dragArea.top()) |
70 | m_legend->setAlignment(Qt::AlignTop); |
71 | else if (event->scenePos().y() >= dragArea.bottom()) |
72 | m_legend->setAlignment(Qt::AlignBottom); |
73 | else |
74 | reattach = false; |
75 | |
76 | QRectF potentialGeom(geom); |
77 | potentialGeom.moveTopLeft(p: toPoint); |
78 | if (potentialGeom.left() <= dragArea.left()) |
79 | toPoint.setX(dragArea.left()); |
80 | else if (potentialGeom.right() >= dragArea.right()) |
81 | toPoint.setX(dragArea.right() - geom.width()); |
82 | |
83 | if (potentialGeom.top() <= dragArea.top()) |
84 | toPoint.setY(dragArea.top()); |
85 | else if (potentialGeom.bottom() >= dragArea.bottom()) |
86 | toPoint.setY(dragArea.bottom() - geom.height()); |
87 | |
88 | // Set the geometry to be the new desired position corrected to |
89 | // be within the bounds of the dragArea. |
90 | geom.moveTopLeft(p: toPoint); |
91 | if (geom != m_legend->geometry()) |
92 | m_legend->setGeometry(geom); |
93 | |
94 | if (reattach && !m_legend->isAttachedToChart()) { |
95 | m_action = Action::Idle; |
96 | m_mode = MousePosition::Nowhere; |
97 | setMouseCursor(); |
98 | m_legend->attachToChart(); |
99 | } |
100 | } else if (m_action == Action::Resizing) { |
101 | QPointF trackPoint = event->scenePos(); |
102 | QRectF boundRect = m_legend->parentWidget()->geometry(); |
103 | |
104 | if (trackPoint.x() <= boundRect.left()) |
105 | trackPoint.rx() = boundRect.left() + 1; |
106 | else if (trackPoint.x() >= boundRect.right()) |
107 | trackPoint.rx() = boundRect.right() - 1; |
108 | |
109 | if (trackPoint.y() <= boundRect.top()) |
110 | trackPoint.ry() = boundRect.top() + 1; |
111 | else if (trackPoint.y() >= boundRect.bottom()) |
112 | trackPoint.ry() = boundRect.bottom(); |
113 | |
114 | switch (m_mode) { |
115 | case MousePosition::TopLeft: |
116 | geom = QRectF(trackPoint, geom.bottomRight()); |
117 | break; |
118 | case MousePosition::BottomRight: |
119 | geom = QRectF(geom.topLeft(), trackPoint); |
120 | break; |
121 | case MousePosition::BottomLeft: |
122 | geom = QRectF(QPointF(trackPoint.x(), geom.y()), QPointF(geom.right(), trackPoint.y())); |
123 | break; |
124 | case MousePosition::TopRight: |
125 | geom = QRectF(QPointF(geom.x(), trackPoint.y()), QPoint(trackPoint.x(), geom.bottom())); |
126 | break; |
127 | case MousePosition::Bottom: |
128 | geom = QRectF(geom.topLeft(), QPointF(geom.right(), trackPoint.y())); |
129 | break; |
130 | case MousePosition::Left: |
131 | geom = QRectF(QPointF(trackPoint.x(), geom.top()), geom.bottomRight()); |
132 | break; |
133 | case MousePosition::Right: |
134 | geom = QRectF(geom.topLeft(), QPointF(trackPoint.x(), geom.bottom())); |
135 | break; |
136 | default: |
137 | break; |
138 | } |
139 | |
140 | geom = QRectF(geom.topLeft(), |
141 | geom.size().expandedTo(otherSize: m_legend->d_ptr->m_layout->minimumSize()) |
142 | .boundedTo(otherSize: (boundRect & geom).size())); |
143 | |
144 | if (geom.size() != m_legend->geometry().size()) |
145 | m_legend->setGeometry(geom); |
146 | } |
147 | } |
148 | |
149 | void LegendMoveResizeHandler::handleMouseReleaseEvent(QGraphicsSceneMouseEvent *event) |
150 | { |
151 | m_action = Action::Idle; |
152 | determineMousePosition(fromPoint: event->pos()); |
153 | setMouseCursor(); |
154 | } |
155 | |
156 | void LegendMoveResizeHandler::handleHoverEnterEvent(QGraphicsSceneHoverEvent *event) |
157 | { |
158 | if (!m_legend->isAttachedToChart()) { |
159 | determineMousePosition(fromPoint: event->pos()); |
160 | setMouseCursor(); |
161 | } |
162 | m_action = Action::Hovered; |
163 | } |
164 | |
165 | void LegendMoveResizeHandler::handleHoverMoveEvent(QGraphicsSceneHoverEvent *event) |
166 | { |
167 | if (!m_legend->isAttachedToChart()) { |
168 | determineMousePosition(fromPoint: event->pos()); |
169 | setMouseCursor(); |
170 | } |
171 | m_action = Action::Hovered; |
172 | } |
173 | |
174 | void LegendMoveResizeHandler::handleHoverLeaveEvent(QGraphicsSceneHoverEvent *) |
175 | { |
176 | if (!m_legend->isAttachedToChart()) { |
177 | m_mode = MousePosition::Nowhere; |
178 | setMouseCursor(); |
179 | } |
180 | m_action = Action::Idle; |
181 | } |
182 | |
183 | void LegendMoveResizeHandler::setMouseCursor() |
184 | { |
185 | setMouseCursor(m_mode); |
186 | } |
187 | |
188 | void LegendMoveResizeHandler::setMouseCursor(MousePosition mpos) |
189 | { |
190 | #ifdef QT_NO_CURSOR |
191 | Q_UNUSED(mpos); |
192 | return; |
193 | #else |
194 | const QList<QGraphicsItem*> items = m_legend->d_ptr->m_items->childItems(); |
195 | for (const auto item : items) { |
196 | if (!item->hasCursor()) |
197 | item->setCursor(Qt::ArrowCursor); |
198 | } |
199 | |
200 | switch (mpos) { |
201 | case MousePosition::TopLeft: |
202 | case MousePosition::BottomRight: |
203 | m_legend->setCursor(Qt::SizeFDiagCursor); |
204 | break; |
205 | case MousePosition::BottomLeft: |
206 | case MousePosition::TopRight: |
207 | m_legend->setCursor(Qt::SizeBDiagCursor); |
208 | break; |
209 | case MousePosition::Top: |
210 | if (m_action == Action::Moving) |
211 | m_legend->setCursor(Qt::ClosedHandCursor); |
212 | else |
213 | m_legend->setCursor(Qt::OpenHandCursor); |
214 | break; |
215 | case MousePosition::Bottom: |
216 | m_legend->setCursor(Qt::SizeVerCursor); |
217 | break; |
218 | case MousePosition::Left: |
219 | case MousePosition::Right: |
220 | m_legend->setCursor(Qt::SizeHorCursor); |
221 | break; |
222 | case MousePosition::Nowhere: |
223 | m_legend->setCursor(Qt::ArrowCursor); |
224 | break; |
225 | } |
226 | #endif |
227 | } |
228 | |
229 | void LegendMoveResizeHandler::determineMousePosition(QPointF fromPoint) |
230 | { |
231 | QRectF contentRect = m_legend->d_ptr->m_layout->contentsRect(); |
232 | |
233 | if (fromPoint.x() <= contentRect.left()) { |
234 | if (fromPoint.y() <= contentRect.top()) |
235 | m_mode = MousePosition::TopLeft; |
236 | else if (fromPoint.y() >= contentRect.bottom()) |
237 | m_mode = MousePosition::BottomLeft; |
238 | else |
239 | m_mode = MousePosition::Left; |
240 | } else if (fromPoint.x() > contentRect.left() && fromPoint.x() < contentRect.right()) { |
241 | if (fromPoint.y() <= contentRect.top()) |
242 | m_mode = MousePosition::Top; |
243 | else if (fromPoint.y() >= contentRect.bottom()) |
244 | m_mode = MousePosition::Bottom; |
245 | else |
246 | // This catches a corner case where the mouse |
247 | // position y is inside of the content rect |
248 | m_mode = MousePosition::Nowhere; |
249 | } else if (fromPoint.x() >= contentRect.left()) { |
250 | if (fromPoint.y() <= contentRect.top()) |
251 | m_mode = MousePosition::TopRight; |
252 | else if (fromPoint.y() >= contentRect.bottom()) |
253 | m_mode = MousePosition::BottomRight; |
254 | else |
255 | m_mode = MousePosition::Right; |
256 | } else { |
257 | m_mode = MousePosition::Nowhere; |
258 | } |
259 | } |
260 | |
261 | QT_END_NAMESPACE |
262 | |