diff options
19 files changed, 480 insertions, 98 deletions
diff --git a/examples/itemmodel/main.cpp b/examples/itemmodel/main.cpp index 46824993..bdecd1a5 100644 --- a/examples/itemmodel/main.cpp +++ b/examples/itemmodel/main.cpp @@ -84,17 +84,18 @@ GraphDataGenerator::GraphDataGenerator(Q3DBars *bargraph, QTableWidget *tableWid #ifndef USE_STATIC_DATA // Set up sample space; make it as deep as it's wide - m_graph->setDataWindow(m_rowCount, m_columnCount); + m_graph->rowAxis()->setRange(0, m_rowCount); + m_graph->columnAxis()->setRange(0, m_columnCount); m_tableWidget->setColumnCount(m_columnCount); // Set selection mode to full m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemRowAndColumn); // Hide axis labels by explicitly setting one empty string as label list - m_graph->rowAxis()->setCategoryLabels(QStringList(QString())); - m_graph->columnAxis()->setCategoryLabels(QStringList(QString())); + m_graph->rowAxis()->setLabels(QStringList(QString())); + m_graph->columnAxis()->setLabels(QStringList(QString())); - m_graph->activeDataProxy()->setItemLabelFormat(QStringLiteral("@valueLabel")); + m_graph->seriesList().at(0)->setItemLabelFormat(QStringLiteral("@valueLabel")); #else //! [6] diff --git a/src/datavisualization/data/abstractitemmodelhandler.cpp b/src/datavisualization/data/abstractitemmodelhandler.cpp index cf87c3f9..879ce086 100644 --- a/src/datavisualization/data/abstractitemmodelhandler.cpp +++ b/src/datavisualization/data/abstractitemmodelhandler.cpp @@ -23,7 +23,8 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION AbstractItemModelHandler::AbstractItemModelHandler(QObject *parent) : QObject(parent), - resolvePending(0) + resolvePending(0), + m_fullReset(true) { m_resolveTimer.setSingleShot(true); QObject::connect(&m_resolveTimer, &QTimer::timeout, @@ -81,9 +82,12 @@ void AbstractItemModelHandler::handleColumnsInserted(const QModelIndex &parent, Q_UNUSED(start) Q_UNUSED(end) - // Resolve new items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Manipulating columns changes all rows in proxies that map rows/columns directly, + // and its effects are not clearly defined in others -> always do full reset. + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleColumnsMoved(const QModelIndex &sourceParent, @@ -98,9 +102,12 @@ void AbstractItemModelHandler::handleColumnsMoved(const QModelIndex &sourceParen Q_UNUSED(destinationParent) Q_UNUSED(destinationColumn) - // Resolve moved items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Manipulating columns changes all rows in proxies that map rows/columns directly, + // and its effects are not clearly defined in others -> always do full reset. + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleColumnsRemoved(const QModelIndex &parent, @@ -110,9 +117,12 @@ void AbstractItemModelHandler::handleColumnsRemoved(const QModelIndex &parent, Q_UNUSED(start) Q_UNUSED(end) - // Remove old items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Manipulating columns changes all rows in proxies that map rows/columns directly, + // and its effects are not clearly defined in others -> always do full reset. + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleDataChanged(const QModelIndex &topLeft, @@ -123,9 +133,13 @@ void AbstractItemModelHandler::handleDataChanged(const QModelIndex &topLeft, Q_UNUSED(bottomRight) Q_UNUSED(roles) - // Resolve changed items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Default handling for dataChanged is to do full reset, as it cannot be optimized + // in a general case, where we do not know which row/column/index the item model item + // actually ended up to in the proxy. + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleLayoutChanged(const QList<QPersistentModelIndex> &parents, @@ -134,16 +148,20 @@ void AbstractItemModelHandler::handleLayoutChanged(const QList<QPersistentModelI Q_UNUSED(parents) Q_UNUSED(hint) - // Resolve moved items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Resolve entire model if layout changes + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleModelReset() { // Data cleared, reset array - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleRowsInserted(const QModelIndex &parent, int start, int end) @@ -152,9 +170,13 @@ void AbstractItemModelHandler::handleRowsInserted(const QModelIndex &parent, int Q_UNUSED(start) Q_UNUSED(end) - // Resolve new items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Default handling for rowsInserted is to do full reset, as it cannot be optimized + // in a general case, where we do not know which row/column/index the item model item + // actually ended up to in the proxy. + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleRowsMoved(const QModelIndex &sourceParent, @@ -169,9 +191,13 @@ void AbstractItemModelHandler::handleRowsMoved(const QModelIndex &sourceParent, Q_UNUSED(destinationParent) Q_UNUSED(destinationRow) - // Resolve moved items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Default handling for rowsMoved is to do full reset, as it cannot be optimized + // in a general case, where we do not know which row/column/index the item model item + // actually ended up to in the proxy. + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleRowsRemoved(const QModelIndex &parent, int start, int end) @@ -180,9 +206,13 @@ void AbstractItemModelHandler::handleRowsRemoved(const QModelIndex &parent, int Q_UNUSED(start) Q_UNUSED(end) - // Resolve removed items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Default handling for rowsRemoved is to do full reset, as it cannot be optimized + // in a general case, where we do not know which row/column/index the item model item + // actually ended up to in the proxy. + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleMappingChanged() @@ -194,6 +224,7 @@ void AbstractItemModelHandler::handleMappingChanged() void AbstractItemModelHandler::handlePendingResolve() { resolveModel(); + m_fullReset = false; } QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/data/abstractitemmodelhandler_p.h b/src/datavisualization/data/abstractitemmodelhandler_p.h index f3b72228..daaf9906 100644 --- a/src/datavisualization/data/abstractitemmodelhandler_p.h +++ b/src/datavisualization/data/abstractitemmodelhandler_p.h @@ -74,6 +74,7 @@ protected: QPointer<const QAbstractItemModel> m_itemModel; // Not owned bool resolvePending; QTimer m_resolveTimer; + bool m_fullReset; private: Q_DISABLE_COPY(AbstractItemModelHandler) diff --git a/src/datavisualization/data/qitemmodelscatterdataproxy.cpp b/src/datavisualization/data/qitemmodelscatterdataproxy.cpp index 74979309..16a7c8f7 100644 --- a/src/datavisualization/data/qitemmodelscatterdataproxy.cpp +++ b/src/datavisualization/data/qitemmodelscatterdataproxy.cpp @@ -32,7 +32,9 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * for Q3DScatter. It maps roles of QAbstractItemModel to the XYZ-values of Q3DScatter points. * * The data is resolved asynchronously whenever the mapping or the model changes. - * QScatterDataProxy::arrayReset() is emitted when the data has been resolved. + * QScatterDataProxy::arrayReset() is emitted when the data has been resolved. However, inserts, + * removes, and single data item changes after the model initialization are resolved synchronously, + * unless the same frame also contains a change that causes the whole model to be resolved. * * Mapping ignores rows and columns of the QAbstractItemModel and treats * all items equally. It requires the model to provide at least three roles for the data items diff --git a/src/datavisualization/data/scatteritemmodelhandler.cpp b/src/datavisualization/data/scatteritemmodelhandler.cpp index 2faa02a9..81ecf8b0 100644 --- a/src/datavisualization/data/scatteritemmodelhandler.cpp +++ b/src/datavisualization/data/scatteritemmodelhandler.cpp @@ -21,6 +21,8 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION +static const int noRoleIndex = -1; + ScatterItemModelHandler::ScatterItemModelHandler(QItemModelScatterDataProxy *proxy, QObject *parent) : AbstractItemModelHandler(parent), m_proxy(proxy), @@ -32,6 +34,79 @@ ScatterItemModelHandler::~ScatterItemModelHandler() { } +void ScatterItemModelHandler::handleDataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight, + const QVector<int> &roles) +{ + // Do nothing if full reset already pending + if (!m_fullReset) { + if (m_itemModel->columnCount() > 1) { + // If the data model is multi-column, do full asynchronous reset to simplify things + AbstractItemModelHandler::handleDataChanged(topLeft, bottomRight, roles); + } else { + int start = qMin(topLeft.row(), bottomRight.row()); + int end = qMax(topLeft.row(), bottomRight.row()); + + QScatterDataArray array(end - start + 1); + int count = 0; + for (int i = start; i <= end; i++) + modelPosToScatterItem(i, 0, array[count++]); + + m_proxy->setItems(start, array); + } + } +} + +void ScatterItemModelHandler::handleRowsInserted(const QModelIndex &parent, int start, int end) +{ + // Do nothing if full reset already pending + if (!m_fullReset) { + if (!m_proxy->itemCount() || m_itemModel->columnCount() > 1) { + // If inserting into an empty array, do full asynchronous reset to avoid multiple + // separate inserts when initializing the model. + // If the data model is multi-column, do full asynchronous reset to simplify things + AbstractItemModelHandler::handleRowsInserted(parent, start, end); + } else { + QScatterDataArray array(end - start + 1); + int count = 0; + for (int i = start; i <= end; i++) + modelPosToScatterItem(i, 0, array[count++]); + + m_proxy->insertItems(start, array); + } + } +} + +void ScatterItemModelHandler::handleRowsRemoved(const QModelIndex &parent, int start, int end) +{ + Q_UNUSED(parent) + + // Do nothing if full reset already pending + if (!m_fullReset) { + if (m_itemModel->columnCount() > 1) { + // If the data model is multi-column, do full asynchronous reset to simplify things + AbstractItemModelHandler::handleRowsRemoved(parent, start, end); + } else { + m_proxy->removeItems(start, end - start + 1); + } + } +} + +void ScatterItemModelHandler::modelPosToScatterItem(int modelRow, int modelColumn, QScatterDataItem &item) +{ + QModelIndex index = m_itemModel->index(modelRow, modelColumn); + float xPos(0.0f); + float yPos(0.0f); + float zPos(0.0f); + if (m_xPosRole != noRoleIndex) + xPos = index.data(m_xPosRole).toFloat(); + if (m_yPosRole != noRoleIndex) + yPos = index.data(m_yPosRole).toFloat(); + if (m_zPosRole != noRoleIndex) + zPos = index.data(m_zPosRole).toFloat(); + item.setPosition(QVector3D(xPos, yPos, zPos)); +} + // Resolve entire item model into QScatterDataArray. void ScatterItemModelHandler::resolveModel() { @@ -41,12 +116,10 @@ void ScatterItemModelHandler::resolveModel() return; } - static const int noRoleIndex = -1; - QHash<int, QByteArray> roleHash = m_itemModel->roleNames(); - const int xPosRole = roleHash.key(m_proxy->xPosRole().toLatin1(), noRoleIndex); - const int yPosRole = roleHash.key(m_proxy->yPosRole().toLatin1(), noRoleIndex); - const int zPosRole = roleHash.key(m_proxy->zPosRole().toLatin1(), noRoleIndex); + m_xPosRole = roleHash.key(m_proxy->xPosRole().toLatin1(), noRoleIndex); + m_yPosRole = roleHash.key(m_proxy->yPosRole().toLatin1(), noRoleIndex); + m_zPosRole = roleHash.key(m_proxy->zPosRole().toLatin1(), noRoleIndex); const int columnCount = m_itemModel->columnCount(); const int rowCount = m_itemModel->rowCount(); const int totalCount = rowCount * columnCount; @@ -59,17 +132,7 @@ void ScatterItemModelHandler::resolveModel() // Parse data into newProxyArray for (int i = 0; i < rowCount; i++) { for (int j = 0; j < columnCount; j++) { - QModelIndex index = m_itemModel->index(i, j); - float xPos(0.0f); - float yPos(0.0f); - float zPos(0.0f); - if (xPosRole != noRoleIndex) - xPos = index.data(xPosRole).toFloat(); - if (yPosRole != noRoleIndex) - yPos = index.data(yPosRole).toFloat(); - if (zPosRole != noRoleIndex) - zPos = index.data(zPosRole).toFloat(); - (*m_proxyArray)[runningCount].setPosition(QVector3D(xPos, yPos, zPos)); + modelPosToScatterItem(i, j, (*m_proxyArray)[runningCount]); runningCount++; } } diff --git a/src/datavisualization/data/scatteritemmodelhandler_p.h b/src/datavisualization/data/scatteritemmodelhandler_p.h index f54ed2c5..06927509 100644 --- a/src/datavisualization/data/scatteritemmodelhandler_p.h +++ b/src/datavisualization/data/scatteritemmodelhandler_p.h @@ -41,11 +41,23 @@ public: ScatterItemModelHandler(QItemModelScatterDataProxy *proxy, QObject *parent = 0); virtual ~ScatterItemModelHandler(); +public slots: + virtual void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector<int> &roles = QVector<int> ()); + virtual void handleRowsInserted(const QModelIndex &parent, int start, int end); + virtual void handleRowsRemoved(const QModelIndex &parent, int start, int end); + protected: void virtual resolveModel(); +private: + void modelPosToScatterItem(int modelRow, int modelColumn, QScatterDataItem &item); + QItemModelScatterDataProxy *m_proxy; // Not owned QScatterDataArray *m_proxyArray; // Not owned + int m_xPosRole; + int m_yPosRole; + int m_zPosRole; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/abstract3dcontroller.cpp b/src/datavisualization/engine/abstract3dcontroller.cpp index 9942a2fe..1efbea8d 100644 --- a/src/datavisualization/engine/abstract3dcontroller.cpp +++ b/src/datavisualization/engine/abstract3dcontroller.cpp @@ -30,6 +30,7 @@ #include "qabstract3dseries_p.h" #include "thememanager_p.h" #include "q3dscene_p.h" +#include "q3dscene.h" #include <QThread> @@ -147,9 +148,7 @@ QList<QAbstract3DSeries *> Abstract3DController::seriesList() */ void Abstract3DController::synchDataToRenderer() { - // If we don't have a renderer, don't do anything - if (!m_renderer) - return; + // Subclass implementations check for renderer validity already, so no need to check here. // If there is a pending click from renderer, handle that first. if (m_renderer->isClickPending()) { @@ -157,7 +156,7 @@ void Abstract3DController::synchDataToRenderer() m_renderer->clearClickPending(); } - // TODO: start recording inserts/removals + startRecordingRemovesAndInserts(); if (m_scene->d_ptr->m_sceneDirty) m_renderer->updateScene(m_scene); @@ -1016,6 +1015,11 @@ QCategory3DAxis *Abstract3DController::createDefaultCategoryAxis() return defaultAxis; } +void Abstract3DController::startRecordingRemovesAndInserts() +{ + // Default implementation does nothing +} + void Abstract3DController::emitNeedRender() { if (!m_renderPending) { diff --git a/src/datavisualization/engine/abstract3dcontroller_p.h b/src/datavisualization/engine/abstract3dcontroller_p.h index 17c1e1fb..0b89251d 100644 --- a/src/datavisualization/engine/abstract3dcontroller_p.h +++ b/src/datavisualization/engine/abstract3dcontroller_p.h @@ -138,9 +138,9 @@ private: ThemeManager *m_themeManager; QAbstract3DGraph::SelectionFlags m_selectionMode; QAbstract3DGraph::ShadowQuality m_shadowQuality; - Q3DScene *m_scene; protected: + Q3DScene *m_scene; QList<QAbstract3DInputHandler *> m_inputHandlers; // List of all added input handlers QAbstract3DInputHandler *m_activeInputHandler; CameraHelper *m_cameraHelper; @@ -273,6 +273,7 @@ protected: virtual QAbstract3DAxis *createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation); QValue3DAxis *createDefaultValueAxis(); QCategory3DAxis *createDefaultCategoryAxis(); + virtual void startRecordingRemovesAndInserts(); private: void setAxisHelper(QAbstract3DAxis::AxisOrientation orientation, QAbstract3DAxis *axis, diff --git a/src/datavisualization/engine/bars3dcontroller.cpp b/src/datavisualization/engine/bars3dcontroller.cpp index d85e474c..b0627377 100644 --- a/src/datavisualization/engine/bars3dcontroller.cpp +++ b/src/datavisualization/engine/bars3dcontroller.cpp @@ -70,6 +70,9 @@ void Bars3DController::initializeOpenGL() void Bars3DController::synchDataToRenderer() { + if (!isInitialized()) + return; + // Background change requires reloading the meshes in bar graphs, so dirty the series visuals if (m_themeManager->activeTheme()->d_ptr->m_dirtyBits.backgroundEnabledDirty) { m_isSeriesVisualsDirty = true; @@ -79,9 +82,6 @@ void Bars3DController::synchDataToRenderer() Abstract3DController::synchDataToRenderer(); - if (!isInitialized()) - return; - // Notify changes to renderer if (m_changeTracker.barSpecsChanged) { m_renderer->updateBarSpecs(m_barThicknessRatio, m_barSpacing, m_isBarSpecRelative); @@ -135,12 +135,8 @@ void Bars3DController::handleRowsRemoved(int startIndex, int count) { Q_UNUSED(startIndex) Q_UNUSED(count) - QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series(); - if (series->isVisible()) { - adjustAxisRanges(); - m_isDataDirty = true; - } + QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series(); if (series == m_selectedBarSeries) { // If rows removed from selected series before the selection, adjust the selection int selectedRow = m_selectedBar.x(); @@ -154,6 +150,11 @@ void Bars3DController::handleRowsRemoved(int startIndex, int count) } } + if (series->isVisible()) { + adjustAxisRanges(); + m_isDataDirty = true; + } + emitNeedRender(); } @@ -162,11 +163,6 @@ void Bars3DController::handleRowsInserted(int startIndex, int count) Q_UNUSED(startIndex) Q_UNUSED(count) QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series(); - if (series->isVisible()) { - adjustAxisRanges(); - m_isDataDirty = true; - } - if (series == m_selectedBarSeries) { // If rows inserted to selected series before the selection, adjust the selection int selectedRow = m_selectedBar.x(); @@ -176,6 +172,11 @@ void Bars3DController::handleRowsInserted(int startIndex, int count) } } + if (series->isVisible()) { + adjustAxisRanges(); + m_isDataDirty = true; + } + emitNeedRender(); } @@ -244,8 +245,6 @@ void Bars3DController::handlePendingClick() QPoint position = m_renderer->clickedPosition(); QBar3DSeries *series = static_cast<QBar3DSeries *>(m_renderer->clickedSeries()); - // TODO: Adjust position according to inserts/removes in the series - setSelectedBar(position, series); m_renderer->resetClickedStatus(); diff --git a/src/datavisualization/engine/scatter3dcontroller.cpp b/src/datavisualization/engine/scatter3dcontroller.cpp index 4527ac2a..6aa10a11 100644 --- a/src/datavisualization/engine/scatter3dcontroller.cpp +++ b/src/datavisualization/engine/scatter3dcontroller.cpp @@ -29,11 +29,14 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION +static const int insertRemoveRecordReserveSize = 31; + Scatter3DController::Scatter3DController(QRect boundRect, Q3DScene *scene) : Abstract3DController(boundRect, scene), m_renderer(0), m_selectedItem(invalidSelectionIndex()), - m_selectedItemSeries(0) + m_selectedItemSeries(0), + m_recordInsertsAndRemoves(false) { // Setting a null axis creates a new default axis according to orientation and graph type. // Note: These cannot be set in Abstract3DController constructor, as they will call virtual @@ -62,11 +65,11 @@ void Scatter3DController::initializeOpenGL() void Scatter3DController::synchDataToRenderer() { - Abstract3DController::synchDataToRenderer(); - if (!isInitialized()) return; + Abstract3DController::synchDataToRenderer(); + // Notify changes to renderer if (m_changeTracker.selectedItemChanged) { m_renderer->updateSelectedItem(m_selectedItem, m_selectedItemSeries); @@ -157,11 +160,6 @@ void Scatter3DController::handleItemsRemoved(int startIndex, int count) Q_UNUSED(count) // TODO should dirty only affected values? QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series(); - if (series->isVisible()) { - adjustValueAxisRange(); - m_isDataDirty = true; - } - if (series == m_selectedItemSeries) { // If items removed from selected series before the selection, adjust the selection int selectedItem = m_selectedItem; @@ -175,6 +173,16 @@ void Scatter3DController::handleItemsRemoved(int startIndex, int count) } } + if (series->isVisible()) { + adjustValueAxisRange(); + m_isDataDirty = true; + } + + if (m_recordInsertsAndRemoves) { + InsertRemoveRecord record(false, startIndex, count, series); + m_insertRemoveRecords.append(record); + } + emitNeedRender(); } @@ -184,11 +192,6 @@ void Scatter3DController::handleItemsInserted(int startIndex, int count) Q_UNUSED(count) // TODO should dirty only affected values? QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series(); - if (series->isVisible()) { - adjustValueAxisRange(); - m_isDataDirty = true; - } - if (series == m_selectedItemSeries) { // If items inserted to selected series before the selection, adjust the selection int selectedItem = m_selectedItem; @@ -198,9 +201,33 @@ void Scatter3DController::handleItemsInserted(int startIndex, int count) } } + if (series->isVisible()) { + adjustValueAxisRange(); + m_isDataDirty = true; + } + + if (m_recordInsertsAndRemoves) { + InsertRemoveRecord record(true, startIndex, count, series); + m_insertRemoveRecords.append(record); + } + emitNeedRender(); } +void Scatter3DController::startRecordingRemovesAndInserts() +{ + m_recordInsertsAndRemoves = false; + + if (m_scene->selectionQueryPosition() != Q3DScene::invalidSelectionPoint()) { + m_recordInsertsAndRemoves = true; + if (m_insertRemoveRecords.size()) { + m_insertRemoveRecords.clear(); + // Reserve some space for remove/insert records to avoid unnecessary reallocations. + m_insertRemoveRecords.reserve(insertRemoveRecordReserveSize); + } + } +} + void Scatter3DController::handleAxisAutoAdjustRangeChangedInOrientation( QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust) { @@ -229,7 +256,25 @@ void Scatter3DController::handlePendingClick() int index = m_renderer->clickedIndex(); QScatter3DSeries *series = static_cast<QScatter3DSeries *>(m_renderer->clickedSeries()); - // TODO: Adjust position according to inserts/removes in the series + // Adjust position according to recorded events + int recordCount = m_insertRemoveRecords.size(); + if (recordCount) { + for (int i = 0; i < recordCount; i++) { + const InsertRemoveRecord &record = m_insertRemoveRecords.at(i); + if (series == record.m_series && record.m_startIndex <= index) { + if (record.m_isInsert) { + index += record.m_count; + } else { + if ((record.m_startIndex + record.m_count) > index) { + index = -1; // Selected row removed + break; + } else { + index -= record.m_count; // Move selected item down by amount of items removed + } + } + } + } + } setSelectedItem(index, series); diff --git a/src/datavisualization/engine/scatter3dcontroller_p.h b/src/datavisualization/engine/scatter3dcontroller_p.h index 35f4015d..2e9ade44 100644 --- a/src/datavisualization/engine/scatter3dcontroller_p.h +++ b/src/datavisualization/engine/scatter3dcontroller_p.h @@ -62,6 +62,30 @@ private: QScatter3DSeries *m_selectedItemSeries; // Points to the series for which the bar is selected // in single series selection cases. + struct InsertRemoveRecord { + bool m_isInsert; + int m_startIndex; + int m_count; + QAbstract3DSeries *m_series; + + InsertRemoveRecord() : + m_isInsert(false), + m_startIndex(0), + m_count(0), + m_series(0) + {} + + InsertRemoveRecord(bool isInsert, int startIndex, int count, QAbstract3DSeries *series) : + m_isInsert(isInsert), + m_startIndex(startIndex), + m_count(count), + m_series(series) + {} + }; + + QVector<InsertRemoveRecord> m_insertRemoveRecords; + bool m_recordInsertsAndRemoves; + public: explicit Scatter3DController(QRect rect, Q3DScene *scene = 0); ~Scatter3DController(); @@ -93,6 +117,9 @@ public slots: void handleItemsRemoved(int startIndex, int count); void handleItemsInserted(int startIndex, int count); +protected: + virtual void startRecordingRemovesAndInserts(); + private: void adjustValueAxisRange(); diff --git a/src/datavisualization/engine/surface3dcontroller.cpp b/src/datavisualization/engine/surface3dcontroller.cpp index 984f65ba..f0dac44b 100644 --- a/src/datavisualization/engine/surface3dcontroller.cpp +++ b/src/datavisualization/engine/surface3dcontroller.cpp @@ -59,7 +59,6 @@ void Surface3DController::initializeOpenGL() m_renderer = new Surface3DRenderer(this); setRenderer(m_renderer); - synchDataToRenderer(); emitNeedRender(); } @@ -388,11 +387,6 @@ void Surface3DController::handleRowsInserted(int startIndex, int count) Q_UNUSED(startIndex) Q_UNUSED(count) QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series(); - if (series->isVisible()) { - adjustValueAxisRange(); - m_isDataDirty = true; - } - if (series == m_selectedSeries) { // If rows inserted to selected series before the selection, adjust the selection int selectedRow = m_selectedPoint.x(); @@ -402,6 +396,11 @@ void Surface3DController::handleRowsInserted(int startIndex, int count) } } + if (series->isVisible()) { + adjustValueAxisRange(); + m_isDataDirty = true; + } + emitNeedRender(); } @@ -410,11 +409,6 @@ void Surface3DController::handleRowsRemoved(int startIndex, int count) Q_UNUSED(startIndex) Q_UNUSED(count) QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series(); - if (series->isVisible()) { - adjustValueAxisRange(); - m_isDataDirty = true; - } - if (series == m_selectedSeries) { // If rows removed from selected series before the selection, adjust the selection int selectedRow = m_selectedPoint.x(); @@ -428,6 +422,11 @@ void Surface3DController::handleRowsRemoved(int startIndex, int count) } } + if (series->isVisible()) { + adjustValueAxisRange(); + m_isDataDirty = true; + } + emitNeedRender(); } diff --git a/tests/barstest/barstest.pro b/tests/barstest/barstest.pro index 55fbefbd..108f8aa7 100644 --- a/tests/barstest/barstest.pro +++ b/tests/barstest/barstest.pro @@ -2,8 +2,8 @@ error( "Couldn't find the tests.pri file!" ) } -SOURCES += main.cpp chart.cpp -HEADERS += chart.h +SOURCES += main.cpp chart.cpp custominputhandler.cpp +HEADERS += chart.h custominputhandler.h QT += widgets diff --git a/tests/barstest/chart.cpp b/tests/barstest/chart.cpp index 7f049645..abbf2a59 100644 --- a/tests/barstest/chart.cpp +++ b/tests/barstest/chart.cpp @@ -17,12 +17,14 @@ ****************************************************************************/ #include "chart.h" +#include "custominputhandler.h" #include <QtDataVisualization/qcategory3daxis.h> #include <QtDataVisualization/qvalue3daxis.h> #include <QtDataVisualization/qbardataproxy.h> #include <QtDataVisualization/q3dscene.h> #include <QtDataVisualization/q3dcamera.h> #include <QtDataVisualization/q3dtheme.h> +#include <QtDataVisualization/q3dinputhandler.h> #include <QTime> using namespace QtDataVisualization; @@ -65,7 +67,8 @@ GraphModifier::GraphModifier(Q3DBars *barchart, QColorDialog *colorDialog) m_useNullInputHandler(false), m_defaultInputHandler(0), m_ownTheme(0), - m_builtinTheme(new Q3DTheme(Q3DTheme::ThemeStoneMoss)) + m_builtinTheme(new Q3DTheme(Q3DTheme::ThemeStoneMoss)), + m_customInputHandler(new CustomInputHandler) { m_temperatureData->setObjectName("m_temperatureData"); m_temperatureData2->setObjectName("m_temperatureData2"); @@ -218,9 +221,15 @@ GraphModifier::GraphModifier(Q3DBars *barchart, QColorDialog *colorDialog) QObject::connect(m_graph, &Q3DBars::primarySeriesChanged, this, &GraphModifier::handlePrimarySeriesChanged); + QObject::connect(&m_insertRemoveTimer, &QTimer::timeout, this, + &GraphModifier::insertRemoveTimerTimeout); + m_graph->addSeries(m_temperatureData); m_graph->addSeries(m_temperatureData2); + QObject::connect(&m_selectionTimer, &QTimer::timeout, this, + &GraphModifier::triggerSelection); + resetTemperatureData(); } @@ -994,6 +1003,69 @@ void GraphModifier::primarySeriesTest() } +void GraphModifier::insertRemoveTestToggle() +{ + if (m_insertRemoveTimer.isActive()) { + m_insertRemoveTimer.stop(); + m_selectionTimer.stop(); + m_graph->removeSeries(m_dummyData); + m_graph->removeSeries(m_dummyData2); + releaseProxies(); + releaseAxes(); + m_graph->setActiveInputHandler(m_defaultInputHandler); + } else { + releaseProxies(); + releaseAxes(); + m_graph->rowAxis()->setRange(0, 32); + m_graph->columnAxis()->setRange(0, 10); + m_graph->setActiveInputHandler(m_customInputHandler); + m_graph->addSeries(m_dummyData); + m_graph->addSeries(m_dummyData2); + m_insertRemoveStep = 0; + m_insertRemoveTimer.start(100); + m_selectionTimer.start(10); + } +} + +void GraphModifier::insertRemoveTimerTimeout() +{ + if (m_insertRemoveStep < 32) { + for (int k = 0; k < 1; k++) { + QBarDataRow *dataRow = new QBarDataRow(10); + for (float i = 0; i < 10; i++) + (*dataRow)[i].setValue(((i + 1) / 10.0f) * (float)(rand() % 100)); + + QString label = QStringLiteral("Insert %1").arg(insertCounter++); + m_dummyData->dataProxy()->insertRow(0, dataRow, label); + } + } else { + for (int k = 0; k < 1; k++) + m_dummyData->dataProxy()->removeRows(0, 1); + } + + if (m_insertRemoveStep < 16 || (m_insertRemoveStep > 31 && m_insertRemoveStep < 48)) { + for (int k = 0; k < 2; k++) { + QBarDataRow *dataRow = new QBarDataRow(10); + for (float i = 0; i < 10; i++) + (*dataRow)[i].setValue(((i + 1) / 10.0f) * (float)(rand() % 100)); + + QString label = QStringLiteral("Insert %1").arg(insertCounter++); + m_dummyData2->dataProxy()->insertRow(0, dataRow, label); + } + } else { + for (int k = 0; k < 2; k++) + m_dummyData2->dataProxy()->removeRows(0, 1); + } + + if (m_insertRemoveStep++ > 63) + m_insertRemoveStep = 0; +} + +void GraphModifier::triggerSelection() +{ + m_graph->scene()->setSelectionQueryPosition(m_customInputHandler->inputPosition()); +} + void GraphModifier::setBackgroundEnabled(int enabled) { m_graph->activeTheme()->setBackgroundEnabled(bool(enabled)); diff --git a/tests/barstest/chart.h b/tests/barstest/chart.h index 703f53e5..425af521 100644 --- a/tests/barstest/chart.h +++ b/tests/barstest/chart.h @@ -22,11 +22,13 @@ #include <QtDataVisualization/q3dbars.h> #include <QtDataVisualization/qabstract3dinputhandler.h> #include <QtDataVisualization/qbar3dseries.h> +#include <QtDataVisualization/q3dtheme.h> #include <QFont> #include <QDebug> #include <QStringList> #include <QPointer> #include <QColorDialog> +#include <QTimer> using namespace QtDataVisualization; @@ -81,6 +83,7 @@ public: void showFiveSeries(); QBarDataArray *makeDummyData(); void primarySeriesTest(); + void insertRemoveTestToggle(); public slots: void flipViews(); @@ -95,6 +98,9 @@ public slots: void handleValueAxisChanged(QValue3DAxis *axis); void handlePrimarySeriesChanged(QBar3DSeries *series); + void insertRemoveTimerTimeout(); + void triggerSelection(); + signals: void shadowQualityChanged(int quality); @@ -137,6 +143,10 @@ private: QAbstract3DInputHandler *m_defaultInputHandler; Q3DTheme *m_ownTheme; Q3DTheme *m_builtinTheme; + QTimer m_insertRemoveTimer; + int m_insertRemoveStep; + QAbstract3DInputHandler *m_customInputHandler; + QTimer m_selectionTimer; }; #endif diff --git a/tests/barstest/custominputhandler.cpp b/tests/barstest/custominputhandler.cpp new file mode 100644 index 00000000..3b050fda --- /dev/null +++ b/tests/barstest/custominputhandler.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the QtDataVisualization module. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise 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.digia.com +** +****************************************************************************/ + +#include "custominputhandler.h" + +#include <QtDataVisualization/Q3DCamera> + +CustomInputHandler::CustomInputHandler(QObject *parent) : + QAbstract3DInputHandler(parent) +{ +} + +//! [0] +void CustomInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos) +{ + Q_UNUSED(event) + setInputPosition(mousePos); +} +//! [0] + +//! [1] +void CustomInputHandler::wheelEvent(QWheelEvent *event) +{ + // Adjust zoom level based on what zoom range we're in. + int zoomLevel = scene()->activeCamera()->zoomLevel(); + if (zoomLevel > 100) + zoomLevel += event->angleDelta().y() / 12; + else if (zoomLevel > 50) + zoomLevel += event->angleDelta().y() / 60; + else + zoomLevel += event->angleDelta().y() / 120; + if (zoomLevel > 500) + zoomLevel = 500; + else if (zoomLevel < 10) + zoomLevel = 10; + + scene()->activeCamera()->setZoomLevel(zoomLevel); +} +//! [1] diff --git a/tests/barstest/custominputhandler.h b/tests/barstest/custominputhandler.h new file mode 100644 index 00000000..1fecb8d8 --- /dev/null +++ b/tests/barstest/custominputhandler.h @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the QtDataVisualization module. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise 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.digia.com +** +****************************************************************************/ + +#ifndef CUSTOMINPUTHANDLER_H +#define CUSTOMINPUTHANDLER_H + +#include <QtDataVisualization/QAbstract3DInputHandler> + +using namespace QtDataVisualization; + +class CustomInputHandler : public QAbstract3DInputHandler +{ + Q_OBJECT +public: + explicit CustomInputHandler(QObject *parent = 0); + + virtual void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos); + virtual void wheelEvent(QWheelEvent *event); +}; + +#endif diff --git a/tests/barstest/main.cpp b/tests/barstest/main.cpp index a0f9d783..a0e1bfc3 100644 --- a/tests/barstest/main.cpp +++ b/tests/barstest/main.cpp @@ -126,6 +126,10 @@ int main(int argc, char **argv) swapAxisButton->setText(QStringLiteral("Swap value axis")); swapAxisButton->setEnabled(false); + QPushButton *insertRemoveTestButton = new QPushButton(widget); + insertRemoveTestButton->setText(QStringLiteral("Toggle insert/remove cycle")); + insertRemoveTestButton->setEnabled(true); + QPushButton *releaseAxesButton = new QPushButton(widget); releaseAxesButton->setText(QStringLiteral("Release all axes")); releaseAxesButton->setEnabled(true); @@ -293,6 +297,7 @@ int main(int argc, char **argv) vLayout->addWidget(selectionButton, 0, Qt::AlignTop); vLayout->addWidget(setSelectedBarButton, 0, Qt::AlignTop); vLayout->addWidget(swapAxisButton, 0, Qt::AlignTop); + vLayout->addWidget(insertRemoveTestButton, 0, Qt::AlignTop); vLayout->addWidget(releaseAxesButton, 0, Qt::AlignTop); vLayout->addWidget(releaseProxiesButton, 1, Qt::AlignTop); vLayout->addWidget(flipViewsButton, 0, Qt::AlignTop); @@ -385,6 +390,8 @@ int main(int argc, char **argv) &GraphModifier::selectBar); QObject::connect(swapAxisButton, &QPushButton::clicked, modifier, &GraphModifier::swapAxis); + QObject::connect(insertRemoveTestButton, &QPushButton::clicked, modifier, + &GraphModifier::insertRemoveTestToggle); QObject::connect(releaseAxesButton, &QPushButton::clicked, modifier, &GraphModifier::releaseAxes); QObject::connect(releaseProxiesButton, &QPushButton::clicked, modifier, diff --git a/tests/qmldynamicdata/qml/qmldynamicdata/main.qml b/tests/qmldynamicdata/qml/qmldynamicdata/main.qml index 4ed3b8ae..3ad454d0 100644 --- a/tests/qmldynamicdata/qml/qmldynamicdata/main.qml +++ b/tests/qmldynamicdata/qml/qmldynamicdata/main.qml @@ -34,7 +34,7 @@ Item { Timer { id: dataTimer - interval: 20 + interval: 1 running: true repeat: true property bool isIncreasing: true @@ -42,7 +42,16 @@ Item { onTriggered: { if (isIncreasing) { graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()}); - if (graphModel.count == 500) { + graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()}); + graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()}); + graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()}); + graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()}); + graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()}); + graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()}); + graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()}); + graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()}); + graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()}); + if (graphModel.count > 5000) { scatterGraph.theme.type = Theme3D.ThemeIsabelle; isIncreasing = false; } @@ -50,6 +59,15 @@ Item { // TODO: Once QTRD-2645 is fixed, change this to remove from // random index to add coverage. graphModel.remove(2); + graphModel.remove(2); + graphModel.remove(2); + graphModel.remove(2); + graphModel.remove(2); + graphModel.remove(2); + graphModel.remove(2); + graphModel.remove(2); + graphModel.remove(2); + graphModel.remove(2); if (graphModel.count == 2) { scatterGraph.theme.type = Theme3D.ThemeDigia; isIncreasing = true; |