1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtCharts/QLegend> |
5 | #include <private/qlegend_p.h> |
6 | #include <QtCharts/QAbstractSeries> |
7 | #include <private/qabstractseries_p.h> |
8 | #include <private/qchart_p.h> |
9 | #include <private/legendlayout_p.h> |
10 | #include <private/chartpresenter_p.h> |
11 | #include <private/abstractchartlayout_p.h> |
12 | #include <QtCharts/QLegendMarker> |
13 | #include <private/qlegendmarker_p.h> |
14 | #include <private/legendmarkeritem_p.h> |
15 | #include <private/legendmoveresizehandler_p.h> |
16 | #include <private/chartdataset_p.h> |
17 | #include <QtGui/QPainter> |
18 | #include <QtGui/QPen> |
19 | #include <QtWidgets/QGraphicsItemGroup> |
20 | |
21 | QT_BEGIN_NAMESPACE |
22 | |
23 | /*! |
24 | \class QLegend |
25 | \inmodule QtCharts |
26 | \inherits QGraphicsWidget |
27 | \brief The QLegend class displays the legend of a chart. |
28 | |
29 | A legend is a graphical object that displays the legend of a chart. The legend state is updated |
30 | by QChart when series change. By default, the legend is attached to the chart, but it can be |
31 | detached to make it independent of chart layout. Legend objects cannot be created or deleted, |
32 | but they can be referenced via the QChart class. |
33 | |
34 | \image examples_percentbarchart_legend.png |
35 | |
36 | \sa QChart |
37 | */ |
38 | /*! |
39 | \qmltype Legend |
40 | \instantiates QLegend |
41 | \inqmlmodule QtCharts |
42 | |
43 | \brief Displays the legend of a chart. |
44 | |
45 | A legend is a graphical object that displays the legend of a chart. The legend state is updated |
46 | by the ChartView type when series change. The \l Legend type properties can be attached to the |
47 | ChartView type. For example: |
48 | \code |
49 | ChartView { |
50 | legend.visible: true |
51 | legend.alignment: Qt.AlignBottom |
52 | // Add a few series... |
53 | } |
54 | \endcode |
55 | |
56 | \image examples_percentbarchart_legend.png |
57 | |
58 | \note There is no QML API available for modifying legend markers. Markers |
59 | can be modified by creating a custom legend. For more information, see |
60 | \l {Using Legend Markers}. |
61 | */ |
62 | |
63 | /*! |
64 | \property QLegend::alignment |
65 | \brief How the legend is aligned with the chart. |
66 | |
67 | Can be Qt::AlignTop, Qt::AlignBottom, Qt::AlignLeft, Qt::AlignRight. If you set more than one |
68 | flag, the result is undefined. |
69 | */ |
70 | /*! |
71 | \qmlproperty alignment Legend::alignment |
72 | Defines how the legend is aligned with the chart. Can be \l{Qt::AlignLeft}{Qt.AlignLeft}, |
73 | \l{Qt::AlignRight}{Qt.AlignRight}, \l{Qt::AlignBottom}{Qt.AlignBottom}, or |
74 | \l{Qt::AlignTop}{Qt.AlignTop}. If you set more than one flag, the result is undefined. |
75 | */ |
76 | |
77 | /*! |
78 | \property QLegend::backgroundVisible |
79 | \brief Whether the legend background is visible. |
80 | */ |
81 | /*! |
82 | \qmlproperty bool Legend::backgroundVisible |
83 | Whether the legend background is visible. |
84 | */ |
85 | |
86 | /*! |
87 | \qmlproperty bool Legend::visible |
88 | Whether the legend is visible. |
89 | |
90 | By default, this property is \c true. |
91 | \sa QGraphicsObject::visible |
92 | */ |
93 | |
94 | /*! |
95 | \property QLegend::color |
96 | \brief The background (brush) color of the legend. |
97 | |
98 | If you change the color of the legend, the style of the legend brush is set to |
99 | Qt::SolidPattern. |
100 | */ |
101 | /*! |
102 | \qmlproperty color Legend::color |
103 | The background (brush) color of the legend. |
104 | */ |
105 | |
106 | /*! |
107 | \property QLegend::borderColor |
108 | \brief The line color of the legend. |
109 | */ |
110 | /*! |
111 | \qmlproperty color Legend::borderColor |
112 | The line color of the legend. |
113 | */ |
114 | |
115 | /*! |
116 | \property QLegend::font |
117 | \brief The font of the markers used by the legend. |
118 | */ |
119 | /*! |
120 | \qmlproperty Font Legend::font |
121 | The font of the markers used by the legend. |
122 | */ |
123 | |
124 | /*! |
125 | \property QLegend::labelColor |
126 | \brief The color of the brush used to draw labels. |
127 | */ |
128 | /*! |
129 | \qmlproperty color Legend::labelColor |
130 | The color of the brush used to draw labels. |
131 | */ |
132 | |
133 | /*! |
134 | \property QLegend::reverseMarkers |
135 | \brief Whether reverse order is used for the markers in the legend. |
136 | |
137 | This property is \c false by default. |
138 | */ |
139 | /*! |
140 | \qmlproperty bool Legend::reverseMarkers |
141 | Whether reverse order is used for the markers in the legend. This property |
142 | is \c false by default. |
143 | */ |
144 | |
145 | /*! |
146 | \property QLegend::showToolTips |
147 | \brief Whether tooltips are shown when the text is truncated. |
148 | |
149 | This property is \c false by default. |
150 | */ |
151 | |
152 | /*! |
153 | \enum QLegend::MarkerShape |
154 | |
155 | This enum describes the shape used when rendering legend marker items. |
156 | |
157 | \value MarkerShapeDefault Default shape determined by QLegend is used for the marker. |
158 | This value is supported only for individual QLegendMarker items. |
159 | \value MarkerShapeRectangle Rectangular markers are used. |
160 | Marker size is determined by font size. |
161 | \value MarkerShapeCircle Circular markers are used. |
162 | Marker size is determined by font size. |
163 | \value MarkerShapeRotatedRectangle Rotated rectangle shaped markers are used. |
164 | Marker size is determined by font size. |
165 | \value MarkerShapeTriangle Triangular markers are used. |
166 | Marker size is determined by font size. |
167 | \value MarkerShapeStar Star shaped markers are used. |
168 | Marker size is determined by font size. |
169 | \value MarkerShapePentagon Pentagon shaped markers are used. |
170 | Marker size is determined by font size. |
171 | \value MarkerShapeFromSeries The marker shape is determined by the series. |
172 | In case of a scatter series, the legend marker looks like a scatter dot and is the same |
173 | size as the dot. In case of a line or spline series, the legend marker looks like a |
174 | small segment of the line. For other series types, rectangular markers are shown. |
175 | If a \c lightMarker is specified for a series, the \c lightMarker will be shown and |
176 | its size will be determined by the series marker size. |
177 | |
178 | \sa markerShape |
179 | */ |
180 | |
181 | /*! |
182 | \qmlproperty enumeration Legend::markerShape |
183 | \since 5.9 |
184 | |
185 | The default shape of the legend markers. |
186 | The default value is \c{MarkerShapeRectangle}. |
187 | |
188 | \value Legend.MarkerShapeRectangle Legend markers are rectangular |
189 | \value Legend.MarkerShapeCircle Legend markers are circular |
190 | \value MarkerShapeRotatedRectangle Legend markers are rotated rectangle shaped. |
191 | \value MarkerShapeTriangle Legend markers are triangular. |
192 | \value MarkerShapeStar Legend markers are star shaped. |
193 | \value MarkerShapePentagon Legend markers are pentagon shaped. |
194 | \value Legend.MarkerShapeFromSeries Legend marker shape is determined by the series |
195 | |
196 | \sa QLegend::MarkerShape |
197 | */ |
198 | |
199 | /*! |
200 | \property QLegend::markerShape |
201 | \since 5.9 |
202 | |
203 | The default shape of the legend markers. |
204 | The default value is \c{MarkerShapeRectangle}. |
205 | */ |
206 | |
207 | /*! |
208 | \qmlproperty bool Legend::showToolTips |
209 | Whether tooltips are shown when the text is truncated. This property is \c false by default. |
210 | This property currently has no effect as there is no support for tooltips in QML. |
211 | */ |
212 | |
213 | /*! |
214 | \fn void QLegend::attachedToChartChanged(bool attached) |
215 | This signal is emitted when the legend is \a attached to or detached from the chart. |
216 | \since 6.2 |
217 | */ |
218 | |
219 | /*! |
220 | \fn void QLegend::backgroundVisibleChanged(bool) |
221 | This signal is emitted when the visibility of the legend background changes to \a visible. |
222 | */ |
223 | |
224 | /*! |
225 | \fn void QLegend::colorChanged(QColor) |
226 | This signal is emitted when the color of the legend background changes to \a color. |
227 | */ |
228 | |
229 | /*! |
230 | \fn void QLegend::borderColorChanged(QColor) |
231 | This signal is emitted when the border color of the legend background changes to \a color. |
232 | */ |
233 | |
234 | /*! |
235 | \fn void QLegend::fontChanged(QFont) |
236 | This signal is emitted when the font of the markers of the legend changes to \a font. |
237 | */ |
238 | |
239 | /*! |
240 | \fn void QLegend::labelColorChanged(QColor color) |
241 | This signal is emitted when the color of the brush used to draw the legend |
242 | labels changes to \a color. |
243 | */ |
244 | |
245 | /*! |
246 | \fn void QLegend::reverseMarkersChanged(bool) |
247 | This signal is emitted when the use of reverse order for the markers in the |
248 | legend is changed to \a reverseMarkers. |
249 | */ |
250 | |
251 | /*! |
252 | \fn void QLegend::showToolTipsChanged(bool showToolTips) |
253 | This signal is emitted when the visibility of tooltips is changed to \a showToolTips. |
254 | */ |
255 | |
256 | QLegend::QLegend(QChart *chart): QGraphicsWidget(chart), |
257 | d_ptr(new QLegendPrivate(chart->d_ptr->m_presenter, chart, this)) |
258 | { |
259 | setZValue(ChartPresenter::LegendZValue); |
260 | setFlags(QGraphicsItem::ItemClipsChildrenToShape); |
261 | QObject::connect(sender: chart->d_ptr->m_dataset, SIGNAL(seriesAdded(QAbstractSeries*)), receiver: d_ptr.data(), SLOT(handleSeriesAdded(QAbstractSeries*))); |
262 | QObject::connect(sender: chart->d_ptr->m_dataset, SIGNAL(seriesRemoved(QAbstractSeries*)), receiver: d_ptr.data(), SLOT(handleSeriesRemoved(QAbstractSeries*))); |
263 | setLayout(d_ptr->m_layout); |
264 | } |
265 | |
266 | /*! |
267 | Destroys the legend object. The legend is always owned by a QChart, so an application |
268 | should never call this function. |
269 | */ |
270 | QLegend::~QLegend() |
271 | { |
272 | } |
273 | |
274 | /*! |
275 | \internal |
276 | */ |
277 | void QLegend::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) |
278 | { |
279 | Q_UNUSED(option); |
280 | Q_UNUSED(widget); |
281 | |
282 | if (!d_ptr->m_backgroundVisible) |
283 | return; |
284 | |
285 | painter->setOpacity(opacity()); |
286 | painter->setPen(d_ptr->m_pen); |
287 | painter->setBrush(d_ptr->m_brush); |
288 | painter->drawRoundedRect(rect: rect(), xRadius: d_ptr->roundness(size: rect().width()), |
289 | yRadius: d_ptr->roundness(size: rect().height()), mode: Qt::RelativeSize); |
290 | } |
291 | |
292 | |
293 | /*! |
294 | Sets the \a brush that is used to draw the background of the legend. |
295 | */ |
296 | void QLegend::setBrush(const QBrush &brush) |
297 | { |
298 | if (d_ptr->m_brush != brush) { |
299 | d_ptr->m_brush = brush; |
300 | update(); |
301 | emit colorChanged(color: brush.color()); |
302 | } |
303 | } |
304 | |
305 | /*! |
306 | Returns the brush used by the legend. |
307 | */ |
308 | QBrush QLegend::brush() const |
309 | { |
310 | if (d_ptr->m_brush == QChartPrivate::defaultBrush()) |
311 | return QBrush(); |
312 | else |
313 | return d_ptr->m_brush; |
314 | } |
315 | |
316 | void QLegend::setColor(QColor color) |
317 | { |
318 | QBrush b = brush(); |
319 | if (b.style() != Qt::SolidPattern || b.color() != color) { |
320 | b.setStyle(Qt::SolidPattern); |
321 | b.setColor(color); |
322 | setBrush(b); |
323 | } |
324 | } |
325 | |
326 | QColor QLegend::color() |
327 | { |
328 | return d_ptr->m_brush.color(); |
329 | } |
330 | |
331 | /*! |
332 | Sets the \a pen that is used to draw the legend borders. |
333 | */ |
334 | void QLegend::setPen(const QPen &pen) |
335 | { |
336 | if (d_ptr->m_pen != pen) { |
337 | d_ptr->m_pen = pen; |
338 | update(); |
339 | emit borderColorChanged(color: pen.color()); |
340 | } |
341 | } |
342 | |
343 | /*! |
344 | Returns the pen used by the legend. |
345 | */ |
346 | |
347 | QPen QLegend::pen() const |
348 | { |
349 | if (d_ptr->m_pen == QChartPrivate::defaultPen()) |
350 | return QPen(); |
351 | else |
352 | return d_ptr->m_pen; |
353 | } |
354 | |
355 | void QLegend::setFont(const QFont &font) |
356 | { |
357 | if (d_ptr->m_font != font) { |
358 | // Hide items to avoid flickering |
359 | d_ptr->items()->setVisible(false); |
360 | d_ptr->m_font = font; |
361 | foreach (QLegendMarker *marker, d_ptr->markers()) { |
362 | marker->setFont(d_ptr->m_font); |
363 | } |
364 | layout()->invalidate(); |
365 | emit fontChanged(font); |
366 | } |
367 | } |
368 | |
369 | QFont QLegend::font() const |
370 | { |
371 | return d_ptr->m_font; |
372 | } |
373 | |
374 | void QLegend::setBorderColor(QColor color) |
375 | { |
376 | QPen p = pen(); |
377 | if (p.color() != color) { |
378 | p.setColor(color); |
379 | setPen(p); |
380 | } |
381 | } |
382 | |
383 | QColor QLegend::borderColor() |
384 | { |
385 | return d_ptr->m_pen.color(); |
386 | } |
387 | |
388 | /*! |
389 | Sets the brush used to draw the legend labels to \a brush. |
390 | */ |
391 | void QLegend::setLabelBrush(const QBrush &brush) |
392 | { |
393 | if (d_ptr->m_labelBrush != brush) { |
394 | d_ptr->m_labelBrush = brush; |
395 | foreach (QLegendMarker *marker, d_ptr->markers()) { |
396 | marker->setLabelBrush(d_ptr->m_labelBrush); |
397 | // Note: The pen of the marker rectangle could be exposed in the public QLegend API |
398 | // instead of mapping it from label brush color |
399 | marker->setPen(brush.color()); |
400 | } |
401 | emit labelColorChanged(color: brush.color()); |
402 | } |
403 | } |
404 | |
405 | /*! |
406 | Returns the brush used to draw labels. |
407 | */ |
408 | QBrush QLegend::labelBrush() const |
409 | { |
410 | if (d_ptr->m_labelBrush == QChartPrivate::defaultBrush()) |
411 | return QBrush(); |
412 | else |
413 | return d_ptr->m_labelBrush; |
414 | } |
415 | |
416 | void QLegend::setLabelColor(QColor color) |
417 | { |
418 | QBrush b = labelBrush(); |
419 | if (b.style() != Qt::SolidPattern || b.color() != color) { |
420 | b.setStyle(Qt::SolidPattern); |
421 | b.setColor(color); |
422 | setLabelBrush(b); |
423 | } |
424 | } |
425 | |
426 | QColor QLegend::labelColor() const |
427 | { |
428 | return d_ptr->m_labelBrush.color(); |
429 | } |
430 | |
431 | |
432 | void QLegend::setAlignment(Qt::Alignment alignment) |
433 | { |
434 | if (d_ptr->m_alignment != alignment) { |
435 | d_ptr->m_alignment = alignment; |
436 | layout()->invalidate(); |
437 | } |
438 | } |
439 | |
440 | Qt::Alignment QLegend::alignment() const |
441 | { |
442 | return d_ptr->m_alignment; |
443 | } |
444 | |
445 | /*! |
446 | Detaches the legend from the chart. The chart will no longer adjust the layout of the legend. |
447 | */ |
448 | void QLegend::detachFromChart() |
449 | { |
450 | bool changed = d_ptr->m_attachedToChart == true; |
451 | d_ptr->m_attachedToChart = false; |
452 | d_ptr->m_chart->layout()->invalidate(); |
453 | setParent(0); |
454 | |
455 | if (changed) |
456 | emit attachedToChartChanged(attachedToChart: false); |
457 | } |
458 | |
459 | /*! |
460 | Attaches the legend to a chart. The chart may adjust the layout of the legend. |
461 | */ |
462 | void QLegend::attachToChart() |
463 | { |
464 | bool changed = d_ptr->m_attachedToChart == false; |
465 | d_ptr->m_attachedToChart = true; |
466 | d_ptr->m_chart->layout()->invalidate(); |
467 | setParent(d_ptr->m_chart); |
468 | |
469 | if (changed) |
470 | emit attachedToChartChanged(attachedToChart: true); |
471 | } |
472 | |
473 | /*! |
474 | Returns \c true, if the legend is attached to a chart. |
475 | */ |
476 | bool QLegend::isAttachedToChart() |
477 | { |
478 | return d_ptr->m_attachedToChart; |
479 | } |
480 | |
481 | /*! |
482 | Sets the visibility of the legend background to \a visible. |
483 | */ |
484 | void QLegend::setBackgroundVisible(bool visible) |
485 | { |
486 | if (d_ptr->m_backgroundVisible != visible) { |
487 | d_ptr->m_backgroundVisible = visible; |
488 | update(); |
489 | emit backgroundVisibleChanged(visible); |
490 | } |
491 | } |
492 | |
493 | /*! |
494 | Returns the visibility of the legend background. |
495 | */ |
496 | bool QLegend::isBackgroundVisible() const |
497 | { |
498 | return d_ptr->m_backgroundVisible; |
499 | } |
500 | |
501 | /*! |
502 | Returns the list of markers in the legend. The list can be filtered by specifying |
503 | the \a series for which the markers are returned. |
504 | */ |
505 | QList<QLegendMarker*> QLegend::markers(QAbstractSeries *series) const |
506 | { |
507 | return d_ptr->markers(series); |
508 | } |
509 | |
510 | bool QLegend::reverseMarkers() |
511 | { |
512 | return d_ptr->m_reverseMarkers; |
513 | } |
514 | |
515 | void QLegend::setReverseMarkers(bool reverseMarkers) |
516 | { |
517 | if (d_ptr->m_reverseMarkers != reverseMarkers) { |
518 | d_ptr->m_reverseMarkers = reverseMarkers; |
519 | layout()->invalidate(); |
520 | emit reverseMarkersChanged(reverseMarkers); |
521 | } |
522 | } |
523 | |
524 | /*! |
525 | Returns whether the tooltips are shown for the legend labels |
526 | when they are elided. |
527 | */ |
528 | |
529 | bool QLegend::showToolTips() const |
530 | { |
531 | return d_ptr->m_showToolTips; |
532 | } |
533 | |
534 | /*! |
535 | When \a show is \c true, the legend labels will show a tooltip when |
536 | the mouse hovers over them if the label itself is shown elided. |
537 | This is \c false by default. |
538 | */ |
539 | |
540 | void QLegend::setShowToolTips(bool show) |
541 | { |
542 | if (d_ptr->m_showToolTips != show) { |
543 | d_ptr->m_showToolTips = show; |
544 | d_ptr->updateToolTips(); |
545 | emit showToolTipsChanged(showToolTips: show); |
546 | } |
547 | } |
548 | |
549 | /*! |
550 | Returns whether the legend can be dragged or resized using a mouse when it is detached. |
551 | |
552 | \sa QLegend::setInteractive() |
553 | \since 6.2 |
554 | */ |
555 | |
556 | bool QLegend::isInteractive() const |
557 | { |
558 | return d_ptr->m_interactive; |
559 | } |
560 | |
561 | /*! |
562 | When \a interactive is \c true and the legend is detached, the legend is able to be moved and |
563 | resized with a mouse in a similar way to a window. |
564 | |
565 | The legend will automatically attach to an edge of the chart by dragging it off of that edge. |
566 | Double clicking an attached legend will detach it. |
567 | This is \c false by default. |
568 | |
569 | \sa QLegend::isInteractive() |
570 | \since 6.2 |
571 | */ |
572 | |
573 | void QLegend::setInteractive(bool interactive) |
574 | { |
575 | if (d_ptr->m_interactive != interactive) { |
576 | d_ptr->m_interactive = interactive; |
577 | update(); |
578 | emit interactiveChanged(interactive); |
579 | } |
580 | } |
581 | |
582 | QLegend::MarkerShape QLegend::markerShape() const |
583 | { |
584 | return d_ptr->m_markerShape; |
585 | } |
586 | |
587 | void QLegend::setMarkerShape(QLegend::MarkerShape shape) |
588 | { |
589 | QLegend::MarkerShape newShape = shape; |
590 | if (newShape == MarkerShapeDefault) |
591 | newShape = MarkerShapeRectangle; |
592 | if (d_ptr->m_markerShape != newShape) { |
593 | d_ptr->m_markerShape = newShape; |
594 | layout()->invalidate(); |
595 | emit markerShapeChanged(shape: newShape); |
596 | } |
597 | } |
598 | |
599 | /*! |
600 | \internal |
601 | \a event, see QGraphicsWidget for details. |
602 | */ |
603 | void QLegend::hideEvent(QHideEvent *event) |
604 | { |
605 | if (isAttachedToChart()) |
606 | d_ptr->m_presenter->layout()->invalidate(); |
607 | QGraphicsWidget::hideEvent(event); |
608 | } |
609 | /*! |
610 | \internal |
611 | \a event, see QGraphicsWidget for details. |
612 | */ |
613 | void QLegend::showEvent(QShowEvent *event) |
614 | { |
615 | if (isAttachedToChart()) |
616 | layout()->invalidate(); |
617 | QGraphicsWidget::showEvent(event); |
618 | //layout activation will show the items |
619 | } |
620 | |
621 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
622 | |
623 | QLegendPrivate::QLegendPrivate(ChartPresenter *presenter, QChart *chart, QLegend *q) |
624 | : q_ptr(q), |
625 | m_presenter(presenter), |
626 | m_layout(new LegendLayout(q)), |
627 | m_resizer(new LegendMoveResizeHandler(q)), |
628 | m_chart(chart), |
629 | m_items(new QGraphicsItemGroup(q)), |
630 | m_alignment(Qt::AlignTop), |
631 | m_brush(QChartPrivate::defaultBrush()), |
632 | m_pen(QChartPrivate::defaultPen()), |
633 | m_labelBrush(QChartPrivate::defaultBrush()), |
634 | m_diameter(5), |
635 | m_attachedToChart(true), |
636 | m_backgroundVisible(false), |
637 | m_reverseMarkers(false), |
638 | m_showToolTips(false), |
639 | m_interactive(false), |
640 | m_markerShape(QLegend::MarkerShapeRectangle) |
641 | { |
642 | m_items->setHandlesChildEvents(false); |
643 | } |
644 | |
645 | QLegendPrivate::~QLegendPrivate() |
646 | { |
647 | delete m_resizer; |
648 | } |
649 | |
650 | void QLegendPrivate::setOffset(const QPointF &offset) |
651 | { |
652 | m_layout->setOffset(x: offset.x(), y: offset.y()); |
653 | } |
654 | |
655 | QPointF QLegendPrivate::offset() const |
656 | { |
657 | return m_layout->offset(); |
658 | } |
659 | |
660 | int QLegendPrivate::roundness(qreal size) |
661 | { |
662 | return 100 * m_diameter / int(size); |
663 | } |
664 | |
665 | QList<QLegendMarker*> QLegendPrivate::markers(QAbstractSeries *series) |
666 | { |
667 | // Return all markers |
668 | if (!series) { |
669 | return m_markers; |
670 | } |
671 | |
672 | // Create filtered list |
673 | QList<QLegendMarker *> markers; |
674 | foreach (QLegendMarker *marker, m_markers) { |
675 | if (marker->series() == series) { |
676 | markers.append(t: marker); |
677 | } |
678 | } |
679 | return markers; |
680 | } |
681 | |
682 | qreal QLegendPrivate::maxMarkerWidth() const |
683 | { |
684 | qreal maxWidth = 0.0; |
685 | for (int i = 0; i < m_markers.size(); i++) { |
686 | LegendMarkerItem *item = m_markers.at(i)->d_ptr->item(); |
687 | if (item) |
688 | maxWidth = qMax(a: item->markerRect().width(), b: maxWidth); |
689 | } |
690 | return maxWidth; |
691 | } |
692 | |
693 | void QLegendPrivate::handleSeriesAdded(QAbstractSeries *series) |
694 | { |
695 | if (m_series.contains(t: series)) { |
696 | return; |
697 | } |
698 | |
699 | QList<QLegendMarker*> newMarkers = series->d_ptr->createLegendMarkers(legend: q_ptr); |
700 | decorateMarkers(markers: newMarkers); |
701 | addMarkers(markers: newMarkers); |
702 | |
703 | QObject::connect(sender: series->d_ptr.data(), SIGNAL(countChanged()), receiver: this, SLOT(handleCountChanged())); |
704 | QObject::connect(sender: series, SIGNAL(visibleChanged()), receiver: this, SLOT(handleSeriesVisibleChanged())); |
705 | |
706 | m_series.append(t: series); |
707 | m_items->setVisible(false); |
708 | m_layout->invalidate(); |
709 | } |
710 | |
711 | void QLegendPrivate::handleSeriesRemoved(QAbstractSeries *series) |
712 | { |
713 | if (m_series.contains(t: series)) { |
714 | m_series.removeOne(t: series); |
715 | } |
716 | |
717 | // Find out, which markers to remove |
718 | QList<QLegendMarker *> removed; |
719 | foreach (QLegendMarker *m, m_markers) { |
720 | if (m->series() == series) { |
721 | removed << m; |
722 | } |
723 | } |
724 | removeMarkers(markers: removed); |
725 | |
726 | QObject::disconnect(sender: series->d_ptr.data(), SIGNAL(countChanged()), receiver: this, SLOT(handleCountChanged())); |
727 | QObject::disconnect(sender: series, SIGNAL(visibleChanged()), receiver: this, SLOT(handleSeriesVisibleChanged())); |
728 | |
729 | m_layout->invalidate(); |
730 | } |
731 | |
732 | void QLegendPrivate::handleSeriesVisibleChanged() |
733 | { |
734 | QAbstractSeries *series = qobject_cast<QAbstractSeries *> (object: sender()); |
735 | Q_ASSERT(series); |
736 | |
737 | foreach (QLegendMarker *marker, m_markers) { |
738 | if (marker->series() == series) { |
739 | marker->setVisible(series->isVisible()); |
740 | } |
741 | } |
742 | |
743 | if (m_chart->isVisible()) |
744 | m_layout->invalidate(); |
745 | } |
746 | |
747 | QObject *QLegendPrivate::relatedObject(const QLegendMarker *l) |
748 | { |
749 | return l->d_ptr->relatedObject(); |
750 | } |
751 | |
752 | // Find equivalent QLegendMarker by checking for relatedObject() |
753 | static int indexOfEquivalent(const QLegendMarker *needle, |
754 | const QList<QLegendMarker *> &hayStack) |
755 | { |
756 | const QObject *needleObject = QLegendPrivate::relatedObject(l: needle); |
757 | for (int i = 0, size = hayStack.size(); i < size; ++i) { |
758 | if (QLegendPrivate::relatedObject(l: hayStack.at(i)) == needleObject) |
759 | return i; |
760 | } |
761 | return -1; |
762 | } |
763 | |
764 | // Find QLegendMarker for series |
765 | static int indexOfSeries(const QAbstractSeries *series, |
766 | const QList<QLegendMarker *> &hayStack) |
767 | { |
768 | for (int i = 0, size = hayStack.size(); i < size; ++i) { |
769 | if (hayStack.at(i)->series() == series) |
770 | return i; |
771 | } |
772 | return -1; |
773 | } |
774 | |
775 | void QLegendPrivate::handleCountChanged() |
776 | { |
777 | // Here we handle the changes in marker count. |
778 | // Can happen for example when pieslice(s) have been added to or removed from pieseries. |
779 | |
780 | QAbstractSeriesPrivate *seriesP = qobject_cast<QAbstractSeriesPrivate *>(object: sender()); |
781 | QAbstractSeries *series = seriesP->q_ptr; |
782 | QList<QLegendMarker *> createdMarkers = seriesP->createLegendMarkers(legend: q_ptr); |
783 | QList<bool> isNew(createdMarkers.size(), true); |
784 | |
785 | const int pos = indexOfSeries(series, hayStack: m_markers); |
786 | // Remove markers of the series from m_markers and check against the newly |
787 | // created ones. |
788 | if (pos != -1) { |
789 | while (pos < m_markers.size() && m_markers.at(i: pos)->series() == series) { |
790 | QLegendMarker *oldMarker = m_markers.takeAt(i: pos); |
791 | const int newIndex = indexOfEquivalent(needle: oldMarker, hayStack: createdMarkers); |
792 | if (newIndex == -1) { |
793 | removeMarkerHelper(marker: oldMarker); // no longer exists |
794 | } else { |
795 | // Replace newly created marker by its equivalent |
796 | delete createdMarkers[newIndex]; |
797 | createdMarkers[newIndex] = oldMarker; |
798 | isNew[newIndex] = false; |
799 | } |
800 | } |
801 | } |
802 | |
803 | for (int i = 0, size = createdMarkers.size(); i < size; ++i) { |
804 | if (isNew.at(i)) { |
805 | insertMarkerHelper(marker: createdMarkers.at(i)); |
806 | decorateMarker(marker: createdMarkers.at(i)); |
807 | } |
808 | } |
809 | |
810 | // Re-insert createdMarkers into m_markers in correct order. |
811 | if (pos == -1 || pos == m_markers.size()) { |
812 | m_markers.append(l: createdMarkers); |
813 | } else { |
814 | for (int c = createdMarkers.size() - 1; c >= 0; --c) |
815 | m_markers.insert(i: pos, t: createdMarkers.at(i: c)); |
816 | } |
817 | |
818 | q_ptr->layout()->invalidate(); |
819 | } |
820 | |
821 | // Helper function for marker insertion except m_markers handling |
822 | void QLegendPrivate::insertMarkerHelper(QLegendMarker *marker) |
823 | { |
824 | LegendMarkerItem *item = marker->d_ptr->item(); |
825 | m_items->addToGroup(item); |
826 | m_markerHash.insert(key: item, value: marker); |
827 | } |
828 | |
829 | void QLegendPrivate::addMarkers(const QList<QLegendMarker *> &markers) |
830 | { |
831 | for (auto *marker : markers) { |
832 | insertMarkerHelper(marker); |
833 | m_markers << marker; |
834 | } |
835 | } |
836 | |
837 | // Helper function for marker removal except m_markers handling |
838 | void QLegendPrivate::removeMarkerHelper(QLegendMarker *marker) |
839 | { |
840 | LegendMarkerItem *item = marker->d_ptr->item(); |
841 | item->setVisible(false); |
842 | m_items->removeFromGroup(item); |
843 | m_markerHash.remove(key: item); |
844 | delete marker; |
845 | } |
846 | |
847 | void QLegendPrivate::removeMarkers(const QList<QLegendMarker *> &markers) |
848 | { |
849 | for (auto *marker : markers) { |
850 | m_markers.removeOne(t: marker); |
851 | removeMarkerHelper(marker); |
852 | } |
853 | } |
854 | |
855 | void QLegendPrivate::decorateMarker(QLegendMarker *marker) |
856 | { |
857 | marker->setFont(m_font); |
858 | marker->setLabelBrush(m_labelBrush); |
859 | } |
860 | |
861 | void QLegendPrivate::decorateMarkers(const QList<QLegendMarker *> &markers) |
862 | { |
863 | for (auto *marker : markers) |
864 | decorateMarker(marker); |
865 | } |
866 | |
867 | void QLegendPrivate::updateToolTips() |
868 | { |
869 | foreach (QLegendMarker *m, m_markers) { |
870 | if (m->d_ptr->m_item->displayedLabel() != m->label()) |
871 | m->d_ptr->m_item->setToolTip(m->label()); |
872 | else |
873 | m->d_ptr->m_item->setToolTip(QString()); |
874 | } |
875 | } |
876 | |
877 | QT_END_NAMESPACE |
878 | |
879 | #include "moc_qlegend.cpp" |
880 | #include "moc_qlegend_p.cpp" |
881 | |