Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
++
++ #include "surface3dcontroller_p.h"
++ #include "surface3drenderer_p.h"
++ #include "qvalue3daxis_p.h"
++ #include "qsurfacedataproxy_p.h"
++ #include "qsurface3dseries_p.h"
++ #include <QtCore/QMutexLocker>
++
++ QT_BEGIN_NAMESPACE
++
++ Surface3DController::Surface3DController(QRect rect, Q3DScene *scene)
++ : Abstract3DController(rect, scene),
++ m_renderer(0),
++ m_selectedPoint(invalidSelectionPosition()),
++ m_selectedSeries(0),
++ m_flatShadingSupported(true),
++ m_flipHorizontalGrid(false)
++ {
++ // Setting a null axis creates a new default axis according to orientation and graph type.
++ // Note: these cannot be set in the Abstract3DController constructor, as they will call virtual
++ // functions implemented by subclasses.
++ setAxisX(0);
++ setAxisY(0);
++ setAxisZ(0);
++ }
++
++ Surface3DController::~Surface3DController()
++ {
++ }
++
++ void Surface3DController::initializeOpenGL()
++ {
++ QMutexLocker mutexLocker(&m_renderMutex);
++
++ // Initialization is called multiple times when Qt Quick components are used
++ if (isInitialized())
++ return;
++
++ m_renderer = new Surface3DRenderer(this);
++ setRenderer(m_renderer);
++
++ emitNeedRender();
++ }
++
++ void Surface3DController::synchDataToRenderer()
++ {
++ QMutexLocker mutexLocker(&m_renderMutex);
++
++ if (!isInitialized())
++ return;
++
++ Abstract3DController::synchDataToRenderer();
++
++ // Notify changes to renderer
++ if (m_changeTracker.rowsChanged) {
++ m_renderer->updateRows(m_changedRows);
++ m_changeTracker.rowsChanged = false;
++ m_changedRows.clear();
++ }
++
++ if (m_changeTracker.itemChanged) {
++ m_renderer->updateItems(m_changedItems);
++ m_changeTracker.itemChanged = false;
++ m_changedItems.clear();
++ }
++
++ if (m_changeTracker.selectedPointChanged) {
++ m_renderer->updateSelectedPoint(m_selectedPoint, m_selectedSeries);
++ m_changeTracker.selectedPointChanged = false;
++ }
++
++ if (m_changeTracker.flipHorizontalGridChanged) {
++ m_renderer->updateFlipHorizontalGrid(m_flipHorizontalGrid);
++ m_changeTracker.flipHorizontalGridChanged = false;
++ }
++
++ if (m_changeTracker.surfaceTextureChanged) {
++ m_renderer->updateSurfaceTextures(m_changedTextures);
++ m_changeTracker.surfaceTextureChanged = false;
++ m_changedTextures.clear();
++ }
++ }
++
++ void Surface3DController::handleAxisAutoAdjustRangeChangedInOrientation(
++ QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
++ {
++ Q_UNUSED(orientation);
++ Q_UNUSED(autoAdjust);
++
++ adjustAxisRanges();
++ }
++
++ void Surface3DController::handleAxisRangeChangedBySender(QObject *sender)
++ {
++ Abstract3DController::handleAxisRangeChangedBySender(sender);
++
++ // Update selected point - may be moved offscreen
++ setSelectedPoint(m_selectedPoint, m_selectedSeries, false);
++ }
++
++ void Surface3DController::handleSeriesVisibilityChangedBySender(QObject *sender)
++ {
++ Abstract3DController::handleSeriesVisibilityChangedBySender(sender);
++
++ // Visibility changes may require disabling slicing,
++ // so just reset selection to ensure everything is still valid.
++ setSelectedPoint(m_selectedPoint, m_selectedSeries, false);
++ }
++
++ void Surface3DController::handlePendingClick()
++ {
++ // This function is called while doing the sync, so it is okay to query from renderer
++ QPoint position = m_renderer->clickedPosition();
++ QSurface3DSeries *series = static_cast<QSurface3DSeries *>(m_renderer->clickedSeries());
++
++ setSelectedPoint(position, series, true);
++
++ Abstract3DController::handlePendingClick();
++
++ m_renderer->resetClickedStatus();
++ }
++
++ QPoint Surface3DController::invalidSelectionPosition()
++ {
++ static QPoint invalidSelectionPoint(-1, -1);
++ return invalidSelectionPoint;
++ }
++
++ bool Surface3DController::isFlatShadingSupported()
++ {
++ return m_flatShadingSupported;
++ }
++
++ void Surface3DController::addSeries(QAbstract3DSeries *series)
++ {
++ Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesTypeSurface);
++
++ Abstract3DController::addSeries(series);
++
++ QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
++ if (surfaceSeries->selectedPoint() != invalidSelectionPosition())
++ setSelectedPoint(surfaceSeries->selectedPoint(), surfaceSeries, false);
++
++ if (!surfaceSeries->texture().isNull())
++ updateSurfaceTexture(surfaceSeries);
++ }
++
++ void Surface3DController::removeSeries(QAbstract3DSeries *series)
++ {
++ bool wasVisible = (series && series->d_ptr->m_controller == this && series->isVisible());
++
++ Abstract3DController::removeSeries(series);
++
++ if (m_selectedSeries == series)
++ setSelectedPoint(invalidSelectionPosition(), 0, false);
++
++ if (wasVisible)
++ adjustAxisRanges();
++ }
++
++ QList<QSurface3DSeries *> Surface3DController::surfaceSeriesList()
++ {
++ QList<QAbstract3DSeries *> abstractSeriesList = seriesList();
++ QList<QSurface3DSeries *> surfaceSeriesList;
++ for (auto abstractSeries: abstractSeriesList) {
++ QSurface3DSeries *surfaceSeries = qobject_cast<QSurface3DSeries *>(abstractSeries);
++ if (surfaceSeries)
++ surfaceSeriesList.append(surfaceSeries);
++ }
++
++ return surfaceSeriesList;
++ }
++
++ void Surface3DController::setFlipHorizontalGrid(bool flip)
++ {
++ if (m_flipHorizontalGrid != flip) {
++ m_flipHorizontalGrid = flip;
++ m_changeTracker.flipHorizontalGridChanged = true;
++ emit flipHorizontalGridChanged(flip);
++ emitNeedRender();
++ }
++ }
++
++ bool Surface3DController::flipHorizontalGrid() const
++ {
++ return m_flipHorizontalGrid;
++ }
++
++ void Surface3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode)
++ {
++ // Currently surface only supports row and column modes when also slicing
++ if ((mode.testFlag(QAbstract3DGraph::SelectionRow)
++ || mode.testFlag(QAbstract3DGraph::SelectionColumn))
++ && !mode.testFlag(QAbstract3DGraph::SelectionSlice)) {
++ qWarning("Unsupported selection mode.");
++ return;
++ } else if (mode.testFlag(QAbstract3DGraph::SelectionSlice)
++ && (mode.testFlag(QAbstract3DGraph::SelectionRow)
++ == mode.testFlag(QAbstract3DGraph::SelectionColumn))) {
++ qWarning("Must specify one of either row or column selection mode in conjunction with slicing mode.");
++ } else {
++ QAbstract3DGraph::SelectionFlags oldMode = selectionMode();
++
++ Abstract3DController::setSelectionMode(mode);
++
++ if (mode != oldMode) {
++ // Refresh selection upon mode change to ensure slicing is correctly updated
++ // according to series the visibility.
++ setSelectedPoint(m_selectedPoint, m_selectedSeries, true);
++
++ // Special case: Always deactivate slicing when changing away from slice
++ // automanagement, as this can't be handled in setSelectedBar.
++ if (!mode.testFlag(QAbstract3DGraph::SelectionSlice)
++ && oldMode.testFlag(QAbstract3DGraph::SelectionSlice)) {
++ scene()->setSlicingActive(false);
++ }
++ }
++ }
++ }
++
++ void Surface3DController::setSelectedPoint(const QPoint &position, QSurface3DSeries *series,
++ bool enterSlice)
++ {
++ // If the selection targets non-existent point, clear selection instead.
++ QPoint pos = position;
++
++ // Series may already have been removed, so check it before setting the selection.
++ if (!m_seriesList.contains(series))
++ series = 0;
++
++ const QSurfaceDataProxy *proxy = 0;
++ if (series)
++ proxy = series->dataProxy();
++
++ if (!proxy)
++ pos = invalidSelectionPosition();
++
++ if (pos != invalidSelectionPosition()) {
++ int maxRow = proxy->rowCount() - 1;
++ int maxCol = proxy->columnCount() - 1;
++
++ if (pos.x() < 0 || pos.x() > maxRow || pos.y() < 0 || pos.y() > maxCol)
++ pos = invalidSelectionPosition();
++ }
++
++ if (selectionMode().testFlag(QAbstract3DGraph::SelectionSlice)) {
++ if (pos == invalidSelectionPosition() || !series->isVisible()) {
++ scene()->setSlicingActive(false);
++ } else {
++ // If the selected point is outside data window, or there is no selected point, disable slicing
++ float axisMinX = m_axisX->min();
++ float axisMaxX = m_axisX->max();
++ float axisMinZ = m_axisZ->min();
++ float axisMaxZ = m_axisZ->max();
++
++ QSurfaceDataItem item = proxy->array()->at(pos.x())->at(pos.y());
++ if (item.x() < axisMinX || item.x() > axisMaxX
++ || item.z() < axisMinZ || item.z() > axisMaxZ) {
++ scene()->setSlicingActive(false);
++ } else if (enterSlice) {
++ scene()->setSlicingActive(true);
++ }
++ }
++ emitNeedRender();
++ }
++
++ if (pos != m_selectedPoint || series != m_selectedSeries) {
++ bool seriesChanged = (series != m_selectedSeries);
++ m_selectedPoint = pos;
++ m_selectedSeries = series;
++ m_changeTracker.selectedPointChanged = true;
++
++ // Clear selection from other series and finally set new selection to the specified series
++ for (auto otherSeries: m_seriesList) {
++ QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(otherSeries);
++ if (surfaceSeries != m_selectedSeries)
++ surfaceSeries->dptr()->setSelectedPoint(invalidSelectionPosition());
++ }
++ if (m_selectedSeries)
++ m_selectedSeries->dptr()->setSelectedPoint(m_selectedPoint);
++
++ if (seriesChanged)
++ emit selectedSeriesChanged(m_selectedSeries);
++
++ emitNeedRender();
++ }
++ }
++
++ void Surface3DController::clearSelection()
++ {
++ setSelectedPoint(invalidSelectionPosition(), 0, false);
++ }
++
++ void Surface3DController::handleArrayReset()
++ {
++ QSurface3DSeries *series;
++ if (qobject_cast<QSurfaceDataProxy *>(sender()))
++ series = static_cast<QSurfaceDataProxy *>(sender())->series();
++ else
++ series = static_cast<QSurface3DSeries *>(sender());
++
++ if (series->isVisible()) {
++ adjustAxisRanges();
++ m_isDataDirty = true;
++ }
++ if (!m_changedSeriesList.contains(series))
++ m_changedSeriesList.append(series);
++
++ // Clear selection unless still valid
++ setSelectedPoint(m_selectedPoint, m_selectedSeries, false);
++ series->d_ptr->markItemLabelDirty();
++ emitNeedRender();
++ }
++
++ void Surface3DController::handleFlatShadingSupportedChange(bool supported)
++ {
++ // Handle renderer flat surface support indicator signal. This happens exactly once per renderer.
++ if (m_flatShadingSupported != supported) {
++ m_flatShadingSupported = supported;
++ // Emit the change for all added surfaces
++ for (auto series: m_seriesList) {
++ QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
++ emit surfaceSeries->flatShadingSupportedChanged(m_flatShadingSupported);
++ }
++ }
++ }
++
++ void Surface3DController::handleRowsChanged(int startIndex, int count)
++ {
++ QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(QObject::sender())->series();
++ int oldChangeCount = m_changedRows.size();
++ if (!oldChangeCount)
++ m_changedRows.reserve(count);
++
++ int selectedRow = m_selectedPoint.x();
++ for (int i = 0; i < count; i++) {
++ bool newItem = true;
++ int candidate = startIndex + i;
++ for (int j = 0; j < oldChangeCount; j++) {
++ const ChangeRow &oldChangeItem = m_changedRows.at(j);
++ if (oldChangeItem.row == candidate && series == oldChangeItem.series) {
++ newItem = false;
++ break;
++ }
++ }
++ if (newItem) {
++ ChangeRow newChangeItem = {series, candidate};
++ m_changedRows.append(newChangeItem);
++ if (series == m_selectedSeries && selectedRow == candidate)
++ series->d_ptr->markItemLabelDirty();
++ }
++ }
++ if (count) {
++ m_changeTracker.rowsChanged = true;
++
++ if (series->isVisible())
++ adjustAxisRanges();
++ emitNeedRender();
++ }
++ }
++
++ void Surface3DController::handleItemChanged(int rowIndex, int columnIndex)
++ {
++ QSurfaceDataProxy *sender = static_cast<QSurfaceDataProxy *>(QObject::sender());
++ QSurface3DSeries *series = sender->series();
++
++ bool newItem = true;
++ QPoint candidate(rowIndex, columnIndex);
++ for (auto item: m_changedItems) {
++ if (item.point == candidate && item.series == series) {
++ newItem = false;
++ break;
++ }
++ }
++ if (newItem) {
++ ChangeItem newItem = {series, candidate};
++ m_changedItems.append(newItem);
++ m_changeTracker.itemChanged = true;
++
++ if (series == m_selectedSeries && m_selectedPoint == candidate)
++ series->d_ptr->markItemLabelDirty();
++
++ if (series->isVisible())
++ adjustAxisRanges();
++ emitNeedRender();
++ }
++ }
++
++ void Surface3DController::handleRowsAdded(int startIndex, int count)
++ {
++ Q_UNUSED(startIndex);
++ Q_UNUSED(count);
++ QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
++ if (series->isVisible()) {
++ adjustAxisRanges();
++ m_isDataDirty = true;
++ }
++ if (!m_changedSeriesList.contains(series))
++ m_changedSeriesList.append(series);
++ emitNeedRender();
++ }
++
++ void Surface3DController::handleRowsInserted(int startIndex, int count)
++ {
++ Q_UNUSED(startIndex);
++ Q_UNUSED(count);
++ QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
++ if (series == m_selectedSeries) {
++ // If rows inserted to selected series before the selection, adjust the selection
++ int selectedRow = m_selectedPoint.x();
++ if (startIndex <= selectedRow) {
++ selectedRow += count;
++ setSelectedPoint(QPoint(selectedRow, m_selectedPoint.y()), m_selectedSeries, false);
++ }
++ }
++
++ if (series->isVisible()) {
++ adjustAxisRanges();
++ m_isDataDirty = true;
++ }
++ if (!m_changedSeriesList.contains(series))
++ m_changedSeriesList.append(series);
++
++ emitNeedRender();
++ }
++
++ void Surface3DController::handleRowsRemoved(int startIndex, int count)
++ {
++ Q_UNUSED(startIndex);
++ Q_UNUSED(count);
++ QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
++ if (series == m_selectedSeries) {
++ // If rows removed from selected series before the selection, adjust the selection
++ int selectedRow = m_selectedPoint.x();
++ if (startIndex <= selectedRow) {
++ if ((startIndex + count) > selectedRow)
++ selectedRow = -1; // Selected row removed
++ else
++ selectedRow -= count; // Move selected row down by amount of rows removed
++
++ setSelectedPoint(QPoint(selectedRow, m_selectedPoint.y()), m_selectedSeries, false);
++ }
++ }
++
++ if (series->isVisible()) {
++ adjustAxisRanges();
++ m_isDataDirty = true;
++ }
++ if (!m_changedSeriesList.contains(series))
++ m_changedSeriesList.append(series);
++
++ emitNeedRender();
++ }
++
++ void Surface3DController::updateSurfaceTexture(QSurface3DSeries *series)
++ {
++ m_changeTracker.surfaceTextureChanged = true;
++
++ if (!m_changedTextures.contains(series))
++ m_changedTextures.append(series);
++
++ emitNeedRender();
++ }
++
++ void Surface3DController::adjustAxisRanges()
++ {
++ QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX);
++ QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_axisY);
++ QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(m_axisZ);
++ bool adjustX = (valueAxisX && valueAxisX->isAutoAdjustRange());
++ bool adjustY = (valueAxisY && valueAxisY->isAutoAdjustRange());
++ bool adjustZ = (valueAxisZ && valueAxisZ->isAutoAdjustRange());
++ bool first = true;
++
++ if (adjustX || adjustY || adjustZ) {
++ float minValueX = 0.0f;
++ float maxValueX = 0.0f;
++ float minValueY = 0.0f;
++ float maxValueY = 0.0f;
++ float minValueZ = 0.0f;
++ float maxValueZ = 0.0f;
++ int seriesCount = m_seriesList.size();
++ for (int series = 0; series < seriesCount; series++) {
++ const QSurface3DSeries *surfaceSeries =
++ static_cast<QSurface3DSeries *>(m_seriesList.at(series));
++ const QSurfaceDataProxy *proxy = surfaceSeries->dataProxy();
++ if (surfaceSeries->isVisible() && proxy) {
++ QVector3D minLimits;
++ QVector3D maxLimits;
++ proxy->dptrc()->limitValues(minLimits, maxLimits, valueAxisX, valueAxisY, valueAxisZ);
++ if (adjustX) {
++ if (first) {
++ // First series initializes the values
++ minValueX = minLimits.x();
++ maxValueX = maxLimits.x();
++ } else {
++ minValueX = qMin(minValueX, minLimits.x());
++ maxValueX = qMax(maxValueX, maxLimits.x());
++ }
++ }
++ if (adjustY) {
++ if (first) {
++ // First series initializes the values
++ minValueY = minLimits.y();
++ maxValueY = maxLimits.y();
++ } else {
++ minValueY = qMin(minValueY, minLimits.y());
++ maxValueY = qMax(maxValueY, maxLimits.y());
++ }
++ }
++ if (adjustZ) {
++ if (first) {
++ // First series initializes the values
++ minValueZ = minLimits.z();
++ maxValueZ = maxLimits.z();
++ } else {
++ minValueZ = qMin(minValueZ, minLimits.z());
++ maxValueZ = qMax(maxValueZ, maxLimits.z());
++ }
++ }
++ first = false;
++ }
++ }
++
++ static const float adjustmentRatio = 20.0f;
++ static const float defaultAdjustment = 1.0f;
++
++ if (adjustX) {
++ // If all points at same coordinate, need to default to some valid range
++ float adjustment = 0.0f;
++ if (minValueX == maxValueX) {
++ if (adjustZ) {
++ // X and Z are linked to have similar unit size, so choose the valid range based on it
++ if (minValueZ == maxValueZ)
++ adjustment = defaultAdjustment;
++ else
++ adjustment = qAbs(maxValueZ - minValueZ) / adjustmentRatio;
++ } else {
++ if (valueAxisZ)
++ adjustment = qAbs(valueAxisZ->max() - valueAxisZ->min()) / adjustmentRatio;
++ else
++ adjustment = defaultAdjustment;
++ }
++ }
++ valueAxisX->dptr()->setRange(minValueX - adjustment, maxValueX + adjustment, true);
++ }
++ if (adjustY) {
++ // If all points at same coordinate, need to default to some valid range
++ // Y-axis unit is not dependent on other axes, so simply adjust +-1.0f
++ float adjustment = 0.0f;
++ if (minValueY == maxValueY)
++ adjustment = defaultAdjustment;
++ valueAxisY->dptr()->setRange(minValueY - adjustment, maxValueY + adjustment, true);
++ }
++ if (adjustZ) {
++ // If all points at same coordinate, need to default to some valid range
++ float adjustment = 0.0f;
++ if (minValueZ == maxValueZ) {
++ if (adjustX) {
++ // X and Z are linked to have similar unit size, so choose the valid range based on it
++ if (minValueX == maxValueX)
++ adjustment = defaultAdjustment;
++ else
++ adjustment = qAbs(maxValueX - minValueX) / adjustmentRatio;
++ } else {
++ if (valueAxisX)
++ adjustment = qAbs(valueAxisX->max() - valueAxisX->min()) / adjustmentRatio;
++ else
++ adjustment = defaultAdjustment;
++ }
++ }
++ valueAxisZ->dptr()->setRange(minValueZ - adjustment, maxValueZ + adjustment, true);
++ }
++ }
++ }
++
++ QT_END_NAMESPACE