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
13QT_BEGIN_NAMESPACE
14
15LegendMoveResizeHandler::LegendMoveResizeHandler(QLegend *legend) :
16 m_legend(legend)
17{
18 m_legend->setAcceptHoverEvents(true);
19 m_legend->setCursor(Qt::ArrowCursor);
20}
21
22LegendMoveResizeHandler::~LegendMoveResizeHandler()
23{
24}
25
26void LegendMoveResizeHandler::reset()
27{
28 m_action = Action::Idle;
29 setMouseCursor(MousePosition::Nowhere);
30}
31
32void 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
55void 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
149void LegendMoveResizeHandler::handleMouseReleaseEvent(QGraphicsSceneMouseEvent *event)
150{
151 m_action = Action::Idle;
152 determineMousePosition(fromPoint: event->pos());
153 setMouseCursor();
154}
155
156void 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
165void 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
174void LegendMoveResizeHandler::handleHoverLeaveEvent(QGraphicsSceneHoverEvent *)
175{
176 if (!m_legend->isAttachedToChart()) {
177 m_mode = MousePosition::Nowhere;
178 setMouseCursor();
179 }
180 m_action = Action::Idle;
181}
182
183void LegendMoveResizeHandler::setMouseCursor()
184{
185 setMouseCursor(m_mode);
186}
187
188void 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
229void 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
261QT_END_NAMESPACE
262

source code of qtcharts/src/charts/legend/legendmoveresizehandler.cpp