1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtWidgets/QGraphicsSceneMouseEvent>
5#include <QtWidgets/QGraphicsSceneHoverEvent>
6#include <QtWidgets/QGraphicsScene>
7#include <QtWidgets/QStylePainter>
8#include <QtWidgets/QStyleOption>
9#include <QtCharts/QLegendMarker>
10#include <QtCharts/QChart>
11#include <private/qlegendmarker_p.h>
12#include <private/legendlayout_p.h>
13#include <private/legendmarkeritem_p.h>
14#include <private/legendscroller_p.h>
15#include <private/legendmoveresizehandler_p.h>
16#include <private/legendlayout_p.h>
17
18QT_BEGIN_NAMESPACE
19
20LegendScroller::LegendScroller(QChart *chart) : QLegend(chart)
21{
22 connect(sender: this, signal: &QLegend::interactiveChanged, context: this, slot: &LegendScroller::handleInteractiveChanged);
23 connect(sender: this, signal: &QLegend::attachedToChartChanged, context: this, slot: &LegendScroller::handleDetached);
24}
25
26void LegendScroller::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
27{
28 QLegend::paint(painter, option, widget);
29
30 if (!isInteractive())
31 return;
32
33 if (!isAttachedToChart() && d_ptr->m_resizer->shouldShowMoveHint()) {
34 QStyle *s = this->style();
35 QStylePainter stylePainter;
36 QStyleOptionRubberBand rubberBandOption;
37 QRectF rubberBandRectF(0, 0, geometry().width(), d_ptr->m_layout->contentsRect().y());
38 rubberBandOption.rect = rubberBandRectF.toRect();
39 s->drawControl(element: QStyle::CE_RubberBand, opt: &rubberBandOption, p: painter);
40 }
41}
42
43void LegendScroller::setOffset(const QPointF &point)
44{
45 d_ptr->setOffset(point);
46}
47
48QPointF LegendScroller::offset() const
49{
50 return d_ptr->offset();
51}
52
53void LegendScroller::mousePressEvent(QGraphicsSceneMouseEvent *event)
54{
55 if (!isInteractive() || isAttachedToChart()) {
56 Scroller::handleMousePressEvent(event);
57 return;
58 }
59
60 m_forwardMouseEvents = false;
61 m_forwardHoverEvents = false;
62
63 QRectF contentRect = geometry();
64 qreal left, top, right, bottom;
65 d_ptr->m_layout->getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
66 contentRect.adjust(xp1: left, yp1: top, xp2: -right, yp2: -bottom);
67 contentRect.moveTo(ax: left, ay: top);
68 QPointF pt = event->pos();
69
70 if (contentRect.contains(p: pt)) {
71 Scroller::handleMousePressEvent(event);
72 } else {
73 d_ptr->m_resizer->handleMousePressEvent(event);
74 updateForResizerChange();
75 m_forwardMouseEvents = event->isAccepted();
76 }
77}
78
79void LegendScroller::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
80{
81 if (m_forwardMouseEvents) {
82 d_ptr->m_resizer->handleMouseMoveEvent(event);
83 updateForResizerChange();
84 } else {
85 Scroller::handleMouseMoveEvent(event);
86 }
87}
88
89void LegendScroller::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
90{
91 if (m_forwardMouseEvents) {
92 d_ptr->m_resizer->handleMouseReleaseEvent(event);
93 updateForResizerChange();
94 m_forwardMouseEvents = false;
95 } else {
96 Scroller::handleMouseReleaseEvent(event);
97
98 if (!event->isAccepted()) {
99 const QList<QGraphicsItem *> items = scene()->items(pos: event->scenePos());
100
101 for (const auto i : items) {
102 if (d_ptr->m_markerHash.contains(key: i)) {
103 QLegendMarker *marker = d_ptr->m_markerHash.value(key: i);
104 emit marker->clicked();
105 }
106 }
107 event->accept();
108 }
109 }
110}
111
112void LegendScroller::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *)
113{
114 if (isInteractive() && isAttachedToChart())
115 detachFromChart();
116}
117
118void LegendScroller::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
119{
120 if (isInteractive() && !isAttachedToChart()) {
121 m_forwardHoverEvents = true;
122 d_ptr->m_resizer->handleHoverEnterEvent(event);
123 updateForResizerChange();
124 } else {
125 QLegend::hoverEnterEvent(event);
126 }
127}
128
129void LegendScroller::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
130{
131 if (!isInteractive() || isAttachedToChart()) {
132 QLegend::hoverMoveEvent(event);
133 return;
134 }
135
136 bool stopForwarding = d_ptr->m_layout->contentsRect().contains(p: event->pos());
137 if (stopForwarding) {
138 if (m_forwardHoverEvents) {
139 // the event position is no longer over the border
140 // frame, so send a leave. This will set the
141 // mouse cursor back to an arrow.
142 m_forwardHoverEvents = false;
143 d_ptr->m_resizer->handleHoverLeaveEvent(event);
144 updateForResizerChange();
145 }
146 } else {
147 if (!m_forwardHoverEvents) {
148 // if we're not already forwarding events, and we
149 // shouldn't stop forwarding, then we should
150 // start forwarding. This would happen if the event
151 // position was inside the layout's content rect
152 // on the _previous_ move event, and now the position
153 // is on the border frame. So, this fakes a hover
154 // enter from the _inside_ (which we otherwise
155 // would not get).
156 m_forwardHoverEvents = true;
157 d_ptr->m_resizer->handleHoverEnterEvent(event);
158 updateForResizerChange();
159 }
160 }
161
162 if (m_forwardHoverEvents) {
163 d_ptr->m_resizer->handleHoverMoveEvent(event);
164 updateForResizerChange();
165 } else {
166 QLegend::hoverMoveEvent(event);
167 }
168}
169
170void LegendScroller::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
171{
172 if (m_forwardHoverEvents) {
173 m_forwardHoverEvents = false;
174 d_ptr->m_resizer->handleHoverLeaveEvent(event);
175 updateForResizerChange();
176 } else {
177 QLegend::hoverLeaveEvent(event);
178 }
179}
180
181void LegendScroller::handleInteractiveChanged(bool interactive)
182{
183 setAcceptHoverEvents(interactive);
184 m_cachedShouldShowMoveEvents = d_ptr->m_resizer->shouldShowMoveHint();
185 m_forwardMouseEvents = false;
186 m_forwardHoverEvents = false;
187 d_ptr->m_resizer->reset();
188 update();
189}
190
191void LegendScroller::updateForResizerChange()
192{
193 LegendMoveResizeHandler* resizer = d_ptr->m_resizer;
194 if (resizer->shouldShowMoveHint() != m_cachedShouldShowMoveEvents) {
195 m_cachedShouldShowMoveEvents = resizer->shouldShowMoveHint();
196 update();
197 }
198}
199
200void LegendScroller::handleDetached(bool attached)
201{
202 if (attached)
203 return;
204
205 qreal left, top, right, bottom;
206 d_ptr->m_layout->getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
207 QSizeF newSize = QSizeF(left + right, top + bottom);
208 qreal width = 0;
209 qreal height = 0;
210
211 for (auto marker : d_ptr->markers()) {
212 LegendMarkerItem *item = marker->d_ptr->item();
213 QSizeF itemESizeHint = item->effectiveSizeHint(which: Qt::PreferredSize);
214
215 switch (alignment()) {
216 case Qt::AlignTop:
217 case Qt::AlignBottom:
218 // + 3 accounts for the margin which is not an accessible member.
219 width += itemESizeHint.width() + 3;
220 height = qMax(a: height, b: itemESizeHint.height());
221 break;
222 case Qt::AlignLeft:
223 case Qt::AlignRight:
224 width = qMax(a: width, b: itemESizeHint.width());
225 height += itemESizeHint.height();
226 break;
227 default:
228 break;
229 }
230 }
231
232 newSize += QSizeF(width, height);
233
234 QRectF newGeom = QRectF(geometry().topLeft(), newSize);
235 setGeometry(newGeom.intersected(r: QRectF(QPoint(0,0), d_ptr->m_chart->size())));
236}
237
238QT_END_NAMESPACE
239
240#include "moc_legendscroller_p.cpp"
241

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