1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "abstract3dcontroller_p.h"
5#include "qabstract3daxis_p.h"
6#include "qvalue3daxis_p.h"
7#include "qcategory3daxis_p.h"
8#include "qquickgraphsitem_p.h"
9#include "qabstract3dseries_p.h"
10#include "q3dscene_p.h"
11#include "qabstract3dinputhandler_p.h"
12#include "qtouch3dinputhandler.h"
13#include "thememanager_p.h"
14#include "q3dtheme_p.h"
15#include "qcustom3ditem_p.h"
16#include "utils_p.h"
17#include <QtCore/QThread>
18#include <QtOpenGL/QOpenGLFramebufferObject>
19#include <QtCore/QMutexLocker>
20
21QT_BEGIN_NAMESPACE
22
23Abstract3DController::Abstract3DController(QRect initialViewport, Q3DScene *scene,
24 QObject *parent) :
25 QObject(parent),
26 m_themeManager(new ThemeManager(this)),
27 m_selectionMode(QAbstract3DGraph::SelectionItem),
28 m_shadowQuality(QAbstract3DGraph::ShadowQualityMedium),
29 m_useOrthoProjection(false),
30 m_aspectRatio(2.0),
31 m_horizontalAspectRatio(0.0),
32 m_optimizationHints(QAbstract3DGraph::OptimizationDefault),
33 m_reflectionEnabled(false),
34 m_reflectivity(0.5),
35 m_locale(QLocale::c()),
36 m_scene(scene),
37 m_axisX(0),
38 m_axisY(0),
39 m_axisZ(0),
40 m_isDataDirty(true),
41 m_isCustomDataDirty(true),
42 m_isCustomItemDirty(true),
43 m_isSeriesVisualsDirty(true),
44 m_renderPending(false),
45 m_isPolar(false),
46 m_radialLabelOffset(1.0f),
47 m_clickedType(QAbstract3DGraph::ElementNone),
48 m_selectedLabelIndex(-1),
49 m_selectedCustomItemIndex(-1),
50 m_margin(-1.0)
51{
52 if (!m_scene)
53 m_scene = new Q3DScene;
54 m_scene->setParent(this);
55
56 // Set initial theme
57 Q3DTheme *defaultTheme = new Q3DTheme(Q3DTheme::ThemeQt);
58 defaultTheme->d_func()->setDefaultTheme(true);
59 setActiveTheme(theme: defaultTheme);
60
61 m_scene->d_func()->setViewport(initialViewport);
62 m_scene->activeLight()->setAutoPosition(true);
63
64 connect(sender: m_scene->d_func(), signal: &Q3DScenePrivate::needRender, context: this,
65 slot: &Abstract3DController::emitNeedRender);
66}
67
68Abstract3DController::~Abstract3DController()
69{
70 delete m_scene;
71 delete m_themeManager;
72 foreach (QCustom3DItem *item, m_customItems)
73 delete item;
74 m_customItems.clear();
75}
76
77void Abstract3DController::addSeries(QAbstract3DSeries *series)
78{
79 insertSeries(index: m_seriesList.size(), series);
80}
81
82void Abstract3DController::insertSeries(int index, QAbstract3DSeries *series)
83{
84 if (series) {
85 if (m_seriesList.contains(t: series)) {
86 int oldIndex = m_seriesList.indexOf(t: series);
87 if (index != oldIndex) {
88 m_seriesList.removeOne(t: series);
89 if (oldIndex < index)
90 index--;
91 m_seriesList.insert(i: index, t: series);
92 }
93 } else {
94 int oldSize = m_seriesList.size();
95 m_seriesList.insert(i: index, t: series);
96 series->d_func()->setController(this);
97 QObject::connect(sender: series, signal: &QAbstract3DSeries::visibilityChanged,
98 context: this, slot: &Abstract3DController::handleSeriesVisibilityChanged);
99 series->d_func()->resetToTheme(theme: *m_themeManager->activeTheme(), seriesIndex: oldSize, force: false);
100 }
101 if (series->isVisible())
102 handleSeriesVisibilityChangedBySender(sender: series);
103 }
104}
105
106void Abstract3DController::removeSeries(QAbstract3DSeries *series)
107{
108 if (series && series->d_func()->m_controller == this) {
109 m_seriesList.removeAll(t: series);
110 QObject::disconnect(sender: series, signal: &QAbstract3DSeries::visibilityChanged,
111 receiver: this, slot: &Abstract3DController::handleSeriesVisibilityChanged);
112 series->d_func()->setController(0);
113 m_isDataDirty = true;
114 m_isSeriesVisualsDirty = true;
115 emitNeedRender();
116 }
117}
118
119bool Abstract3DController::hasSeries(QAbstract3DSeries *series)
120{
121 return m_seriesList.contains(t: series);
122}
123
124QList<QAbstract3DSeries *> Abstract3DController::seriesList()
125{
126 return m_seriesList;
127}
128
129void Abstract3DController::handleThemeColorStyleChanged(Q3DTheme::ColorStyle style)
130{
131 // Set value for series that have not explicitly set this value
132 foreach (QAbstract3DSeries *series, m_seriesList) {
133 if (!series->d_func()->m_themeTracker.colorStyleOverride) {
134 series->setColorStyle(style);
135 series->d_func()->m_themeTracker.colorStyleOverride = false;
136 }
137 }
138 markSeriesVisualsDirty();
139}
140
141void Abstract3DController::handleThemeBaseColorsChanged(const QList<QColor> &colors)
142{
143 int colorIdx = 0;
144 // Set value for series that have not explicitly set this value
145 foreach (QAbstract3DSeries *series, m_seriesList) {
146 if (!series->d_func()->m_themeTracker.baseColorOverride) {
147 series->setBaseColor(colors.at(i: colorIdx));
148 series->d_func()->m_themeTracker.baseColorOverride = false;
149 }
150 if (++colorIdx >= colors.size())
151 colorIdx = 0;
152 }
153 markSeriesVisualsDirty();
154}
155
156void Abstract3DController::handleThemeBaseGradientsChanged(const QList<QLinearGradient> &gradients)
157{
158 int gradientIdx = 0;
159 // Set value for series that have not explicitly set this value
160 foreach (QAbstract3DSeries *series, m_seriesList) {
161 if (!series->d_func()->m_themeTracker.baseGradientOverride) {
162 series->setBaseGradient(gradients.at(i: gradientIdx));
163 series->d_func()->m_themeTracker.baseGradientOverride = false;
164 }
165 if (++gradientIdx >= gradients.size())
166 gradientIdx = 0;
167 }
168 markSeriesVisualsDirty();
169}
170
171void Abstract3DController::handleThemeSingleHighlightColorChanged(const QColor &color)
172{
173 // Set value for series that have not explicitly set this value
174 foreach (QAbstract3DSeries *series, m_seriesList) {
175 if (!series->d_func()->m_themeTracker.singleHighlightColorOverride) {
176 series->setSingleHighlightColor(color);
177 series->d_func()->m_themeTracker.singleHighlightColorOverride = false;
178 }
179 }
180 markSeriesVisualsDirty();
181}
182
183void Abstract3DController::handleThemeSingleHighlightGradientChanged(
184 const QLinearGradient &gradient)
185{
186 // Set value for series that have not explicitly set this value
187 foreach (QAbstract3DSeries *series, m_seriesList) {
188 if (!series->d_func()->m_themeTracker.singleHighlightGradientOverride) {
189 series->setSingleHighlightGradient(gradient);
190 series->d_func()->m_themeTracker.singleHighlightGradientOverride = false;
191 }
192 }
193 markSeriesVisualsDirty();
194}
195
196void Abstract3DController::handleThemeMultiHighlightColorChanged(const QColor &color)
197{
198 // Set value for series that have not explicitly set this value
199 foreach (QAbstract3DSeries *series, m_seriesList) {
200 if (!series->d_func()->m_themeTracker.multiHighlightColorOverride) {
201 series->setMultiHighlightColor(color);
202 series->d_func()->m_themeTracker.multiHighlightColorOverride = false;
203 }
204 }
205 markSeriesVisualsDirty();
206}
207
208void Abstract3DController::handleThemeMultiHighlightGradientChanged(const QLinearGradient &gradient)
209{
210 // Set value for series that have not explicitly set this value
211 foreach (QAbstract3DSeries *series, m_seriesList) {
212 if (!series->d_func()->m_themeTracker.multiHighlightGradientOverride) {
213 series->setMultiHighlightGradient(gradient);
214 series->d_func()->m_themeTracker.multiHighlightGradientOverride = false;
215 }
216 }
217 markSeriesVisualsDirty();
218}
219
220void Abstract3DController::handleThemeTypeChanged(Q3DTheme::Theme theme)
221{
222 Q_UNUSED(theme);
223
224 // Changing theme type is logically equivalent of changing the entire theme
225 // object, so reset all attached series to the new theme.
226 bool force = m_qml->isReady();
227 Q3DTheme *activeTheme = m_themeManager->activeTheme();
228 for (int i = 0; i < m_seriesList.size(); i++)
229 m_seriesList.at(i)->d_func()->resetToTheme(theme: *activeTheme, seriesIndex: i, force);
230
231 markSeriesVisualsDirty();
232
233 emit themeTypeChanged();
234}
235
236void Abstract3DController::setAxisX(QAbstract3DAxis *axis)
237{
238 // Setting null axis will always create new default axis
239 if (!axis || axis != m_axisX) {
240 setAxisHelper(orientation: QAbstract3DAxis::AxisOrientationX, axis, axisPtr: &m_axisX);
241 emit axisXChanged(axis: m_axisX);
242 }
243}
244
245QAbstract3DAxis *Abstract3DController::axisX() const
246{
247 return m_axisX;
248}
249
250void Abstract3DController::setAxisY(QAbstract3DAxis *axis)
251{
252 // Setting null axis will always create new default axis
253 if (!axis || axis != m_axisY) {
254 setAxisHelper(orientation: QAbstract3DAxis::AxisOrientationY, axis, axisPtr: &m_axisY);
255 emit axisYChanged(axis: m_axisY);
256 }
257}
258
259QAbstract3DAxis *Abstract3DController::axisY() const
260{
261 return m_axisY;
262}
263
264void Abstract3DController::setAxisZ(QAbstract3DAxis *axis)
265{
266 // Setting null axis will always create new default axis
267 if (!axis || axis != m_axisZ) {
268 setAxisHelper(orientation: QAbstract3DAxis::AxisOrientationZ, axis, axisPtr: &m_axisZ);
269 emit axisZChanged(axis: m_axisZ);
270 }
271}
272
273QAbstract3DAxis *Abstract3DController::axisZ() const
274{
275 return m_axisZ;
276}
277
278void Abstract3DController::addAxis(QAbstract3DAxis *axis)
279{
280 Q_ASSERT(axis);
281 Abstract3DController *owner = qobject_cast<Abstract3DController *>(object: axis->parent());
282 if (owner != this) {
283 Q_ASSERT_X(!owner, "addAxis", "Axis already attached to a graph.");
284 axis->setParent(this);
285 }
286 if (!m_axes.contains(t: axis))
287 m_axes.append(t: axis);
288}
289
290void Abstract3DController::releaseAxis(QAbstract3DAxis *axis)
291{
292 if (axis && m_axes.contains(t: axis)) {
293 // Clear the default status from released default axes
294 if (axis->d_func()->isDefaultAxis())
295 axis->d_func()->setDefaultAxis(false);
296
297 // If the axis is in use, replace it with a temporary one
298 switch (axis->orientation()) {
299 case QAbstract3DAxis::AxisOrientationX:
300 setAxisX(0);
301 break;
302 case QAbstract3DAxis::AxisOrientationY:
303 setAxisY(0);
304 break;
305 case QAbstract3DAxis::AxisOrientationZ:
306 setAxisZ(0);
307 break;
308 default:
309 break;
310 }
311
312 m_axes.removeAll(t: axis);
313 axis->setParent(0);
314 }
315}
316
317QList<QAbstract3DAxis *> Abstract3DController::axes() const
318{
319 return m_axes;
320}
321
322void Abstract3DController::addTheme(Q3DTheme *theme)
323{
324 m_themeManager->addTheme(theme);
325}
326
327void Abstract3DController::releaseTheme(Q3DTheme *theme)
328{
329 Q3DTheme *oldTheme = m_themeManager->activeTheme();
330
331 m_themeManager->releaseTheme(theme);
332
333 if (oldTheme != m_themeManager->activeTheme())
334 emit activeThemeChanged(activeTheme: m_themeManager->activeTheme());
335}
336
337QList<Q3DTheme *> Abstract3DController::themes() const
338{
339 return m_themeManager->themes();
340}
341
342void Abstract3DController::setActiveTheme(Q3DTheme *theme, bool force)
343{
344 if (theme != m_themeManager->activeTheme()) {
345 m_themeManager->setActiveTheme(theme);
346 m_changeTracker.themeChanged = true;
347 // Default theme can be created by theme manager, so ensure we have correct theme
348 Q3DTheme *newActiveTheme = m_themeManager->activeTheme();
349 // Reset all attached series to the new theme
350 for (int i = 0; i < m_seriesList.size(); i++)
351 m_seriesList.at(i)->d_func()->resetToTheme(theme: *newActiveTheme, seriesIndex: i, force);
352 markSeriesVisualsDirty();
353 emit activeThemeChanged(activeTheme: newActiveTheme);
354 }
355}
356
357Q3DTheme *Abstract3DController::activeTheme() const
358{
359 return m_themeManager->activeTheme();
360}
361
362void Abstract3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode)
363{
364 if (mode != m_selectionMode) {
365 m_selectionMode = mode;
366 m_changeTracker.selectionModeChanged = true;
367 emit selectionModeChanged(mode);
368 emitNeedRender();
369 }
370}
371
372QAbstract3DGraph::SelectionFlags Abstract3DController::selectionMode() const
373{
374 return m_selectionMode;
375}
376
377void Abstract3DController::setShadowQuality(QAbstract3DGraph::ShadowQuality quality)
378{
379 if (!m_useOrthoProjection)
380 doSetShadowQuality(quality);
381}
382
383void Abstract3DController::doSetShadowQuality(QAbstract3DGraph::ShadowQuality quality)
384{
385 if (quality != m_shadowQuality) {
386 m_shadowQuality = quality;
387 m_changeTracker.shadowQualityChanged = true;
388 emit shadowQualityChanged(quality: m_shadowQuality);
389 emitNeedRender();
390 }
391}
392
393QAbstract3DGraph::ShadowQuality Abstract3DController::shadowQuality() const
394{
395 return m_shadowQuality;
396}
397
398void Abstract3DController::setOptimizationHints(QAbstract3DGraph::OptimizationHints hints)
399{
400 if (hints != m_optimizationHints) {
401 m_optimizationHints = hints;
402 m_changeTracker.optimizationHintChanged = true;
403 m_isDataDirty = true;
404 emit optimizationHintsChanged(hints);
405 emitNeedRender();
406 }
407}
408
409QAbstract3DGraph::OptimizationHints Abstract3DController::optimizationHints() const
410{
411 return m_optimizationHints;
412}
413
414bool Abstract3DController::isSlicingActive() const
415{
416 return m_scene->isSlicingActive();
417}
418
419void Abstract3DController::setSlicingActive(bool isSlicing)
420{
421 m_scene->setSlicingActive(isSlicing);
422}
423
424bool Abstract3DController::isCustomLabelItem(QCustom3DItem *item) const
425{
426 return item->d_ptr->m_isLabelItem;
427}
428
429bool Abstract3DController::isCustomVolumeItem(QCustom3DItem *item) const
430{
431 return item->d_ptr->m_isVolumeItem;
432}
433
434QImage Abstract3DController::customTextureImage(QCustom3DItem *item)
435{
436 return item->d_ptr->textureImage();
437}
438
439Q3DScene *Abstract3DController::scene()
440{
441 return m_scene;
442}
443
444void Abstract3DController::markDataDirty()
445{
446 m_isDataDirty = true;
447
448 markSeriesItemLabelsDirty();
449 emitNeedRender();
450}
451
452void Abstract3DController::markSeriesVisualsDirty()
453{
454 m_isSeriesVisualsDirty = true;
455 emitNeedRender();
456}
457
458int Abstract3DController::addCustomItem(QCustom3DItem *item)
459{
460 if (!item)
461 return -1;
462
463 int index = m_customItems.indexOf(t: item);
464
465 if (index != -1)
466 return index;
467
468 item->setParent(this);
469 connect(sender: item, signal: &QCustom3DItem::needUpdate,
470 context: this, slot: &Abstract3DController::updateCustomItem);
471 m_customItems.append(t: item);
472 item->d_func()->resetDirtyBits();
473 m_isCustomDataDirty = true;
474 emitNeedRender();
475 return m_customItems.size() - 1;
476}
477
478void Abstract3DController::deleteCustomItems()
479{
480 foreach (QCustom3DItem *item, m_customItems)
481 delete item;
482 m_customItems.clear();
483 m_isCustomDataDirty = true;
484 emitNeedRender();
485}
486
487void Abstract3DController::deleteCustomItem(QCustom3DItem *item)
488{
489 if (!item)
490 return;
491
492 m_customItems.removeOne(t: item);
493 delete item;
494 item = 0;
495 m_isCustomDataDirty = true;
496 emitNeedRender();
497}
498
499void Abstract3DController::deleteCustomItem(const QVector3D &position)
500{
501 // Get the item for the position
502 foreach (QCustom3DItem *item, m_customItems) {
503 if (item->position() == position)
504 deleteCustomItem(item);
505 }
506}
507
508void Abstract3DController::releaseCustomItem(QCustom3DItem *item)
509{
510 if (item && m_customItems.contains(t: item)) {
511 disconnect(sender: item, signal: &QCustom3DItem::needUpdate,
512 receiver: this, slot: &Abstract3DController::updateCustomItem);
513 m_customItems.removeOne(t: item);
514 item->setParent(0);
515 m_isCustomDataDirty = true;
516 emitNeedRender();
517 }
518}
519
520QList<QCustom3DItem *> Abstract3DController::customItems() const
521{
522 return m_customItems;
523}
524
525void Abstract3DController::updateCustomItem()
526{
527 m_isCustomItemDirty = true;
528 m_isCustomDataDirty = true;
529 emitNeedRender();
530}
531
532void Abstract3DController::handleAxisTitleChanged(const QString &title)
533{
534 Q_UNUSED(title);
535 handleAxisTitleChangedBySender(sender: sender());
536}
537
538void Abstract3DController::handleAxisTitleChangedBySender(QObject *sender)
539{
540 if (sender == m_axisX)
541 m_changeTracker.axisXTitleChanged = true;
542 else if (sender == m_axisY)
543 m_changeTracker.axisYTitleChanged = true;
544 else if (sender == m_axisZ)
545 m_changeTracker.axisZTitleChanged = true;
546 else
547 qWarning() << __FUNCTION__ << "invoked for invalid axis";
548
549 markSeriesItemLabelsDirty();
550 emitNeedRender();
551}
552
553void Abstract3DController::handleAxisLabelsChanged()
554{
555 handleAxisLabelsChangedBySender(sender: sender());
556}
557
558void Abstract3DController::handleAxisLabelsChangedBySender(QObject *sender)
559{
560 if (sender == m_axisX)
561 m_changeTracker.axisXLabelsChanged = true;
562 else if (sender == m_axisY)
563 m_changeTracker.axisYLabelsChanged = true;
564 else if (sender == m_axisZ)
565 m_changeTracker.axisZLabelsChanged = true;
566 else
567 qWarning() << __FUNCTION__ << "invoked for invalid axis";
568
569 markSeriesItemLabelsDirty();
570 emitNeedRender();
571}
572
573void Abstract3DController::handleAxisRangeChanged(float min, float max)
574{
575 Q_UNUSED(min);
576 Q_UNUSED(max);
577 handleAxisRangeChangedBySender(sender: sender());
578}
579
580void Abstract3DController::handleAxisRangeChangedBySender(QObject *sender)
581{
582 if (sender == m_axisX) {
583 m_isSeriesVisualsDirty = true;
584 m_changeTracker.axisXRangeChanged = true;
585 } else if (sender == m_axisY) {
586 m_isSeriesVisualsDirty = true;
587 m_changeTracker.axisYRangeChanged = true;
588 } else if (sender == m_axisZ) {
589 m_isSeriesVisualsDirty = true;
590 m_changeTracker.axisZRangeChanged = true;
591 } else {
592 qWarning() << __FUNCTION__ << "invoked for invalid axis";
593 }
594 emitNeedRender();
595}
596
597void Abstract3DController::handleAxisSegmentCountChanged(int count)
598{
599 Q_UNUSED(count);
600 handleAxisSegmentCountChangedBySender(sender: sender());
601}
602
603void Abstract3DController::handleAxisSegmentCountChangedBySender(QObject *sender)
604{
605 if (sender == m_axisX)
606 m_changeTracker.axisXSegmentCountChanged = true;
607 else if (sender == m_axisY)
608 m_changeTracker.axisYSegmentCountChanged = true;
609 else if (sender == m_axisZ)
610 m_changeTracker.axisZSegmentCountChanged = true;
611 else
612 qWarning() << __FUNCTION__ << "invoked for invalid axis";
613 emitNeedRender();
614}
615
616void Abstract3DController::handleAxisSubSegmentCountChanged(int count)
617{
618 Q_UNUSED(count);
619 handleAxisSubSegmentCountChangedBySender(sender: sender());
620}
621
622void Abstract3DController::handleAxisSubSegmentCountChangedBySender(QObject *sender)
623{
624 if (sender == m_axisX)
625 m_changeTracker.axisXSubSegmentCountChanged = true;
626 else if (sender == m_axisY)
627 m_changeTracker.axisYSubSegmentCountChanged = true;
628 else if (sender == m_axisZ)
629 m_changeTracker.axisZSubSegmentCountChanged = true;
630 else
631 qWarning() << __FUNCTION__ << "invoked for invalid axis";
632 emitNeedRender();
633}
634
635void Abstract3DController::handleAxisAutoAdjustRangeChanged(bool autoAdjust)
636{
637 QObject *sender = QObject::sender();
638 if (sender != m_axisX && sender != m_axisY && sender != m_axisZ)
639 return;
640
641 QAbstract3DAxis *axis = static_cast<QAbstract3DAxis*>(sender);
642 handleAxisAutoAdjustRangeChangedInOrientation(orientation: axis->orientation(), autoAdjust);
643}
644
645void Abstract3DController::handleAxisLabelFormatChanged(const QString &format)
646{
647 Q_UNUSED(format);
648 handleAxisLabelFormatChangedBySender(sender: sender());
649}
650
651void Abstract3DController::handleAxisReversedChanged(bool enable)
652{
653 Q_UNUSED(enable);
654 handleAxisReversedChangedBySender(sender: sender());
655}
656
657void Abstract3DController::handleAxisFormatterDirty()
658{
659 handleAxisFormatterDirtyBySender(sender: sender());
660}
661
662void Abstract3DController::handleAxisLabelAutoRotationChanged(float angle)
663{
664 Q_UNUSED(angle);
665 handleAxisLabelAutoRotationChangedBySender(sender: sender());
666}
667
668void Abstract3DController::handleAxisTitleVisibilityChanged(bool visible)
669{
670 Q_UNUSED(visible);
671 handleAxisTitleVisibilityChangedBySender(sender: sender());
672}
673
674void Abstract3DController::handleAxisTitleFixedChanged(bool fixed)
675{
676 Q_UNUSED(fixed);
677 handleAxisTitleFixedChangedBySender(sender: sender());
678}
679
680void Abstract3DController::handleInputViewChanged(QAbstract3DInputHandler::InputView view)
681{
682 // When in automatic slicing mode, input view change to primary disables slice mode
683 if (m_selectionMode.testFlag(flag: QAbstract3DGraph::SelectionSlice)
684 && view == QAbstract3DInputHandler::InputViewOnPrimary) {
685 setSlicingActive(false);
686 }
687
688 emitNeedRender();
689}
690
691void Abstract3DController::handleInputPositionChanged(const QPoint &position)
692{
693 Q_UNUSED(position);
694 emitNeedRender();
695}
696
697void Abstract3DController::handleSeriesVisibilityChanged(bool visible)
698{
699 Q_UNUSED(visible);
700
701 handleSeriesVisibilityChangedBySender(sender: sender());
702}
703
704void Abstract3DController::handleRequestShadowQuality(QAbstract3DGraph::ShadowQuality quality)
705{
706 setShadowQuality(quality);
707}
708
709void Abstract3DController::handleAxisLabelFormatChangedBySender(QObject *sender)
710{
711 // Label format changing needs to dirty the data so that labels are reset.
712 if (sender == m_axisX) {
713 m_isDataDirty = true;
714 m_changeTracker.axisXLabelFormatChanged = true;
715 } else if (sender == m_axisY) {
716 m_isDataDirty = true;
717 m_changeTracker.axisYLabelFormatChanged = true;
718 } else if (sender == m_axisZ) {
719 m_isDataDirty = true;
720 m_changeTracker.axisZLabelFormatChanged = true;
721 } else {
722 qWarning() << __FUNCTION__ << "invoked for invalid axis";
723 }
724 emitNeedRender();
725}
726
727void Abstract3DController::handleAxisReversedChangedBySender(QObject *sender)
728{
729 // Reversing change needs to dirty the data so item positions are recalculated
730 if (sender == m_axisX) {
731 m_isDataDirty = true;
732 m_changeTracker.axisXReversedChanged = true;
733 } else if (sender == m_axisY) {
734 m_isDataDirty = true;
735 m_changeTracker.axisYReversedChanged = true;
736 } else if (sender == m_axisZ) {
737 m_isDataDirty = true;
738 m_changeTracker.axisZReversedChanged = true;
739 } else {
740 qWarning() << __FUNCTION__ << "invoked for invalid axis";
741 }
742 emitNeedRender();
743}
744
745void Abstract3DController::handleAxisFormatterDirtyBySender(QObject *sender)
746{
747 // Sender is QValue3DAxisPrivate
748 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(sender);//->qptr();
749 if (valueAxis == m_axisX) {
750 m_isDataDirty = true;
751 m_changeTracker.axisXFormatterChanged = true;
752 } else if (valueAxis == m_axisY) {
753 m_isDataDirty = true;
754 m_changeTracker.axisYFormatterChanged = true;
755 } else if (valueAxis == m_axisZ) {
756 m_isDataDirty = true;
757 m_changeTracker.axisZFormatterChanged = true;
758 } else {
759 qWarning() << __FUNCTION__ << "invoked for invalid axis";
760 }
761 emitNeedRender();
762}
763
764void Abstract3DController::handleAxisLabelAutoRotationChangedBySender(QObject *sender)
765{
766 if (sender == m_axisX)
767 m_changeTracker.axisXLabelAutoRotationChanged = true;
768 else if (sender == m_axisY)
769 m_changeTracker.axisYLabelAutoRotationChanged = true;
770 else if (sender == m_axisZ)
771 m_changeTracker.axisZLabelAutoRotationChanged = true;
772 else
773 qWarning() << __FUNCTION__ << "invoked for invalid axis";
774
775 emitNeedRender();
776}
777
778void Abstract3DController::handleAxisTitleVisibilityChangedBySender(QObject *sender)
779{
780 if (sender == m_axisX)
781 m_changeTracker.axisXTitleVisibilityChanged = true;
782 else if (sender == m_axisY)
783 m_changeTracker.axisYTitleVisibilityChanged = true;
784 else if (sender == m_axisZ)
785 m_changeTracker.axisZTitleVisibilityChanged = true;
786 else
787 qWarning() << __FUNCTION__ << "invoked for invalid axis";
788
789 emitNeedRender();
790}
791
792void Abstract3DController::handleAxisTitleFixedChangedBySender(QObject *sender)
793{
794 if (sender == m_axisX)
795 m_changeTracker.axisXTitleFixedChanged = true;
796 else if (sender == m_axisY)
797 m_changeTracker.axisYTitleFixedChanged = true;
798 else if (sender == m_axisZ)
799 m_changeTracker.axisZTitleFixedChanged = true;
800 else
801 qWarning() << __FUNCTION__ << "invoked for invalid axis";
802
803 emitNeedRender();
804}
805
806void Abstract3DController::handleSeriesVisibilityChangedBySender(QObject *sender)
807{
808 QAbstract3DSeries *series = static_cast<QAbstract3DSeries *>(sender);
809 series->d_func()->m_changeTracker.visibilityChanged = true;
810
811 m_isDataDirty = true;
812 m_isSeriesVisualsDirty = true;
813
814 adjustAxisRanges();
815
816 emitNeedRender();
817}
818
819void Abstract3DController::markSeriesItemLabelsDirty()
820{
821 for (int i = 0; i < m_seriesList.size(); i++)
822 m_seriesList.at(i)->d_func()->markItemLabelDirty();
823}
824
825void Abstract3DController::setAxisHelper(QAbstract3DAxis::AxisOrientation orientation,
826 QAbstract3DAxis *axis, QAbstract3DAxis **axisPtr)
827{
828 // Setting null axis indicates using default axis
829 if (!axis)
830 axis = createDefaultAxis(orientation);
831
832 // If old axis is default axis, delete it
833 QAbstract3DAxis *oldAxis = *axisPtr;
834 if (oldAxis) {
835 if (oldAxis->d_func()->isDefaultAxis()) {
836 m_axes.removeAll(t: oldAxis);
837 delete oldAxis;
838 oldAxis = 0;
839 } else {
840 // Disconnect the old axis from use
841 QObject::disconnect(sender: oldAxis, signal: 0, receiver: this, member: 0);
842 oldAxis->d_func()->setOrientation(QAbstract3DAxis::AxisOrientationNone);
843 }
844 }
845
846 // Assume ownership
847 addAxis(axis);
848
849 // Connect the new axis
850 *axisPtr = axis;
851
852 axis->d_func()->setOrientation(orientation);
853
854 QObject::connect(sender: axis, signal: &QAbstract3DAxis::titleChanged,
855 context: this, slot: &Abstract3DController::handleAxisTitleChanged);
856 QObject::connect(sender: axis, signal: &QAbstract3DAxis::labelsChanged,
857 context: this, slot: &Abstract3DController::handleAxisLabelsChanged);
858 QObject::connect(sender: axis, signal: &QAbstract3DAxis::rangeChanged,
859 context: this, slot: &Abstract3DController::handleAxisRangeChanged);
860 QObject::connect(sender: axis, signal: &QAbstract3DAxis::autoAdjustRangeChanged,
861 context: this, slot: &Abstract3DController::handleAxisAutoAdjustRangeChanged);
862 QObject::connect(sender: axis, signal: &QAbstract3DAxis::labelAutoRotationChanged,
863 context: this, slot: &Abstract3DController::handleAxisLabelAutoRotationChanged);
864 QObject::connect(sender: axis, signal: &QAbstract3DAxis::titleVisibilityChanged,
865 context: this, slot: &Abstract3DController::handleAxisTitleVisibilityChanged);
866 QObject::connect(sender: axis, signal: &QAbstract3DAxis::titleFixedChanged,
867 context: this, slot: &Abstract3DController::handleAxisTitleFixedChanged);
868
869 if (orientation == QAbstract3DAxis::AxisOrientationX)
870 m_changeTracker.axisXTypeChanged = true;
871 else if (orientation == QAbstract3DAxis::AxisOrientationY)
872 m_changeTracker.axisYTypeChanged = true;
873 else if (orientation == QAbstract3DAxis::AxisOrientationZ)
874 m_changeTracker.axisZTypeChanged = true;
875
876 handleAxisTitleChangedBySender(sender: axis);
877 handleAxisLabelsChangedBySender(sender: axis);
878 handleAxisRangeChangedBySender(sender: axis);
879 handleAxisAutoAdjustRangeChangedInOrientation(orientation: axis->orientation(),
880 autoAdjust: axis->isAutoAdjustRange());
881 handleAxisLabelAutoRotationChangedBySender(sender: axis);
882 handleAxisTitleVisibilityChangedBySender(sender: axis);
883 handleAxisTitleFixedChangedBySender(sender: axis);
884
885 if (axis->type() & QAbstract3DAxis::AxisTypeValue) {
886 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(axis);
887 QObject::connect(sender: valueAxis, signal: &QValue3DAxis::segmentCountChanged,
888 context: this, slot: &Abstract3DController::handleAxisSegmentCountChanged);
889 QObject::connect(sender: valueAxis, signal: &QValue3DAxis::subSegmentCountChanged,
890 context: this, slot: &Abstract3DController::handleAxisSubSegmentCountChanged);
891 QObject::connect(sender: valueAxis, signal: &QValue3DAxis::labelFormatChanged,
892 context: this, slot: &Abstract3DController::handleAxisLabelFormatChanged);
893 QObject::connect(sender: valueAxis, signal: &QValue3DAxis::reversedChanged,
894 context: this, slot: &Abstract3DController::handleAxisReversedChanged);
895 // TODO: Handle this somehow (add API to QValue3DAxis?)
896// QObject::connect(valueAxis->d_func(), &QValue3DAxisPrivate::formatterDirty,
897// this, &Abstract3DController::handleAxisFormatterDirty);
898
899 handleAxisSegmentCountChangedBySender(sender: valueAxis);
900 handleAxisSubSegmentCountChangedBySender(sender: valueAxis);
901 handleAxisLabelFormatChangedBySender(sender: valueAxis);
902 handleAxisReversedChangedBySender(sender: valueAxis);
903 // TODO: Handle this somehow (add API to QValue3DAxis?)
904// handleAxisFormatterDirtyBySender(valueAxis->d_func());
905
906 valueAxis->formatter()->setLocale(m_locale);
907 }
908}
909
910QAbstract3DAxis *Abstract3DController::createDefaultAxis(
911 QAbstract3DAxis::AxisOrientation orientation)
912{
913 Q_UNUSED(orientation);
914
915 // The default default axis is a value axis. If the graph type has a different default axis
916 // for some orientation, this function needs to be overridden.
917 QAbstract3DAxis *defaultAxis = createDefaultValueAxis();
918 return defaultAxis;
919}
920
921QValue3DAxis *Abstract3DController::createDefaultValueAxis()
922{
923 // Default value axis has single segment, empty label format, and auto scaling
924 QValue3DAxis *defaultAxis = new QValue3DAxis;
925 defaultAxis->d_func()->setDefaultAxis(true);
926
927 return defaultAxis;
928}
929
930QCategory3DAxis *Abstract3DController::createDefaultCategoryAxis()
931{
932 // Default category axis has no labels
933 QCategory3DAxis *defaultAxis = new QCategory3DAxis;
934 defaultAxis->d_func()->setDefaultAxis(true);
935 return defaultAxis;
936}
937
938void Abstract3DController::startRecordingRemovesAndInserts()
939{
940 // Default implementation does nothing
941}
942
943void Abstract3DController::emitNeedRender()
944{
945 if (!m_renderPending) {
946 emit needRender();
947 m_renderPending = true;
948 }
949}
950
951int Abstract3DController::selectedLabelIndex() const
952{
953 int index = m_selectedLabelIndex;
954 QAbstract3DAxis *axis = selectedAxis();
955 if (axis && axis->labels().size() <= index)
956 index = -1;
957 return index;
958}
959
960QAbstract3DAxis *Abstract3DController::selectedAxis() const
961{
962 QAbstract3DAxis *axis = 0;
963 QAbstract3DGraph::ElementType type = m_clickedType;
964 switch (type) {
965 case QAbstract3DGraph::ElementAxisXLabel:
966 axis = axisX();
967 break;
968 case QAbstract3DGraph::ElementAxisYLabel:
969 axis = axisY();
970 break;
971 case QAbstract3DGraph::ElementAxisZLabel:
972 axis = axisZ();
973 break;
974 default:
975 axis = 0;
976 break;
977 }
978
979 return axis;
980}
981
982int Abstract3DController::selectedCustomItemIndex() const
983{
984 int index = m_selectedCustomItemIndex;
985 if (m_customItems.size() <= index)
986 index = -1;
987 return index;
988}
989
990QCustom3DItem *Abstract3DController::selectedCustomItem() const
991{
992 QCustom3DItem *item = 0;
993 int index = selectedCustomItemIndex();
994 if (index >= 0)
995 item = m_customItems[index];
996 return item;
997}
998
999QAbstract3DGraph::ElementType Abstract3DController::selectedElement() const
1000{
1001 return m_clickedType;
1002}
1003
1004void Abstract3DController::setOrthoProjection(bool enable)
1005{
1006 if (enable != m_useOrthoProjection) {
1007 m_useOrthoProjection = enable;
1008 m_changeTracker.projectionChanged = true;
1009 emit orthoProjectionChanged(enabled: m_useOrthoProjection);
1010 // If changed to ortho, disable shadows
1011 if (m_useOrthoProjection)
1012 doSetShadowQuality(quality: QAbstract3DGraph::ShadowQualityNone);
1013 emitNeedRender();
1014 }
1015}
1016
1017bool Abstract3DController::isOrthoProjection() const
1018{
1019 return m_useOrthoProjection;
1020}
1021
1022void Abstract3DController::setAspectRatio(qreal ratio)
1023{
1024 if (m_aspectRatio != ratio) {
1025 m_aspectRatio = ratio;
1026 m_changeTracker.aspectRatioChanged = true;
1027 emit aspectRatioChanged(ratio: m_aspectRatio);
1028 m_isDataDirty = true;
1029 emitNeedRender();
1030 }
1031}
1032
1033qreal Abstract3DController::aspectRatio()
1034{
1035 return m_aspectRatio;
1036}
1037
1038void Abstract3DController::setHorizontalAspectRatio(qreal ratio)
1039{
1040 if (m_horizontalAspectRatio != ratio) {
1041 m_horizontalAspectRatio = ratio;
1042 m_changeTracker.horizontalAspectRatioChanged = true;
1043 emit horizontalAspectRatioChanged(ratio: m_horizontalAspectRatio);
1044 m_isDataDirty = true;
1045 emitNeedRender();
1046 }
1047}
1048
1049qreal Abstract3DController::horizontalAspectRatio() const
1050{
1051 return m_horizontalAspectRatio;
1052}
1053
1054void Abstract3DController::setReflection(bool enable)
1055{
1056 if (m_reflectionEnabled != enable) {
1057 m_reflectionEnabled = enable;
1058 m_changeTracker.reflectionChanged = true;
1059 emit reflectionChanged(enabled: m_reflectionEnabled);
1060 emitNeedRender();
1061 }
1062}
1063
1064bool Abstract3DController::reflection() const
1065{
1066 return m_reflectionEnabled;
1067}
1068
1069void Abstract3DController::setReflectivity(qreal reflectivity)
1070{
1071 if (m_reflectivity != reflectivity) {
1072 m_reflectivity = reflectivity;
1073 m_changeTracker.reflectivityChanged = true;
1074 emit reflectivityChanged(reflectivity: m_reflectivity);
1075 emitNeedRender();
1076 }
1077}
1078
1079qreal Abstract3DController::reflectivity() const
1080{
1081 return m_reflectivity;
1082}
1083
1084void Abstract3DController::setPolar(bool enable)
1085{
1086 if (enable != m_isPolar) {
1087 m_isPolar = enable;
1088 m_changeTracker.polarChanged = true;
1089 m_isDataDirty = true;
1090 emit polarChanged(enabled: m_isPolar);
1091 emitNeedRender();
1092 }
1093}
1094
1095bool Abstract3DController::isPolar() const
1096{
1097 return m_isPolar;
1098}
1099
1100void Abstract3DController::setRadialLabelOffset(float offset)
1101{
1102 if (m_radialLabelOffset != offset) {
1103 m_radialLabelOffset = offset;
1104 m_changeTracker.radialLabelOffsetChanged = true;
1105 emit radialLabelOffsetChanged(offset: m_radialLabelOffset);
1106 emitNeedRender();
1107 }
1108}
1109
1110float Abstract3DController::radialLabelOffset() const
1111{
1112 return m_radialLabelOffset;
1113}
1114
1115void Abstract3DController::setLocale(const QLocale &locale)
1116{
1117 if (m_locale != locale) {
1118 m_locale = locale;
1119
1120 // Value axis formatters need to be updated
1121 QValue3DAxis *axis = qobject_cast<QValue3DAxis *>(object: m_axisX);
1122 if (axis)
1123 axis->formatter()->setLocale(m_locale);
1124 axis = qobject_cast<QValue3DAxis *>(object: m_axisY);
1125 if (axis)
1126 axis->formatter()->setLocale(m_locale);
1127 axis = qobject_cast<QValue3DAxis *>(object: m_axisZ);
1128 if (axis)
1129 axis->formatter()->setLocale(m_locale);
1130 emit localeChanged(locale: m_locale);
1131 }
1132}
1133
1134QLocale Abstract3DController::locale() const
1135{
1136 return m_locale;
1137}
1138
1139QVector3D Abstract3DController::queriedGraphPosition() const
1140{
1141 return m_queriedGraphPosition;
1142}
1143
1144void Abstract3DController::setMargin(qreal margin)
1145{
1146 if (m_margin != margin) {
1147 m_margin = margin;
1148 m_changeTracker.marginChanged = true;
1149 emit marginChanged(margin);
1150 emitNeedRender();
1151 }
1152}
1153
1154qreal Abstract3DController::margin() const
1155{
1156 return m_margin;
1157}
1158
1159QT_END_NAMESPACE
1160

source code of qtgraphs/src/graphs/engine/abstract3dcontroller.cpp