/**************************************************************************** ** ** Copyright (C) 2014 Digia Plc ** All rights reserved. ** For any questions to Digia, please use contact form at http://qt.io ** ** This file is part of the Qt Data Visualization module. ** ** Licensees holding valid commercial license for Qt may use this file in ** accordance with the Qt License Agreement provided with the Software ** or, alternatively, in accordance with the terms contained in a written ** agreement between you and Digia. ** ** If you have questions regarding the use of this file, please use ** contact form at http://qt.io ** ****************************************************************************/ #include "surface3dcontroller_p.h" #include "surface3drenderer_p.h" #include "qvalue3daxis_p.h" #include "qsurfacedataproxy_p.h" #include "qsurface3dseries_p.h" QT_BEGIN_NAMESPACE_DATAVISUALIZATION 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() { // 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() { 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(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(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 Surface3DController::surfaceSeriesList() { QList abstractSeriesList = seriesList(); QList surfaceSeriesList; foreach (QAbstract3DSeries *abstractSeries, abstractSeriesList) { QSurface3DSeries *surfaceSeries = qobject_cast(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 foreach (QAbstract3DSeries *otherSeries, m_seriesList) { QSurface3DSeries *surfaceSeries = static_cast(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 = static_cast(sender())->series(); 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 foreach (QAbstract3DSeries *series, m_seriesList) { QSurface3DSeries *surfaceSeries = static_cast(series); emit surfaceSeries->flatShadingSupportedChanged(m_flatShadingSupported); } } } void Surface3DController::handleRowsChanged(int startIndex, int count) { QSurface3DSeries *series = static_cast(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(QObject::sender()); QSurface3DSeries *series = sender->series(); bool newItem = true; QPoint candidate(rowIndex, columnIndex); foreach (ChangeItem 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(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(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(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(m_axisX); QValue3DAxis *valueAxisY = static_cast(m_axisY); QValue3DAxis *valueAxisZ = static_cast(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(m_seriesList.at(series)); const QSurfaceDataProxy *proxy = surfaceSeries->dataProxy(); if (surfaceSeries->isVisible() && proxy) { QVector3D minLimits; QVector3D maxLimits; proxy->dptrc()->limitValues(minLimits, maxLimits); 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_DATAVISUALIZATION