From 298dd60ef723ebc3e9e774e7ac3fd442728f2091 Mon Sep 17 00:00:00 2001 From: vcloarec Date: Tue, 29 Jun 2021 16:18:02 -0400 Subject: [PATCH 1/9] start, commit, roll back and cancel edit for mesh layer --- .../mesh/qgsmeshdataprovider.sip.in | 30 ++ .../auto_generated/mesh/qgsmesheditor.sip.in | 10 + .../auto_generated/mesh/qgsmeshlayer.sip.in | 73 ++++- src/app/qgisapp.cpp | 278 ++++++++++++++++-- src/app/qgisapp.h | 37 +++ src/core/layertree/qgslayertreemodel.cpp | 17 +- src/core/layertree/qgslayertreeutils.cpp | 35 ++- src/core/mesh/qgsmeshdataprovider.cpp | 2 + src/core/mesh/qgsmeshdataprovider.h | 30 ++ src/core/mesh/qgsmeshdatasetgroupstore.cpp | 4 +- src/core/mesh/qgsmesheditor.cpp | 15 +- src/core/mesh/qgsmesheditor.h | 6 + src/core/mesh/qgsmeshlayer.cpp | 122 +++++++- src/core/mesh/qgsmeshlayer.h | 69 ++++- src/core/mesh/qgstriangularmesh.cpp | 24 +- src/core/mesh/qgstriangularmesh.h | 8 + .../meshmemory/qgsmeshmemorydataprovider.cpp | 8 + .../meshmemory/qgsmeshmemorydataprovider.h | 4 + src/core/qgsmaplayerlegend.cpp | 10 +- .../qgsmeshrendereractivedatasetwidget.cpp | 6 +- .../qgsmeshrendererscalarsettingswidget.cpp | 9 +- .../mesh/qgsrenderermeshpropertieswidget.cpp | 6 +- src/providers/mdal/qgsmdalprovider.cpp | 46 ++- src/providers/mdal/qgsmdalprovider.h | 7 + tests/src/core/testqgsmesheditor.cpp | 178 ++++++++--- tests/src/core/testqgsmeshlayer.cpp | 3 + tests/testdata/mesh/quad_flower_to_edit.2dm | 14 + .../mesh/quad_flower_to_edit_expected.2dm | 11 + 28 files changed, 948 insertions(+), 114 deletions(-) create mode 100644 tests/testdata/mesh/quad_flower_to_edit.2dm create mode 100644 tests/testdata/mesh/quad_flower_to_edit_expected.2dm diff --git a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in index af8f27de6b46..9e4b9a3f7b29 100644 --- a/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshdataprovider.sip.in @@ -147,6 +147,18 @@ Returns number of edges in the native mesh Populates the mesh vertices, edges and faces .. versionadded:: 3.6 +%End + + virtual bool saveMeshFrame( const QgsMesh &mesh ) = 0; +%Docstring +Saves the ``mesh`` frame to the source. + +:param mesh: the mesh to save + +:return: ``True`` on success + + +.. versionadded:: 3.22 %End }; @@ -412,6 +424,24 @@ Sets the temporal unit of the provider and reload data if it changes. .. versionadded:: 3.14 %End + + virtual QgsMeshDriverMetadata driverMetadata() const; +%Docstring +Returns the mesh driver metadata of the provider + +:return: the mesh driver metadata of the provider + +.. versionadded:: 3.22 +%End + + + virtual void close() = 0; +%Docstring +Closes the data provider and free every resources used + +.. versionadded:: 3.22 +%End + signals: void datasetGroupsAdded( int count ); %Docstring diff --git a/python/core/auto_generated/mesh/qgsmesheditor.sip.in b/python/core/auto_generated/mesh/qgsmesheditor.sip.in index 3b24c99ffc88..e9f09c94e605 100644 --- a/python/core/auto_generated/mesh/qgsmesheditor.sip.in +++ b/python/core/auto_generated/mesh/qgsmesheditor.sip.in @@ -98,6 +98,16 @@ If removing these vertices leads to a topological errors, the method will return void stopEditing(); %Docstring Stops editing +%End + + QgsRectangle extent() const; +%Docstring +Returns the extent of the edited mesh +%End + + bool isModified() const; +%Docstring +Returns whether the mesh has been modified %End signals: diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index 35ba1230f651..46bbdbb703b7 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -157,6 +157,8 @@ QgsMeshLayer cannot be copied. virtual QString htmlMetadata() const; + virtual bool isEditable() const; + QString providerType() const; %Docstring @@ -706,6 +708,15 @@ Returns the relative time of the dataset from the reference time of its group Returns the relative time (in milliseconds) of the dataset from the reference time of its group .. versionadded:: 3.16 +%End + + bool meshFrameSupportEditing() const; +%Docstring +Returns whether the mesh frame support editing + +:return: whether the mesh frame support editing + +.. versionadded:: 3.22 %End bool startFrameEditing( const QgsCoordinateTransform &transform ); @@ -713,6 +724,28 @@ Returns the relative time (in milliseconds) of the dataset from the reference ti Starts edition of the mesh frame. Coordinate ``transform`` used to initialize the triangular mesh if needed. This operation will disconnect the mesh layer from the data provider anf removes all existing dataset group +.. versionadded:: 3.22 +%End + + bool commitFrameEditing( const QgsCoordinateTransform &transform, bool continueEditing = true ); +%Docstring +Commit edition of the mesh frame, +Rebuilds the triangular mesh and its spatial index with ``transform``, +Continue editing with the same mesh editor if ``continueEditing`` is True + +:return: whether the commit succeeds + +.. versionadded:: 3.22 +%End + + bool rollBackFrameEditing( const QgsCoordinateTransform &transform, bool continueEditing = true ); +%Docstring +Roll Back edition of the mesh frame. +Reload mesh from file, rebuilds the triangular mesh and its spatial index with ``transform``, +Continue editing with the same mesh editor if ``continueEditing`` is True + +:return: whether the commit succeeds + .. versionadded:: 3.22 %End @@ -732,14 +765,29 @@ Returns a pointer to the mesh editor own by the mesh layer .. versionadded:: 3.22 %End - int meshVerticesCount() const; + bool isFrameModified() const; +%Docstring +Returns whether the mesh fram has been modified since the last save + +.. versionadded:: 3.22 +%End + + + bool contains( const QgsMesh::ElementType &type ) const; +%Docstring +Returns whether the mesh contains at mesh elements of given type + +.. versionadded:: 3.22 +%End + + int meshVertexCount() const; %Docstring Returns the vertices count of the mesh frame .. versionadded:: 3.22 %End - int meshFacesCount() const; + int meshFaceCount() const; %Docstring Returns the faces count of the mesh frame @@ -784,6 +832,27 @@ Emitted when active vector group dataset is changed Emitted when time format is changed .. versionadded:: 3.8 +%End + + void frameModified(); +%Docstring +Emitted when the mesh frame is modified + +.. versionadded:: 3.22 +%End + + void frameEditingStarted(); +%Docstring +Emitted when the mesh frame editing is started + +.. versionadded:: 3.22 +%End + + void frameEditingStopped(); +%Docstring +Emitted when the mesh frame editing is stopped + +.. versionadded:: 3.22 %End private: // Private methods diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 12eb7c37d878..2aef04fc734a 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -11140,8 +11140,10 @@ void QgisApp::toggleMapTips( bool enabled ) void QgisApp::toggleEditing() { - QgsVectorLayer *currentLayer = qobject_cast( activeLayer() ); - if ( currentLayer ) + QgsMapLayer *currentLayer = activeLayer(); + if ( currentLayer && + ( currentLayer->type() == QgsMapLayerType::VectorLayer || + currentLayer->type() == QgsMapLayerType::MeshLayer ) ) { toggleEditing( currentLayer, true ); } @@ -11154,7 +11156,22 @@ void QgisApp::toggleEditing() bool QgisApp::toggleEditing( QgsMapLayer *layer, bool allowCancel ) { - QgsVectorLayer *vlayer = qobject_cast( layer ); + switch ( layer->type() ) + { + case QgsMapLayerType::VectorLayer: + return toggleEditingVectorLayer( qobject_cast( layer ), allowCancel ); + break; + case QgsMapLayerType::MeshLayer: + return toggleEditingMeshLayer( qobject_cast( layer ) ); + break; + default: + return false; + break; + } +} + +bool QgisApp::toggleEditingVectorLayer( QgsVectorLayer *vlayer, bool allowCancel ) +{ if ( !vlayer ) { return false; @@ -11298,12 +11315,102 @@ bool QgisApp::toggleEditing( QgsMapLayer *layer, bool allowCancel ) vlayer->triggerRepaint(); } - if ( !res && layer == activeLayer() ) + if ( !res && vlayer == activeLayer() ) { // while also called when layer sends editingStarted/editingStopped signals, // this ensures correct restoring of gui state if toggling was canceled // or layer commit/rollback functions failed - activateDeactivateLayerRelatedActions( layer ); + activateDeactivateLayerRelatedActions( vlayer ); + } + + return res; +} + +bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel ) +{ + if ( !mlayer ) + return false; + + if ( !mlayer->meshFrameSupportEditing() ) + return false; //TODO: when adapted widget will be ready, ask the user if he want to create a new one based on this one + + bool res = false; + + QgsCoordinateTransform transform( mlayer->crs(), mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance() ); + + if ( !mlayer->isEditable() ) + { + res = mlayer->startFrameEditing( transform ); + mActionToggleEditing->setChecked( res ); + + if ( !res ) + QgsMessageLog::logMessage( tr( "Unable to start mesh editing for layer \"%1\"" ).arg( mlayer->name() ) ); + } + else if ( mlayer->isFrameModified() ) + { + QMessageBox::StandardButtons buttons = QMessageBox::Save | QMessageBox::Discard; + if ( allowCancel ) + buttons = buttons | QMessageBox::Cancel; + switch ( QMessageBox::question( nullptr, + tr( "Stop Editing" ), + tr( "Do you want to save the changes to layer %1?" ).arg( mlayer->name() ), + buttons ) ) + { + case QMessageBox::Cancel: + res = false; + break; + + case QMessageBox::Save: + { + QApplication::setOverrideCursor( Qt::WaitCursor ); + QgsCanvasRefreshBlocker refreshBlocker; + if ( !mlayer->commitFrameEditing( transform, false ) ) + { + res = false; + } + + mlayer->triggerRepaint(); + + QApplication::restoreOverrideCursor(); + } + break; + case QMessageBox::Discard: + { + QApplication::setOverrideCursor( Qt::WaitCursor ); + QgsCanvasRefreshBlocker refreshBlocker; + if ( !mlayer->rollBackFrameEditing( transform, false ) ) + { + visibleMessageBar()->pushMessage( tr( "Error" ), + tr( "Problems during roll back" ), + Qgis::MessageLevel::Critical ); + res = false; + } + + mlayer->triggerRepaint(); + + QApplication::restoreOverrideCursor(); + break; + } + + default: + break; + } + } + else //mesh layer not modified + { + QApplication::setOverrideCursor( Qt::WaitCursor ); + QgsCanvasRefreshBlocker refreshBlocker; + mlayer->rollBackFrameEditing( transform, false ); + mlayer->triggerRepaint(); + QApplication::restoreOverrideCursor(); + } + + if ( !res && mlayer == activeLayer() ) + { + // while also called when layer sends editingStarted/editingStopped signals, + // this ensures correct restoring of gui state if toggling was canceled + // or layer commit/rollback functions failed + activateDeactivateLayerRelatedActions( mlayer ); } return res; @@ -11315,6 +11422,24 @@ void QgisApp::saveActiveLayerEdits() } void QgisApp::saveEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint ) +{ + if ( !layer ) + return; + + switch ( layer->type() ) + { + case QgsMapLayerType::VectorLayer: + return saveVectorLayerEdits( layer, leaveEditable, triggerRepaint ); + break; + case QgsMapLayerType::MeshLayer: + return saveMeshLayerEdits( layer, leaveEditable, triggerRepaint ); + break; + default: + break; + } +} + +void QgisApp::saveVectorLayerEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint ) { QgsVectorLayer *vlayer = qobject_cast( layer ); if ( !vlayer || !vlayer->isEditable() || !vlayer->isModified() ) @@ -11335,7 +11460,44 @@ void QgisApp::saveEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRep } } +void QgisApp::saveMeshLayerEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint ) +{ + QgsMeshLayer *mlayer = qobject_cast( layer ); + if ( !mlayer || !mlayer->isEditable() || !mlayer->isFrameModified() ) + return; + + if ( mlayer == activeLayer() ) + mSaveRollbackInProgress = true; + + QgsCanvasRefreshBlocker refreshBlocker; + QgsCoordinateTransform transform( mlayer->crs(), mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance() ); + mlayer->commitFrameEditing( transform, leaveEditable ); + + if ( triggerRepaint ) + { + mlayer->triggerRepaint(); + } +} + void QgisApp::cancelEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint ) +{ + if ( !layer ) + return; + + switch ( layer->type() ) + { + case QgsMapLayerType::VectorLayer: + return cancelVectorLayerEdits( layer, leaveEditable, triggerRepaint ); + break; + case QgsMapLayerType::MeshLayer: + return cancelMeshLayerEdits( layer, leaveEditable, triggerRepaint ); + break; + default: + break; + } +} + +void QgisApp::cancelVectorLayerEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint ) { QgsVectorLayer *vlayer = qobject_cast( layer ); if ( !vlayer || !vlayer->isEditable() ) @@ -11366,6 +11528,33 @@ void QgisApp::cancelEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerR } } +void QgisApp::cancelMeshLayerEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint ) +{ + QgsMeshLayer *mlayer = qobject_cast( layer ); + if ( !mlayer || !mlayer->isEditable() ) + return; + + if ( mlayer == activeLayer() && leaveEditable ) + mSaveRollbackInProgress = true; + + QgsCanvasRefreshBlocker refreshBlocker; + QgsCoordinateTransform transform( mlayer->crs(), mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance() ); + if ( !mlayer->rollBackFrameEditing( transform, leaveEditable ) ) + { + mSaveRollbackInProgress = false; + QMessageBox::warning( nullptr, + tr( "Error" ), + tr( "Could not %1 changes to layer %2" ) + .arg( leaveEditable ? tr( "rollback" ) : tr( "cancel" ), + mlayer->name() ) ); + } + + if ( triggerRepaint ) + { + mlayer->triggerRepaint(); + } +} + void QgisApp::saveEdits() { const auto constSelectedLayers = mLayerTreeView->selectedLayers(); @@ -11472,17 +11661,35 @@ bool QgisApp::verifyEditsActionDialog( const QString &act, const QString &upon ) void QgisApp::updateLayerModifiedActions() { bool enableSaveLayerEdits = false; - QgsVectorLayer *vlayer = qobject_cast( activeLayer() ); - if ( vlayer ) + + QgsMapLayer *currentLayer = activeLayer(); + if ( currentLayer ) { - QgsVectorDataProvider *dprovider = vlayer->dataProvider(); - if ( dprovider ) + switch ( currentLayer->type() ) { - enableSaveLayerEdits = ( dprovider->capabilities() & QgsVectorDataProvider::ChangeAttributeValues - && vlayer->isEditable() - && vlayer->isModified() ); + case QgsMapLayerType::VectorLayer: + { + QgsVectorLayer *vlayer = qobject_cast( currentLayer ); + QgsVectorDataProvider *dprovider = vlayer->dataProvider(); + if ( dprovider ) + { + enableSaveLayerEdits = ( dprovider->capabilities() & QgsVectorDataProvider::ChangeAttributeValues + && vlayer->isEditable() + && vlayer->isModified() ); + } + } + break; + case QgsMapLayerType::MeshLayer: + { + QgsMeshLayer *mlayer = qobject_cast( currentLayer ); + enableSaveLayerEdits = ( mlayer->isEditable() && mlayer->isFrameModified() ); + } + break; + default: + break; } } + mActionSaveLayerEdits->setEnabled( enableSaveLayerEdits ); QList selectedLayerNodes = mLayerTreeView ? mLayerTreeView->selectedLayerNodes() : QList(); @@ -11507,15 +11714,27 @@ QList QgisApp::editableLayers( bool modified ) const const auto constFindLayers = mLayerTreeView->layerTreeModel()->rootGroup()->findLayers(); for ( QgsLayerTreeLayer *nodeLayer : constFindLayers ) { - if ( !nodeLayer->layer() ) + QgsMapLayer *layer = nodeLayer->layer(); + if ( !layer ) continue; - QgsVectorLayer *vl = qobject_cast( nodeLayer->layer() ); - if ( !vl ) - continue; + bool layerIsModified = false; + + switch ( layer->type() ) + { + case QgsMapLayerType::VectorLayer: + layerIsModified = qobject_cast( layer )->isModified(); + break; + case QgsMapLayerType::MeshLayer: + layerIsModified = qobject_cast( layer )->isFrameModified(); + break; + default: + continue; + break; + } - if ( vl->isEditable() && ( !modified || vl->isModified() ) ) - editLayers << vl; + if ( layer->isEditable() && ( !modified || layerIsModified ) ) + editLayers << layer; } return editLayers; } @@ -14437,6 +14656,15 @@ void QgisApp::layersWereAdded( const QList &layers ) provider = rlayer->dataProvider(); } + QgsMeshLayer *mlayer = qobject_cast( layer ); + if ( mlayer ) + { + connect( mlayer, &QgsMeshLayer::frameModified, this, &QgisApp::updateLayerModifiedActions ); + connect( mlayer, &QgsMeshLayer::frameEditingStarted, this, &QgisApp::layerEditStateChanged ); + connect( mlayer, &QgsMeshLayer::frameEditingStopped, this, &QgisApp::layerEditStateChanged ); + provider = mlayer->dataProvider(); + } + if ( provider ) { connect( provider, &QgsDataProvider::dataChanged, layer, [layer] { layer->triggerRepaint(); } ); @@ -15346,6 +15574,9 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer ) } case QgsMapLayerType::MeshLayer: + { + QgsMeshLayer *mlayer = qobject_cast( layer ); + mActionLocalHistogramStretch->setEnabled( false ); mActionFullHistogramStretch->setEnabled( false ); mActionLocalCumulativeCutStretch->setEnabled( false ); @@ -15375,8 +15606,6 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer ) mActionSelectByExpression->setEnabled( false ); mActionSelectByForm->setEnabled( false ); mActionOpenFieldCalc->setEnabled( false ); - mActionToggleEditing->setEnabled( false ); - mActionToggleEditing->setChecked( false ); mActionSaveLayerEdits->setEnabled( false ); mUndoDock->widget()->setEnabled( false ); mActionUndo->setEnabled( false ); @@ -15412,7 +15641,14 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer ) mActionDiagramProperties->setEnabled( false ); mActionIdentify->setEnabled( true ); enableDigitizeTechniqueActions( false ); - break; + + bool canSupportEditing = mlayer->meshFrameSupportEditing(); + bool isEditable = mlayer->isEditable(); + mActionToggleEditing->setEnabled( canSupportEditing ); + mActionToggleEditing->setChecked( canSupportEditing && isEditable ); + } + + break; case QgsMapLayerType::VectorTileLayer: mActionLocalHistogramStretch->setEnabled( false ); diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 3e686fa56e5d..6b4925beae67 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -2362,6 +2362,43 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow */ void pasteFeatures( QgsVectorLayer *pasteVectorLayer, int invalidGeometriesCount, int nTotalFeatures, QgsFeatureList &features ); + /** + * starts/stops for a vector layer \a vlayer + */ + bool toggleEditingVectorLayer( QgsVectorLayer *vlayer, bool allowCancel = true ); + + /** + * Starts/stops for a mesh layer \a mlayer + */ + bool toggleEditingMeshLayer( QgsMeshLayer *vlayer, bool allowCancel = true ); + + /** + * Saves edits of a vector layer + * \param leaveEditable leave the layer in editing mode when done + * \param triggerRepaint send layer signal to repaint canvas when done + */ + void saveVectorLayerEdits( QgsMapLayer *layer, bool leaveEditable = true, bool triggerRepaint = true ); + + /** + * Saves edits of a mesh layer + * \param leaveEditable leave the layer in editing mode when done + * \param triggerRepaint send layer signal to repaint canvas when done + */ + void saveMeshLayerEdits( QgsMapLayer *layer, bool leaveEditable = true, bool triggerRepaint = true ); + + /** + * Cancels edits of a vector layer + * \param leaveEditable leave the layer in editing mode when done + * \param triggerRepaint send layer signal to repaint canvas when done + */ + void cancelVectorLayerEdits( QgsMapLayer *layer, bool leaveEditable = true, bool triggerRepaint = true ); + + /** + * Cancels edits of a mesh layer + * \param leaveEditable leave the layer in editing mode when done + * \param triggerRepaint send layer signal to repaint canvas when done + */ + void cancelMeshLayerEdits( QgsMapLayer *layer, bool leaveEditable = true, bool triggerRepaint = true ); QgisAppStyleSheet *mStyleSheetBuilder = nullptr; diff --git a/src/core/layertree/qgslayertreemodel.cpp b/src/core/layertree/qgslayertreemodel.cpp index b2392c87a907..e72cd36f81fc 100644 --- a/src/core/layertree/qgslayertreemodel.cpp +++ b/src/core/layertree/qgslayertreemodel.cpp @@ -27,6 +27,7 @@ #include "qgsmaplayer.h" #include "qgsmaplayerlegend.h" #include "qgsmaplayerstylemanager.h" +#include "qgsmeshlayer.h" #include "qgspluginlayer.h" #include "qgsrasterlayer.h" #include "qgsrenderer.h" @@ -200,6 +201,7 @@ QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const // icons possibly overriding default icon QIcon icon; + bool isModified = false; switch ( layer->type() ) { case QgsMapLayerType::RasterLayer: @@ -208,6 +210,7 @@ QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const case QgsMapLayerType::MeshLayer: icon = QgsIconUtils::iconMesh(); + isModified = qobject_cast( layer )->isFrameModified(); break; case QgsMapLayerType::VectorTileLayer: @@ -221,6 +224,7 @@ QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const case QgsMapLayerType::VectorLayer: { QgsVectorLayer *vlayer = qobject_cast( layer ); + isModified = vlayer->isModified(); if ( vlayer->geometryType() == QgsWkbTypes::PointGeometry ) icon = QgsIconUtils::iconPoint(); else if ( vlayer->geometryType() == QgsWkbTypes::LineGeometry ) @@ -245,14 +249,13 @@ QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const icon = legendIconEmbeddedInParent( nodeLayer ); } - QgsVectorLayer *vlayer = qobject_cast( layer ); - if ( vlayer && vlayer->isEditable() && testFlag( UseTextFormatting ) ) + if ( layer->isEditable() && testFlag( UseTextFormatting ) ) { const int iconSize = scaleIconSize( 16 ); QPixmap pixmap( icon.pixmap( iconSize, iconSize ) ); QPainter painter( &pixmap ); - painter.drawPixmap( 0, 0, iconSize, iconSize, QgsApplication::getThemePixmap( vlayer->isModified() ? QStringLiteral( "/mIconEditableEdits.svg" ) : QStringLiteral( "/mActionToggleEditing.svg" ) ) ); + painter.drawPixmap( 0, 0, iconSize, iconSize, QgsApplication::getThemePixmap( isModified ? QStringLiteral( "/mIconEditableEdits.svg" ) : QStringLiteral( "/mActionToggleEditing.svg" ) ) ); painter.end(); icon = QIcon( pixmap ); @@ -941,6 +944,14 @@ void QgsLayerTreeModel::connectToLayer( QgsLayerTreeLayer *nodeLayer ) connect( vl, &QgsVectorLayer::layerModified, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); } + if ( layer->type() == QgsMapLayerType::MeshLayer ) + { + QgsMeshLayer *ml = qobject_cast< QgsMeshLayer * >( layer ); + connect( ml, &QgsMeshLayer::frameEditingStarted, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); + connect( ml, &QgsMeshLayer::frameEditingStopped, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); + connect( ml, &QgsMeshLayer::frameModified, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); + } + emit dataChanged( node2index( nodeLayer ), node2index( nodeLayer ) ); } diff --git a/src/core/layertree/qgslayertreeutils.cpp b/src/core/layertree/qgslayertreeutils.cpp index 8603966fb82f..77d76381b315 100644 --- a/src/core/layertree/qgslayertreeutils.cpp +++ b/src/core/layertree/qgslayertreeutils.cpp @@ -16,6 +16,7 @@ #include "qgslayertreeutils.h" #include "qgslayertree.h" #include "qgsvectorlayer.h" +#include "qgsmeshlayer.h" #include "qgsproject.h" #include "qgslogger.h" @@ -260,18 +261,16 @@ static void _readOldLegendLayer( const QDomElement &layerElem, QgsLayerTreeGroup parent->addChildNode( layerNode ); } - - bool QgsLayerTreeUtils::layersEditable( const QList &layerNodes ) { const auto constLayerNodes = layerNodes; for ( QgsLayerTreeLayer *layerNode : constLayerNodes ) { - QgsVectorLayer *vl = qobject_cast( layerNode->layer() ); - if ( !vl ) + QgsMapLayer *layer = layerNode->layer(); + if ( !layer ) continue; - if ( vl->isEditable() ) + if ( layer->isEditable() ) return true; } return false; @@ -282,12 +281,30 @@ bool QgsLayerTreeUtils::layersModified( const QList &layerN const auto constLayerNodes = layerNodes; for ( QgsLayerTreeLayer *layerNode : constLayerNodes ) { - QgsVectorLayer *vl = qobject_cast( layerNode->layer() ); - if ( !vl ) + QgsMapLayer *layer = layerNode->layer(); + if ( !layer ) continue; - if ( vl->isEditable() && vl->isModified() ) - return true; + switch ( layer->type() ) + { + case QgsMapLayerType::VectorLayer: + { + QgsVectorLayer *vl = qobject_cast( layer ); + if ( vl->isEditable() && vl->isModified() ) + return true; + } + break; + case QgsMapLayerType::MeshLayer: + { + QgsMeshLayer *ml = qobject_cast( layer ); + if ( ml->isEditable() && ml->isFrameModified() ) + return true; + } + break; + default: + continue; + break; + } } return false; } diff --git a/src/core/mesh/qgsmeshdataprovider.cpp b/src/core/mesh/qgsmeshdataprovider.cpp index 6cc7f1a529b0..bc5f4db1ef79 100644 --- a/src/core/mesh/qgsmeshdataprovider.cpp +++ b/src/core/mesh/qgsmeshdataprovider.cpp @@ -44,6 +44,8 @@ void QgsMeshDataProvider::setTemporalUnit( QgsUnitTypes::TemporalUnit unit ) reloadData(); } +QgsMeshDriverMetadata QgsMeshDataProvider::driverMetadata() const { return QgsMeshDriverMetadata();} + QgsMeshDatasetIndex QgsMeshDatasetSourceInterface::datasetIndexAtTime( const QDateTime &referenceTime, int groupIndex, quint64 time, diff --git a/src/core/mesh/qgsmeshdataprovider.h b/src/core/mesh/qgsmeshdataprovider.h index 8a657a4165d5..9e8f19e079a1 100644 --- a/src/core/mesh/qgsmeshdataprovider.h +++ b/src/core/mesh/qgsmeshdataprovider.h @@ -28,6 +28,7 @@ #include "qgis_core.h" #include "qgspoint.h" #include "qgsdataprovider.h" +#include "qgsprovidermetadata.h" #include "qgsmeshdataset.h" #include "qgsmeshdataprovidertemporalcapabilities.h" @@ -168,6 +169,17 @@ class CORE_EXPORT QgsMeshDataSourceInterface SIP_ABSTRACT * \since QGIS 3.6 */ virtual void populateMesh( QgsMesh *mesh ) const = 0; + + /** + * Saves the \a mesh frame to the source. + * + * \param mesh the mesh to save + * + * \returns TRUE on success + * + * \since QGIS 3.22 + */ + virtual bool saveMeshFrame( const QgsMesh &mesh ) = 0; }; /** @@ -415,6 +427,24 @@ class CORE_EXPORT QgsMeshDataProvider: public QgsDataProvider, public QgsMeshDat */ void setTemporalUnit( QgsUnitTypes::TemporalUnit unit ); + + /** + * Returns the mesh driver metadata of the provider + * + * \return the mesh driver metadata of the provider + * + * \since QGIS 3.22 + */ + virtual QgsMeshDriverMetadata driverMetadata() const; + + + /** + * Closes the data provider and free every resources used + * + * \since QGIS 3.22 + */ + virtual void close() = 0; + signals: //! Emitted when some new dataset groups have been added void datasetGroupsAdded( int count ); diff --git a/src/core/mesh/qgsmeshdatasetgroupstore.cpp b/src/core/mesh/qgsmeshdatasetgroupstore.cpp index e0823307952a..810ab6286b8b 100644 --- a/src/core/mesh/qgsmeshdatasetgroupstore.cpp +++ b/src/core/mesh/qgsmeshdatasetgroupstore.cpp @@ -99,11 +99,11 @@ bool QgsMeshDatasetGroupStore::addDatasetGroup( QgsMeshDatasetGroup *group ) switch ( group->dataType() ) { case QgsMeshDatasetGroupMetadata::DataOnFaces: - if ( ! group->checkValueCountPerDataset( mLayer->meshFacesCount() ) ) + if ( ! group->checkValueCountPerDataset( mLayer->meshFaceCount() ) ) return false; break; case QgsMeshDatasetGroupMetadata::DataOnVertices: - if ( ! group->checkValueCountPerDataset( mLayer->meshVerticesCount() ) ) + if ( ! group->checkValueCountPerDataset( mLayer->meshVertexCount() ) ) return false; break; case QgsMeshDatasetGroupMetadata::DataOnVolumes: diff --git a/src/core/mesh/qgsmesheditor.cpp b/src/core/mesh/qgsmesheditor.cpp index 461dec33ef5d..558db83bcd55 100644 --- a/src/core/mesh/qgsmesheditor.cpp +++ b/src/core/mesh/qgsmesheditor.cpp @@ -235,7 +235,6 @@ QgsMeshEditingError QgsMeshEditor::removeVertices( const QList &verticesToR } } - if ( !fillHoles ) { QSet concernedNativeFaces; @@ -255,6 +254,7 @@ QgsMeshEditingError QgsMeshEditor::removeVertices( const QList &verticesToR void QgsMeshEditor::stopEditing() { mTopologicalMesh.reindex(); + mUndoStack->clear(); } @@ -381,3 +381,16 @@ void QgsMeshLayerUndoCommandRemoveFaces::redo() QgsMeshEditingError::QgsMeshEditingError(): errorType( Qgis::MeshEditingErrorType::NoError ), elementIndex( -1 ) {} QgsMeshEditingError::QgsMeshEditingError( Qgis::MeshEditingErrorType type, int elementIndex ): errorType( type ), elementIndex( elementIndex ) {} + +QgsRectangle QgsMeshEditor::extent() const +{ + return mTriangularMesh->nativeExtent(); +} + +bool QgsMeshEditor::isModified() const +{ + if ( mUndoStack ) + return !mUndoStack->isClean(); + + return false; +} diff --git a/src/core/mesh/qgsmesheditor.h b/src/core/mesh/qgsmesheditor.h index 4d57401c202a..59b84ab71f9c 100644 --- a/src/core/mesh/qgsmesheditor.h +++ b/src/core/mesh/qgsmesheditor.h @@ -113,6 +113,12 @@ class CORE_EXPORT QgsMeshEditor : public QObject //! Stops editing void stopEditing(); + //! Returns the extent of the edited mesh + QgsRectangle extent() const; + + //! Returns whether the mesh has been modified + bool isModified() const; + signals: //! Emitted when the mesh is edited void meshEdited(); diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index b23d6fb49512..e64bca54b76b 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -168,6 +168,9 @@ QgsMeshLayer *QgsMeshLayer::clone() const QgsRectangle QgsMeshLayer::extent() const { + if ( mMeshEditor ) + return mMeshEditor->extent(); + if ( mDataProvider ) return mDataProvider->extent(); else @@ -183,6 +186,19 @@ QString QgsMeshLayer::providerType() const return mProviderKey; } +bool QgsMeshLayer::meshFrameSupportEditing() const +{ + if ( !mDataProvider ) + return false; + + if ( mMeshEditor ) + return true; + + QgsMeshDriverMetadata driverMetadata = mDataProvider->driverMetadata(); + + return driverMetadata.capabilities() & QgsMeshDriverMetadata::CanWriteMeshData; +} + bool QgsMeshLayer::addDatasets( const QString &path, const QDateTime &defaultReferenceTime ) { bool isTemporalBefore = dataProvider()->temporalCapabilities()->hasTemporalCapabilities(); @@ -401,9 +417,9 @@ QgsMeshDatasetValue QgsMeshLayer::datasetValue( const QgsMeshDatasetIndex &index QgsMeshDatasetValue value; const QgsTriangularMesh *mesh = triangularMesh(); - if ( mesh && dataProvider() && dataProvider()->isValid() && index.isValid() ) + if ( mesh && index.isValid() ) { - if ( dataProvider()->contains( QgsMesh::ElementType::Edge ) ) + if ( contains( QgsMesh::ElementType::Edge ) ) { QgsRectangle searchRectangle( point.x() - searchRadius, point.y() - searchRadius, point.x() + searchRadius, point.y() + searchRadius ); return dataset1dValue( index, point, searchRadius ); @@ -689,6 +705,7 @@ void QgsMeshLayer::onDatasetGroupsAdded( const QList &datasetGroupIndexes ) void QgsMeshLayer::onMeshEdited() { mRendererCache.reset( new QgsMeshLayerRendererCache() ); + emit frameModified(); triggerRepaint(); } @@ -873,6 +890,12 @@ qint64 QgsMeshLayer::datasetRelativeTimeInMilliseconds( const QgsMeshDatasetInde bool QgsMeshLayer::startFrameEditing( const QgsCoordinateTransform &transform ) { + if ( !meshFrameSupportEditing() ) + { + QgsMessageLog::logMessage( QObject::tr( "Mesh layer \"%1\" not support mesh editing" ).arg( name() ) ); + return false; + } + if ( mMeshEditor ) { QgsMessageLog::logMessage( QObject::tr( "Mesh layer \"%1\" already in editing mode" ).arg( name() ) ); @@ -893,15 +916,78 @@ bool QgsMeshLayer::startFrameEditing( const QgsCoordinateTransform &transform ) return false; } + // During editing, we don't need anymore the provider data. Mesh frame data is stored in the mesh editor. + mDataProvider->close(); + + // All dataset group are removed and replace by a unique virtual dataset group that provide vertices elevation value. + mExtraDatasetUri.clear(); mDatasetGroupStore.reset( new QgsMeshDatasetGroupStore( this ) ); mDatasetGroupStore->addDatasetGroup( new QgsMeshVerticesElevationDatasetGroup( tr( "vertices elevation" ), mNativeMesh.get() ) ); resetDatasetGroupTreeItem(); connect( mMeshEditor, &QgsMeshEditor::meshEdited, this, &QgsMeshLayer::onMeshEdited ); + emit frameEditingStarted(); + return true; } +bool QgsMeshLayer::commitFrameEditing( const QgsCoordinateTransform &transform, bool continueEditing ) +{ + stopFrameEditing( transform ); + + if ( !mDataProvider ) + return false; + + bool res = mDataProvider->saveMeshFrame( *mNativeMesh.get() ); + + if ( continueEditing ) + { + mMeshEditor->initialize(); + emit frameModified(); + return res; + } + + mMeshEditor->deleteLater(); + mMeshEditor = nullptr; + emit frameEditingStopped(); + + mDataProvider->reloadData(); + mDataProvider->populateMesh( mNativeMesh.get() ); + mDatasetGroupStore.reset( new QgsMeshDatasetGroupStore( this ) ); + mDatasetGroupStore->setPersistentProvider( mDataProvider, QStringList() ); + resetDatasetGroupTreeItem(); + return true; +} + +bool QgsMeshLayer::rollBackFrameEditing( const QgsCoordinateTransform &transform, bool continueEditing ) +{ + stopFrameEditing( transform ); + + if ( !mDataProvider ) + return false; + + mDataProvider->reloadData(); + mDataProvider->populateMesh( mNativeMesh.get() ); + updateTriangularMesh( transform ); + + if ( continueEditing ) + { + return mMeshEditor->initialize() == QgsMeshEditingError(); + } + else + { + mMeshEditor->deleteLater(); + mMeshEditor = nullptr; + emit frameEditingStopped(); + + mDatasetGroupStore.reset( new QgsMeshDatasetGroupStore( this ) ); + mDatasetGroupStore->setPersistentProvider( mDataProvider, QStringList() ); + resetDatasetGroupTreeItem(); + return true; + } +} + void QgsMeshLayer::stopFrameEditing( const QgsCoordinateTransform &transform ) { if ( !mMeshEditor ) @@ -909,6 +995,7 @@ void QgsMeshLayer::stopFrameEditing( const QgsCoordinateTransform &transform ) mMeshEditor->stopEditing(); mTriangularMeshes.at( 0 )->update( mNativeMesh.get(), transform ); + mRendererCache.reset( new QgsMeshLayerRendererCache() ); } QgsMeshEditor *QgsMeshLayer::meshEditor() @@ -916,7 +1003,29 @@ QgsMeshEditor *QgsMeshLayer::meshEditor() return mMeshEditor; } -int QgsMeshLayer::meshVerticesCount() const +bool QgsMeshLayer::isFrameModified() const +{ + if ( mMeshEditor ) + return mMeshEditor->isModified(); + + return false; +} + +bool QgsMeshLayer::contains( const QgsMesh::ElementType &type ) const +{ + switch ( type ) + { + case QgsMesh::ElementType::Vertex: + return meshVertexCount() != 0; + case QgsMesh::ElementType::Edge: + return meshEdgeCount() != 0; + case QgsMesh::ElementType::Face: + return meshFaceCount() != 0; + } + return false; +} + +int QgsMeshLayer::meshVertexCount() const { if ( mMeshEditor ) return mNativeMesh->vertexCount(); @@ -924,7 +1033,7 @@ int QgsMeshLayer::meshVerticesCount() const return mDataProvider->vertexCount(); } -int QgsMeshLayer::meshFacesCount() const +int QgsMeshLayer::meshFaceCount() const { if ( mMeshEditor ) return mNativeMesh->faceCount(); @@ -1475,6 +1584,11 @@ QString QgsMeshLayer::htmlMetadata() const return myMetadata; } +bool QgsMeshLayer::isEditable() const +{ + return mMeshEditor != nullptr; +} + bool QgsMeshLayer::setDataProvider( QString const &provider, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags ) { mDatasetGroupStore->setPersistentProvider( nullptr, QStringList() ); diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index 4800984e3dd4..0d86d147e31b 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -179,6 +179,7 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer void reload() override; QStringList subLayers() const override; QString htmlMetadata() const override; + bool isEditable() const override; //! Returns the provider type for this layer QString providerType() const; @@ -720,6 +721,15 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer */ qint64 datasetRelativeTimeInMilliseconds( const QgsMeshDatasetIndex &index ); + /** + * Returns whether the mesh frame support editing + * + * \return whether the mesh frame support editing + * + * \since QGIS 3.22 + */ + bool meshFrameSupportEditing() const; + /** * Starts edition of the mesh frame. Coordinate \a transform used to initialize the triangular mesh if needed. * This operation will disconnect the mesh layer from the data provider anf removes all existing dataset group @@ -728,6 +738,26 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer */ bool startFrameEditing( const QgsCoordinateTransform &transform ); + /** + * Commit edition of the mesh frame, + * Rebuilds the triangular mesh and its spatial index with \a transform, + * Continue editing with the same mesh editor if \a continueEditing is True + * + * \return whether the commit succeeds + * \since QGIS 3.22 + */ + bool commitFrameEditing( const QgsCoordinateTransform &transform, bool continueEditing = true ); + + /** + * Roll Back edition of the mesh frame. + * Reload mesh from file, rebuilds the triangular mesh and its spatial index with \a transform, + * Continue editing with the same mesh editor if \a continueEditing is True + * + * \return whether the commit succeeds + * \since QGIS 3.22 + */ + bool rollBackFrameEditing( const QgsCoordinateTransform &transform, bool continueEditing = true ); + /** * Stops edition of the mesh, re-indexes the faces and vertices, * rebuilds the triangular mesh and its spatial index with \a transform, @@ -744,19 +774,33 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer */ QgsMeshEditor *meshEditor(); + /** + * Returns whether the mesh fram has been modified since the last save + * + * \since QGIS 3.22 + */ + bool isFrameModified() const; + + + /** + * Returns whether the mesh contains at mesh elements of given type + * \since QGIS 3.22 + */ + bool contains( const QgsMesh::ElementType &type ) const; + /** * Returns the vertices count of the mesh frame * * \since QGIS 3.22 */ - int meshVerticesCount() const; + int meshVertexCount() const; /** * Returns the faces count of the mesh frame * * \since QGIS 3.22 */ - int meshFacesCount() const; + int meshFaceCount() const; /** * Returns the edges count of the mesh frame @@ -797,6 +841,27 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer */ void timeSettingsChanged( ); + /** + * Emitted when the mesh frame is modified + * + * \since QGIS 3.22 + */ + void frameModified(); + + /** + * Emitted when the mesh frame editing is started + * + * \since QGIS 3.22 + */ + void frameEditingStarted(); + + /** + * Emitted when the mesh frame editing is stopped + * + * \since QGIS 3.22 + */ + void frameEditingStopped(); + private: // Private methods /** diff --git a/src/core/mesh/qgstriangularmesh.cpp b/src/core/mesh/qgstriangularmesh.cpp index 8ac013db9ef8..20bf95fb0168 100644 --- a/src/core/mesh/qgstriangularmesh.cpp +++ b/src/core/mesh/qgstriangularmesh.cpp @@ -170,7 +170,8 @@ bool QgsTriangularMesh::update( QgsMesh *nativeMesh, const QgsCoordinateTransfor Q_ASSERT( nativeMesh ); // FIND OUT IF UPDATE IS NEEDED - if ( mTriangularMesh.vertices.size() >= nativeMesh->vertices.size() && + if ( mTriangularMesh.vertices.size() == nativeMesh->vertices.size() && + mNativeMeshFaceCentroids.size() == nativeMesh->faces.size() && mTriangularMesh.faces.size() >= nativeMesh->faces.size() && mTriangularMesh.edges.size() == nativeMesh->edges.size() && ( ( !mCoordinateTransform.isValid() && !transform.isValid() ) || @@ -290,6 +291,27 @@ QgsMeshVertex QgsTriangularMesh::triangularToNativeCoordinates( const QgsMeshVer return transformVertex( vertex, QgsCoordinateTransform::ReverseTransform ); } +QgsRectangle QgsTriangularMesh::nativeExtent() +{ + QgsRectangle nativeExtent; + if ( mCoordinateTransform.isValid() ) + { + try + { + nativeExtent = mCoordinateTransform.transform( mExtent, QgsCoordinateTransform::ReverseTransform ); + } + catch ( QgsCsException &cse ) + { + Q_UNUSED( cse ) + QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) ); + } + } + else + nativeExtent = mExtent; + + return nativeExtent; +} + QgsRectangle QgsTriangularMesh::extent() const { return mExtent; diff --git a/src/core/mesh/qgstriangularmesh.h b/src/core/mesh/qgstriangularmesh.h index 91bf74a9dfe5..1e1e6d73ab40 100644 --- a/src/core/mesh/qgstriangularmesh.h +++ b/src/core/mesh/qgstriangularmesh.h @@ -292,6 +292,14 @@ class CORE_EXPORT QgsTriangularMesh // TODO rename to QgsRendererMesh in QGIS 4 */ QgsMeshVertex triangularToNativeCoordinates( const QgsMeshVertex &vertex ) const; + /** + * + * Returns the extent of the mesh in the native mesh coordinates system, returns empty extent if the transformation fails + * + * \since QGIS 3.22 + */ + QgsRectangle nativeExtent(); + private: /** diff --git a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp index e103a72038a4..368f8f739217 100644 --- a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp +++ b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.cpp @@ -623,6 +623,14 @@ bool QgsMeshMemoryDataProvider::persistDatasetGroup( const QString &outputFilePa return true; // not implemented/supported } +void QgsMeshMemoryDataProvider::close() +{ + mVertices.clear(); + mFaces.clear(); + mEdges.clear(); + mDatasetGroups.clear(); +} + QgsRectangle QgsMeshMemoryDataProvider::calculateExtent() const { QgsRectangle rec; diff --git a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h index a4e38f0fb2ee..31b78451b9aa 100644 --- a/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h +++ b/src/core/providers/meshmemory/qgsmeshmemorydataprovider.h @@ -153,6 +153,10 @@ class CORE_EXPORT QgsMeshMemoryDataProvider final: public QgsMeshDataProvider int datasetGroupIndex ) override; + bool saveMeshFrame( const QgsMesh & ) override {return false;} + + void close() override; + //! Returns the memory provider key static QString providerKey(); //! Returns the memory provider description diff --git a/src/core/qgsmaplayerlegend.cpp b/src/core/qgsmaplayerlegend.cpp index 32c674412901..c633484a6574 100644 --- a/src/core/qgsmaplayerlegend.cpp +++ b/src/core/qgsmaplayerlegend.cpp @@ -519,10 +519,6 @@ QList QgsDefaultMeshLayerLegend::createLayerTreeM { QList nodes; - QgsMeshDataProvider *provider = mLayer->dataProvider(); - if ( !provider ) - return nodes; - QgsMeshRendererSettings rendererSettings = mLayer->rendererSettings(); int indexScalar = rendererSettings.activeScalarDatasetGroup(); @@ -530,11 +526,11 @@ QList QgsDefaultMeshLayerLegend::createLayerTreeM QString name; if ( indexScalar > -1 && indexVector > -1 && indexScalar != indexVector ) - name = QString( "%1 / %2" ).arg( provider->datasetGroupMetadata( indexScalar ).name(), provider->datasetGroupMetadata( indexVector ).name() ); + name = QString( "%1 / %2" ).arg( mLayer->datasetGroupMetadata( indexScalar ).name(), mLayer->datasetGroupMetadata( indexVector ).name() ); else if ( indexScalar > -1 ) - name = provider->datasetGroupMetadata( indexScalar ).name(); + name = mLayer->datasetGroupMetadata( indexScalar ).name(); else if ( indexVector > -1 ) - name = provider->datasetGroupMetadata( indexVector ).name(); + name = mLayer->datasetGroupMetadata( indexVector ).name(); else { // neither contours nor vectors get rendered - no legend needed diff --git a/src/gui/mesh/qgsmeshrendereractivedatasetwidget.cpp b/src/gui/mesh/qgsmeshrendereractivedatasetwidget.cpp index c0828ed65831..e4e079ca6b9f 100644 --- a/src/gui/mesh/qgsmeshrendereractivedatasetwidget.cpp +++ b/src/gui/mesh/qgsmeshrendereractivedatasetwidget.cpp @@ -147,9 +147,9 @@ QString QgsMeshRendererActiveDatasetWidget::metadata( QgsMeshDatasetIndex datase msg += QLatin1String( "" ); QString definedOnMesh; - if ( mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Face ) ) + if ( mMeshLayer->contains( QgsMesh::ElementType::Face ) ) { - if ( mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Edge ) ) + if ( mMeshLayer->contains( QgsMesh::ElementType::Edge ) ) { definedOnMesh = tr( "faces and edges" ); } @@ -158,7 +158,7 @@ QString QgsMeshRendererActiveDatasetWidget::metadata( QgsMeshDatasetIndex datase definedOnMesh = tr( "faces" ); } } - else if ( mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Edge ) ) + else if ( mMeshLayer->contains( QgsMesh::ElementType::Edge ) ) { definedOnMesh = tr( "edges" ); } diff --git a/src/gui/mesh/qgsmeshrendererscalarsettingswidget.cpp b/src/gui/mesh/qgsmeshrendererscalarsettingswidget.cpp index 253d69803912..f3d010ebdfa4 100644 --- a/src/gui/mesh/qgsmeshrendererscalarsettingswidget.cpp +++ b/src/gui/mesh/qgsmeshrendererscalarsettingswidget.cpp @@ -84,8 +84,7 @@ QgsMeshRendererScalarSettings QgsMeshRendererScalarSettingsWidget::settings() co settings.setOpacity( mOpacityWidget->opacity() ); settings.setDataResamplingMethod( dataIntepolationMethod() ); - bool hasEdges = ( mMeshLayer->dataProvider() && - mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Edge ) ); + bool hasEdges = ( mMeshLayer->contains( QgsMesh::ElementType::Edge ) ); if ( hasEdges ) { @@ -121,10 +120,8 @@ void QgsMeshRendererScalarSettingsWidget::syncToLayer( ) int index = mScalarInterpolationTypeComboBox->findData( settings.dataResamplingMethod() ); whileBlocking( mScalarInterpolationTypeComboBox )->setCurrentIndex( index ); - bool hasEdges = ( mMeshLayer->dataProvider() && - mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Edge ) ); - bool hasFaces = ( mMeshLayer->dataProvider() && - mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Face ) ); + bool hasEdges = ( mMeshLayer->contains( QgsMesh::ElementType::Edge ) ); + bool hasFaces = ( mMeshLayer->contains( QgsMesh::ElementType::Face ) ); mScalarResamplingWidget->setVisible( hasFaces ); diff --git a/src/gui/mesh/qgsrenderermeshpropertieswidget.cpp b/src/gui/mesh/qgsrenderermeshpropertieswidget.cpp index 9b592d16b5f8..45de3c6131a0 100644 --- a/src/gui/mesh/qgsrenderermeshpropertieswidget.cpp +++ b/src/gui/mesh/qgsrenderermeshpropertieswidget.cpp @@ -148,12 +148,10 @@ void QgsRendererMeshPropertiesWidget::syncToLayer() onActiveScalarGroupChanged( mMeshLayer->rendererSettings().activeScalarDatasetGroup() ); onActiveVectorGroupChanged( mMeshLayer->rendererSettings().activeVectorDatasetGroup() ); - bool hasFaces = ( mMeshLayer->dataProvider() && - mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Face ) ); + bool hasFaces = ( mMeshLayer->contains( QgsMesh::ElementType::Face ) ); mFaceMeshGroupBox->setVisible( hasFaces ); - bool hasEdges = ( mMeshLayer->dataProvider() && - mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Edge ) ); + bool hasEdges = ( mMeshLayer->contains( QgsMesh::ElementType::Edge ) ); mEdgeMeshGroupBox->setVisible( hasEdges ); QgsSettings settings; diff --git a/src/providers/mdal/qgsmdalprovider.cpp b/src/providers/mdal/qgsmdalprovider.cpp index e68fc4dfa79c..a44bcd60ad41 100644 --- a/src/providers/mdal/qgsmdalprovider.cpp +++ b/src/providers/mdal/qgsmdalprovider.cpp @@ -221,6 +221,34 @@ QgsRectangle QgsMdalProvider::extent() const return ret; } +QgsMeshDriverMetadata QgsMdalProvider::driverMetadata() const +{ + if ( !mMeshH ) + return QgsMeshDriverMetadata(); + + QString name = MDAL_M_driverName( mMeshH ); + MDAL_DriverH mdalDriver = MDAL_driverFromName( name.toStdString().c_str() ); + QString longName = MDAL_DR_longName( mdalDriver ); + QString writeDatasetSuffix = MDAL_DR_writeDatasetsSuffix( mdalDriver ); + + QgsMeshDriverMetadata::MeshDriverCapabilities capabilities; + bool hasSaveFaceDatasetsCapability = MDAL_DR_writeDatasetsCapability( mdalDriver, MDAL_DataLocation::DataOnFaces ); + if ( hasSaveFaceDatasetsCapability ) + capabilities |= QgsMeshDriverMetadata::CanWriteFaceDatasets; + bool hasSaveVertexDatasetsCapability = MDAL_DR_writeDatasetsCapability( mdalDriver, MDAL_DataLocation::DataOnVertices ); + if ( hasSaveVertexDatasetsCapability ) + capabilities |= QgsMeshDriverMetadata::CanWriteVertexDatasets; + bool hasSaveEdgeDatasetsCapability = MDAL_DR_writeDatasetsCapability( mdalDriver, MDAL_DataLocation::DataOnEdges ); + if ( hasSaveEdgeDatasetsCapability ) + capabilities |= QgsMeshDriverMetadata::CanWriteEdgeDatasets; + bool hasMeshSaveCapability = MDAL_DR_saveMeshCapability( mdalDriver ); + if ( hasMeshSaveCapability ) + capabilities |= QgsMeshDriverMetadata::CanWriteMeshData; + const QgsMeshDriverMetadata meta( name, longName, capabilities, writeDatasetSuffix ); + + return meta; +} + bool QgsMdalProvider::persistDatasetGroup( const QString &outputFilePath, const QString &outputDriver, @@ -420,6 +448,21 @@ bool QgsMdalProvider::persistDatasetGroup( const QString &outputFilePath, const return true; } +bool QgsMdalProvider::saveMeshFrame( const QgsMesh &mesh ) +{ + QgsMdalProviderMetadata mdalProviderMetaData; + return mdalProviderMetaData.createMeshData( mesh, dataSourceUri(), mDriverName, crs() ); +} + +void QgsMdalProvider::close() +{ + if ( mMeshH ) + MDAL_CloseMesh( mMeshH ); + mMeshH = nullptr; + + mExtraDatasetUris.clear(); +} + void QgsMdalProvider::loadData() { QByteArray curi = dataSourceUri().toUtf8(); @@ -428,6 +471,7 @@ void QgsMdalProvider::loadData() if ( mMeshH ) { + mDriverName = MDAL_M_driverName( mMeshH ); const QString proj = MDAL_M_projection( mMeshH ); if ( !proj.isEmpty() ) mCrs.createFromString( proj ); @@ -472,7 +516,7 @@ void QgsMdalProvider::reloadProviderData() int datasetCountBeforeAdding = datasetGroupCount(); if ( mMeshH ) - for ( auto uri : mExtraDatasetUris ) + for ( const QString &uri : std::as_const( mExtraDatasetUris ) ) { std::string str = uri.toStdString(); MDAL_M_LoadDatasets( mMeshH, str.c_str() ); diff --git a/src/providers/mdal/qgsmdalprovider.h b/src/providers/mdal/qgsmdalprovider.h index e0e4a3e40682..bc6afc50ac90 100644 --- a/src/providers/mdal/qgsmdalprovider.h +++ b/src/providers/mdal/qgsmdalprovider.h @@ -78,6 +78,8 @@ class QgsMdalProvider : public QgsMeshDataProvider QgsMeshDataBlock areFacesActive( QgsMeshDatasetIndex index, int faceIndex, int count ) const override; QgsRectangle extent() const override; + QgsMeshDriverMetadata driverMetadata() const override; + bool persistDatasetGroup( const QString &outputFilePath, const QString &outputDriver, const QgsMeshDatasetGroupMetadata &meta, @@ -92,6 +94,10 @@ class QgsMdalProvider : public QgsMeshDataProvider int datasetGroupIndex ) override; + bool saveMeshFrame( const QgsMesh &mesh ) override; + + void close() override; + /** * Returns file filters for meshes and datasets to be used in Open File Dialogs * \param fileMeshFiltersString file mesh filters @@ -124,6 +130,7 @@ class QgsMdalProvider : public QgsMeshDataProvider QStringList mExtraDatasetUris; QgsCoordinateReferenceSystem mCrs; QStringList mSubLayersUris; + QString mDriverName; /** * Closes and reloads dataset diff --git a/tests/src/core/testqgsmesheditor.cpp b/tests/src/core/testqgsmesheditor.cpp index 41b1d25d8fd1..b6c6f0a159bc 100644 --- a/tests/src/core/testqgsmesheditor.cpp +++ b/tests/src/core/testqgsmesheditor.cpp @@ -42,7 +42,7 @@ class TestQgsMeshEditor : public QObject void init(); // will be called before each testfunction is executed. void cleanup() {} // will be called after every testfunction. - void startEditing(); + void startStopEditing(); void createTopologicMesh(); void editTopologicMesh(); void badTopologicMesh(); @@ -86,13 +86,13 @@ void TestQgsMeshEditor::init() QVERIFY( meshLayerQuadTriangle ); QCOMPARE( meshLayerQuadTriangle->datasetGroupCount(), 1 ); - uri = QString( mDataDir + "/quad_flower.2dm" ); + uri = QString( mDataDir + "/quad_flower_to_edit.2dm" ); meshLayerQuadFlower.reset( new QgsMeshLayer( uri, "Quad Flower", "mdal" ) ); QVERIFY( meshLayerQuadFlower ); QCOMPARE( meshLayerQuadFlower->datasetGroupCount(), 1 ); } -void TestQgsMeshEditor::startEditing() +void TestQgsMeshEditor::startStopEditing() { meshLayerQuadTriangle->addDatasets( mDataDir + "/quad_and_triangle_vertex_scalar.dat" ); QCOMPARE( meshLayerQuadTriangle->datasetGroupCount(), 2 ); @@ -115,9 +115,61 @@ void TestQgsMeshEditor::startEditing() QCOMPARE( meta.isScalar(), true ); QCOMPARE( meta.minimum(), 10.0 ); QCOMPARE( meta.maximum(), 50.0 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 5 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 2 ); QgsMesh mesh = *meshLayerQuadTriangle->nativeMesh(); for ( int i = 0; i < mesh.vertexCount(); ++i ) QCOMPARE( mesh.vertex( i ).z(), meshLayerQuadTriangle->datasetValue( QgsMeshDatasetIndex( 0, 0 ), i ).scalar() ); + + QgsMeshEditor *editor = meshLayerQuadTriangle->meshEditor(); + QCOMPARE( editor->addVertices( {QgsMeshVertex( 1500, 2500, 0 )}, 10 ), 1 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 6 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 6 ); + + // roll back editing and continue editing + QVERIFY( meshLayerQuadTriangle->isFrameModified() ); + QVERIFY( meshLayerQuadTriangle->rollBackFrameEditing( transform, true ) ); + QVERIFY( editor->mTriangularMesh->faceCentroids().count() == editor->mMesh->faceCount() ); + QVERIFY( editor->mTriangularMesh->vertices().count() == editor->mMesh->vertexCount() ); + + QVERIFY( meshLayerQuadTriangle->meshEditor() ); + + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 5 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 2 ); + QCOMPARE( meshLayerQuadTriangle->datasetGroupCount(), 1 ); + datasetGroupIndex = meshLayerQuadTriangle->datasetGroupsIndexes().at( 0 ); + meta = meshLayerQuadTriangle->datasetGroupMetadata( datasetGroupIndex ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); + QVERIFY( meta.name() == QStringLiteral( "vertices elevation" ) ); + QCOMPARE( meta.isTemporal(), false ); + QCOMPARE( meta.isScalar(), true ); + QCOMPARE( meta.minimum(), 10.0 ); + QCOMPARE( meta.maximum(), 50.0 ); + + QCOMPARE( editor->addVertices( {QgsMeshVertex( 1500, 2500, 0 )}, 10 ), 1 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 6 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 6 ); + + // roll back editing and stop editing + QVERIFY( meshLayerQuadTriangle->isFrameModified() ); + QVERIFY( meshLayerQuadTriangle->rollBackFrameEditing( transform, false ) ); + + QVERIFY( !meshLayerQuadTriangle->meshEditor() ); + + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 5 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 2 ); + QCOMPARE( meshLayerQuadTriangle->datasetGroupCount(), 1 ); + datasetGroupIndex = meshLayerQuadTriangle->datasetGroupsIndexes().at( 0 ); + meta = meshLayerQuadTriangle->datasetGroupMetadata( datasetGroupIndex ); + QVERIFY( meta.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices ); + QVERIFY( meta.name() == QStringLiteral( "Bed Elevation" ) ); + QCOMPARE( meta.isTemporal(), false ); + QCOMPARE( meta.isScalar(), true ); + QCOMPARE( meta.minimum(), 10.0 ); + QCOMPARE( meta.maximum(), 50.0 ); + + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 5 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 2 ); } static bool checkNeighbors( const QgsTopologicalMesh &mesh, int faceIndex, const QList &expectedNeighbors ) @@ -607,8 +659,8 @@ void TestQgsMeshEditor::meshEditorSimpleEdition() void TestQgsMeshEditor::meshEditorFromMeshLayer_quadTriangle() { - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 5 ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 2 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 5 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 2 ); QgsCoordinateTransform transform; QVERIFY( meshLayerQuadTriangle->startFrameEditing( transform ) ); @@ -616,10 +668,10 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadTriangle() QgsMeshEditor *editor = meshLayerQuadTriangle->meshEditor(); editor->addVertices( {{4000, 2000, 0}}, 10 ); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 6 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 6 ); meshLayerQuadTriangle->undoStack()->undo(); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 5 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 5 ); editor->addVertices( { @@ -627,50 +679,50 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadTriangle() {4000, 3000, 0} // 6 }, 10 ); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 7 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 7 ); meshLayerQuadTriangle->undoStack()->undo(); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 5 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 5 ); meshLayerQuadTriangle->undoStack()->redo(); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 7 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 7 ); // try to add a face that shares only one vertex QgsMeshEditingError error = editor->addFaces( {{2, 5, 6}} ); QVERIFY( error == QgsMeshEditingError( Qgis::MeshEditingErrorType::UniqueSharedVertex, 2 ) ); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 7 ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 2 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 7 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 2 ); // Add a face that shares two vertices error = editor->addFaces( {{2, 3, 6}} ); QVERIFY( error == QgsMeshEditingError() ); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 7 ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 3 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 7 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 3 ); QgsPointXY centroid = meshLayerQuadTriangle->snapOnElement( QgsMesh::Face, QgsPoint( 3100, 2600, 0 ), 10 ); QVERIFY( centroid.compare( QgsPointXY( 3000, 2666.666666666 ), 1e-6 ) ); meshLayerQuadTriangle->undoStack()->undo(); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 7 ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 2 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 7 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 2 ); centroid = meshLayerQuadTriangle->snapOnElement( QgsMesh::Face, QgsPoint( 3100, 2600, 0 ), 10 ); QVERIFY( centroid.isEmpty() ); meshLayerQuadTriangle->undoStack()->undo(); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 5 ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 2 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 5 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 2 ); meshLayerQuadTriangle->undoStack()->redo(); meshLayerQuadTriangle->undoStack()->redo(); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 7 ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 3 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 7 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 3 ); // Add another face error = editor->addFaces( {{2, 5, 6}} ); QVERIFY( error == QgsMeshEditingError() ); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 7 ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 4 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 7 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 4 ); centroid = meshLayerQuadTriangle->snapOnElement( QgsMesh::Face, QgsPoint( 3100, 2600, 0 ), 10 ); QVERIFY( centroid.compare( QgsPointXY( 3000, 2666.666666666 ), 1e-6 ) ); @@ -698,7 +750,7 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadTriangle() editor->removeFaces( {2, 3} ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 4 ); //removed faces are still present but empty + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 4 ); //removed faces are still present but empty QVERIFY( meshLayerQuadTriangle->nativeMesh()->face( 2 ).isEmpty() ); QVERIFY( meshLayerQuadTriangle->nativeMesh()->face( 3 ).isEmpty() ); @@ -716,7 +768,7 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadTriangle() centroid = meshLayerQuadTriangle->snapOnElement( QgsMesh::Face, QgsPoint( 3500, 2100, 0 ), 10 ); QVERIFY( centroid.compare( QgsPointXY( 3666.6666666, 2333.33333333 ), 1e-6 ) ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 4 ); //removed faces are still present but empty + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 4 ); //removed faces are still present but empty QVERIFY( QgsMesh::compareFaces( meshLayerQuadTriangle->nativeMesh()->face( 2 ), {2, 6, 3} ) ); QVERIFY( QgsMesh::compareFaces( meshLayerQuadTriangle->nativeMesh()->face( 3 ), {2, 5, 6} ) ); @@ -727,8 +779,8 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadTriangle() {3000, 3500, 0} }, 10 ); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 9 ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 8 ); // vertex on a quad face : 4 faces created, 1 removed, removed are still present but void + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 9 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 8 ); // vertex on a quad face : 4 faces created, 1 removed, removed are still present but void QVERIFY( meshLayerQuadTriangle->nativeMesh()->face( 0 ).isEmpty() ); QVERIFY( QgsMesh::compareFaces( meshLayerQuadTriangle->nativeMesh()->face( 4 ), {0, 1, 7} ) ); QVERIFY( QgsMesh::compareFaces( meshLayerQuadTriangle->nativeMesh()->face( 5 ), {1, 3, 7} ) ); @@ -755,8 +807,8 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadTriangle() centroid = meshLayerQuadTriangle->snapOnElement( QgsMesh::Face, QgsPoint( 3500, 2100, 0 ), 10 ); QVERIFY( centroid.compare( QgsPointXY( 3666.6666666, 2333.33333333 ), 1e-6 ) ); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 7 ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 4 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 7 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 4 ); QVERIFY( QgsMesh::compareFaces( meshLayerQuadTriangle->nativeMesh()->face( 2 ), {2, 6, 3} ) ); QVERIFY( QgsMesh::compareFaces( meshLayerQuadTriangle->nativeMesh()->face( 3 ), {2, 5, 6} ) ); @@ -771,8 +823,8 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadTriangle() centroid = meshLayerQuadTriangle->snapOnElement( QgsMesh::Face, QgsPoint( 1950, 2700, 0 ), 10 ); QVERIFY( centroid.compare( QgsPointXY( 1833.33333333, 2600 ), 1e-6 ) ); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 9 ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 8 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 9 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 8 ); editor->removeVertices( {7} ); @@ -785,7 +837,7 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadTriangle() centroid = meshLayerQuadTriangle->snapOnElement( QgsMesh::Face, QgsPoint( 1950, 2700, 0 ), 10 ); QVERIFY( centroid.isEmpty() ); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 9 ); //empty vertex still presents + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 9 ); //empty vertex still presents snappedPoint = meshLayerQuadTriangle->snapOnElement( QgsMesh::Vertex, QgsPoint( 1498, 3505, 0 ), 10 ); QVERIFY( snappedPoint.isEmpty() ); @@ -805,8 +857,8 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadTriangle() QVERIFY( snappedPoint.compare( QgsPointXY( 1500, 2800 ), 1e-6 ) ); meshLayerQuadTriangle->stopFrameEditing( transform ); - QCOMPARE( meshLayerQuadTriangle->meshVerticesCount(), 9 ); - QCOMPARE( meshLayerQuadTriangle->meshFacesCount(), 7 ); + QCOMPARE( meshLayerQuadTriangle->meshVertexCount(), 9 ); + QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 7 ); centroid = meshLayerQuadTriangle->snapOnElement( QgsMesh::Face, QgsPoint( 1400, 2050, 0 ), 10 ); QVERIFY( centroid.compare( QgsPointXY( 1500, 2266.66666666 ), 1e-6 ) ); @@ -823,8 +875,8 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadTriangle() void TestQgsMeshEditor::meshEditorFromMeshLayer_quadFlower() { - QCOMPARE( meshLayerQuadFlower->meshVerticesCount(), 8 ); - QCOMPARE( meshLayerQuadFlower->meshFacesCount(), 5 ); + QCOMPARE( meshLayerQuadFlower->meshVertexCount(), 8 ); + QCOMPARE( meshLayerQuadFlower->meshFaceCount(), 5 ); QgsCoordinateTransform transform; QVERIFY( meshLayerQuadFlower->startFrameEditing( transform ) ); @@ -835,28 +887,28 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadFlower() meshLayerQuadFlower->startFrameEditing( transform ); editor = meshLayerQuadFlower->meshEditor(); QCOMPARE( editor->addPointsAsVertices( {QgsPoint( 1500, 2800, -10 )}, 10 ), 1 ); // 8 - QCOMPARE( meshLayerQuadFlower->meshFacesCount(), 9 ); + QCOMPARE( meshLayerQuadFlower->meshFaceCount(), 9 ); QCOMPARE( editor->addPointsAsVertices( {QgsPoint( 1800, 2700, -10 )}, 10 ), 1 ); // 9 - QCOMPARE( meshLayerQuadFlower->meshFacesCount(), 12 ); + QCOMPARE( meshLayerQuadFlower->meshFaceCount(), 12 ); QCOMPARE( editor->addPointsAsVertices( {QgsPoint( 1400, 2300, -10 ), QgsPoint( 1500, 2200, -10 )}, 10 ), 2 ); // 10 & 11 - QCOMPARE( meshLayerQuadFlower->meshFacesCount(), 18 ); + QCOMPARE( meshLayerQuadFlower->meshFaceCount(), 18 ); // attempt to add a vertex under tolerance next existing one QCOMPARE( editor->addPointsAsVertices( {QgsPoint( 1499, 2801, -10 )}, 10 ), 0 ); - QCOMPARE( meshLayerQuadFlower->meshFacesCount(), 18 ); + QCOMPARE( meshLayerQuadFlower->meshFaceCount(), 18 ); QCOMPARE( editor->addPointsAsVertices( {QgsPoint( 700, 1750, 0 )}, 10 ), 1 ); // 12 - QCOMPARE( meshLayerQuadFlower->meshFacesCount(), 18 ); - QCOMPARE( meshLayerQuadFlower->meshVerticesCount(), 13 ); + QCOMPARE( meshLayerQuadFlower->meshFaceCount(), 18 ); + QCOMPARE( meshLayerQuadFlower->meshVertexCount(), 13 ); QVERIFY( editor->addFace( {0, 6, 12} ) == QgsMeshEditingError() ); QCOMPARE( editor->addPointsAsVertices( {QgsPoint( 1400, 2200, -10 )}, 10 ), 1 ); // 13 - QCOMPARE( meshLayerQuadFlower->meshFacesCount(), 22 ); + QCOMPARE( meshLayerQuadFlower->meshFaceCount(), 22 ); QCOMPARE( meshLayerQuadFlower->datasetValue( QgsMeshDatasetIndex( 0, 0 ), QgsPointXY( 1420, 2220 ), 10 ).x(), -10 ); QVERIFY( editor->removeVertices( {0}, true ) == QgsMeshEditingError() ); //for now filling after removing boundary vertices is not supported, --> behavior as it is false @@ -918,16 +970,46 @@ void TestQgsMeshEditor::meshEditorFromMeshLayer_quadFlower() QVERIFY( editor->removeVertices( {7}, true ) == QgsMeshEditingError() ); - QVERIFY( editor->removeVertices( {4}, false ).errorType != Qgis::MeshEditingErrorType::NoError ); // leads to a topological error + QCOMPARE( meshLayerQuadFlower->meshFaceCount(), 57 ); + QCOMPARE( meshLayerQuadFlower->meshVertexCount(), 16 ); + + meshLayerQuadFlower->commitFrameEditing( transform, true ); + + QVERIFY( meshLayerQuadFlower->meshEditor() == editor ); + + QCOMPARE( meshLayerQuadFlower->meshFaceCount(), 5 ); + QCOMPARE( meshLayerQuadFlower->meshVertexCount(), 7 ); + + QVERIFY( editor->removeVertices( {3}, false ).errorType != Qgis::MeshEditingErrorType::NoError ); // leads to a topological error + + QVERIFY( editor->removeVertices( {3}, true ).errorType != Qgis::MeshEditingErrorType::NoError ); // filling after removing boundary not supported, so not fill and leads to a topological error + + QVERIFY( editor->removeVertices( {4}, true ) == QgsMeshEditingError() ); + + meshLayerQuadFlower->commitFrameEditing( transform, false ); + + QVERIFY( meshLayerQuadFlower->meshEditor() == nullptr ); + ; + QCOMPARE( meshLayerQuadFlower->meshFaceCount(), 4 ); + QCOMPARE( meshLayerQuadFlower->meshVertexCount(), 6 ); + + QFile alteredFile( QString( mDataDir + "/quad_flower_to_edit.2dm" ) ); + QFile expectedFile( QString( mDataDir + "/quad_flower_to_edit_expected.2dm" ) ); + QFile original( QString( mDataDir + "/quad_flower.2dm" ) ); - QVERIFY( editor->removeVertices( {4}, true ).errorType != Qgis::MeshEditingErrorType::NoError ); // filling after removing boundary not supported, so not fill and leads to a topological error + alteredFile.open( QIODevice::ReadOnly ); + original.open( QIODevice::ReadOnly ); + expectedFile.open( QIODevice::ReadOnly ); - QVERIFY( editor->removeVertices( {5}, true ) == QgsMeshEditingError() ); + QTextStream streamAltered( &alteredFile ); + QTextStream streamOriginal( &original ); + QTextStream streamExpected( &expectedFile ); - meshLayerQuadFlower->stopFrameEditing( transform ); + QCOMPARE( streamAltered.readAll(), streamExpected.readAll() ); - QCOMPARE( meshLayerQuadFlower->meshFacesCount(), 4 ); - QCOMPARE( meshLayerQuadFlower->meshVerticesCount(), 6 ); + alteredFile.close(); + alteredFile.open( QIODevice::WriteOnly ); + streamAltered << streamOriginal.readAll(); } QGSTEST_MAIN( TestQgsMeshEditor ) diff --git a/tests/src/core/testqgsmeshlayer.cpp b/tests/src/core/testqgsmeshlayer.cpp index 5b236a98a716..8d9e338504bb 100644 --- a/tests/src/core/testqgsmeshlayer.cpp +++ b/tests/src/core/testqgsmeshlayer.cpp @@ -133,6 +133,7 @@ void TestQgsMeshLayer::initTestCase() QCOMPARE( mMemoryLayer->datasetGroupTreeRootItem()->childCount(), 5 ); QVERIFY( mMemoryLayer->dataProvider()->temporalCapabilities()->hasTemporalCapabilities() ); QVERIFY( mMemoryLayer->temporalProperties()->isActive() ); + QVERIFY( !mMemoryLayer->meshFrameSupportEditing() ); QgsProject::instance()->addMapLayers( QList() << mMemoryLayer ); @@ -152,6 +153,7 @@ void TestQgsMeshLayer::initTestCase() mMdalLayer->dataProvider()->addDataset( mDataDir + "/quad_and_triangle_els_face_scalar.dat" ); mMdalLayer->dataProvider()->addDataset( mDataDir + "/quad_and_triangle_els_face_vector.dat" ); QCOMPARE( mMdalLayer->datasetGroupTreeRootItem()->childCount(), 5 ); + QVERIFY( mMdalLayer->meshFrameSupportEditing() ); QVERIFY( mMdalLayer->isValid() ); QVERIFY( mMemoryLayer->temporalProperties()->isActive() ); @@ -227,6 +229,7 @@ void TestQgsMeshLayer::test_read_flower_mesh() QVERIFY( layer.dataProvider()->isValid() ); QCOMPARE( 8, layer.dataProvider()->vertexCount() ); QCOMPARE( 5, layer.dataProvider()->faceCount() ); + QVERIFY( layer.meshFrameSupportEditing() ); } void TestQgsMeshLayer::test_read_1d_mesh() diff --git a/tests/testdata/mesh/quad_flower_to_edit.2dm b/tests/testdata/mesh/quad_flower_to_edit.2dm new file mode 100644 index 000000000000..c7f6f293f479 --- /dev/null +++ b/tests/testdata/mesh/quad_flower_to_edit.2dm @@ -0,0 +1,14 @@ +MESH2D +ND 1 1000.000 2000.000 200.000 +ND 2 2000.000 2000.000 200.000 +ND 3 2500.000 2500.000 800.000 +ND 4 2000.000 3000.000 200.000 +ND 5 1000.000 3000.000 200.000 +ND 6 500.000 2500.000 800.000 +ND 7 1500.000 1500.000 800.000 +ND 8 1500.000 3500.000 800.000 +E4Q 1 1 2 4 5 1 +E3T 2 2 3 4 1 +E3T 3 1 5 6 1 +E3T 4 2 1 7 1 +E3T 5 5 4 8 1 diff --git a/tests/testdata/mesh/quad_flower_to_edit_expected.2dm b/tests/testdata/mesh/quad_flower_to_edit_expected.2dm new file mode 100644 index 000000000000..6cfd0975bfba --- /dev/null +++ b/tests/testdata/mesh/quad_flower_to_edit_expected.2dm @@ -0,0 +1,11 @@ +MESH2D +ND 1 2000 2000 200 +ND 2 2500 2500 800 +ND 3 2000 3000 200 +ND 4 1000 3000 200 +ND 5 1500 1500 800 +ND 6 700 1750 0 +E3T 1 1 2 3 +E3T 2 4 1 3 +E3T 3 1 4 6 +E3T 4 1 6 5 From 309454c4dee6321daae56ef65bab2030d28fb14c Mon Sep 17 00:00:00 2001 From: vcloarec Date: Tue, 29 Jun 2021 18:21:41 -0400 Subject: [PATCH 2/9] fix when invalid dataprovider --- src/core/mesh/qgsmeshlayer.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index e64bca54b76b..7fac115f2818 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -1029,24 +1029,27 @@ int QgsMeshLayer::meshVertexCount() const { if ( mMeshEditor ) return mNativeMesh->vertexCount(); - else + else if ( mDataProvider ) return mDataProvider->vertexCount(); + else return 0; } int QgsMeshLayer::meshFaceCount() const { if ( mMeshEditor ) return mNativeMesh->faceCount(); - else + else if ( mDataProvider ) return mDataProvider->faceCount(); + else return 0; } int QgsMeshLayer::meshEdgeCount() const { if ( mMeshEditor ) return mNativeMesh->edgeCount(); - else + else if ( mDataProvider ) return mDataProvider->edgeCount(); + else return 0; } void QgsMeshLayer::updateActiveDatasetGroups() From c4c61ca928e417f850c96cdaaeb8a190d210bf3b Mon Sep 17 00:00:00 2001 From: vcloarec Date: Tue, 29 Jun 2021 21:22:03 -0400 Subject: [PATCH 3/9] isModified virtual method for QgsMapLayer --- .../auto_generated/mesh/qgsmeshlayer.sip.in | 4 ++-- python/core/auto_generated/qgsmaplayer.sip.in | 7 ++++++ .../vector/qgsvectorlayer.sip.in | 1 + src/app/qgisapp.cpp | 4 ++-- src/core/layertree/qgslayertreemodel.cpp | 5 +---- src/core/layertree/qgslayertreeutils.cpp | 22 ++----------------- src/core/mesh/qgsmeshlayer.cpp | 2 +- src/core/mesh/qgsmeshlayer.h | 3 +-- src/core/qgsmaplayer.cpp | 5 +++++ src/core/qgsmaplayer.h | 6 +++++ src/core/vector/qgsvectorlayer.h | 2 +- tests/src/core/testqgsmesheditor.cpp | 4 ++-- 12 files changed, 31 insertions(+), 34 deletions(-) diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index 46bbdbb703b7..ad4b9420300a 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -765,14 +765,14 @@ Returns a pointer to the mesh editor own by the mesh layer .. versionadded:: 3.22 %End - bool isFrameModified() const; + virtual bool isModified() const; + %Docstring Returns whether the mesh fram has been modified since the last save .. versionadded:: 3.22 %End - bool contains( const QgsMesh::ElementType &type ) const; %Docstring Returns whether the mesh contains at mesh elements of given type diff --git a/python/core/auto_generated/qgsmaplayer.sip.in b/python/core/auto_generated/qgsmaplayer.sip.in index 3defad792e9d..63994d30a400 100644 --- a/python/core/auto_generated/qgsmaplayer.sip.in +++ b/python/core/auto_generated/qgsmaplayer.sip.in @@ -555,6 +555,13 @@ Set the visibility of the given sublayer name. virtual bool isEditable() const; %Docstring Returns ``True`` if the layer can be edited. +%End + + virtual bool isModified() const; +%Docstring +Return ``True`` if the layer has been modified since last commit/save, default implementation returns ``False`` + +.. versionadded:: 3.22 %End virtual bool isSpatial() const; diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in index b9fe7a1eb677..cb7a10567db1 100644 --- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1742,6 +1742,7 @@ Returns ``True`` if this is a geometry layer and ``False`` in case of NoGeometry %End virtual bool isModified() const; + %Docstring Returns ``True`` if the provider has been modified since the last commit %End diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 2aef04fc734a..39e476e9b7d5 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -11162,7 +11162,7 @@ bool QgisApp::toggleEditing( QgsMapLayer *layer, bool allowCancel ) return toggleEditingVectorLayer( qobject_cast( layer ), allowCancel ); break; case QgsMapLayerType::MeshLayer: - return toggleEditingMeshLayer( qobject_cast( layer ) ); + return toggleEditingMeshLayer( qobject_cast( layer ), allowCancel ); break; default: return false; @@ -11346,7 +11346,7 @@ bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel ) if ( !res ) QgsMessageLog::logMessage( tr( "Unable to start mesh editing for layer \"%1\"" ).arg( mlayer->name() ) ); } - else if ( mlayer->isFrameModified() ) + else if ( mlayer->isModified() ) { QMessageBox::StandardButtons buttons = QMessageBox::Save | QMessageBox::Discard; if ( allowCancel ) diff --git a/src/core/layertree/qgslayertreemodel.cpp b/src/core/layertree/qgslayertreemodel.cpp index e72cd36f81fc..5bc2484848fd 100644 --- a/src/core/layertree/qgslayertreemodel.cpp +++ b/src/core/layertree/qgslayertreemodel.cpp @@ -201,7 +201,6 @@ QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const // icons possibly overriding default icon QIcon icon; - bool isModified = false; switch ( layer->type() ) { case QgsMapLayerType::RasterLayer: @@ -210,7 +209,6 @@ QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const case QgsMapLayerType::MeshLayer: icon = QgsIconUtils::iconMesh(); - isModified = qobject_cast( layer )->isFrameModified(); break; case QgsMapLayerType::VectorTileLayer: @@ -224,7 +222,6 @@ QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const case QgsMapLayerType::VectorLayer: { QgsVectorLayer *vlayer = qobject_cast( layer ); - isModified = vlayer->isModified(); if ( vlayer->geometryType() == QgsWkbTypes::PointGeometry ) icon = QgsIconUtils::iconPoint(); else if ( vlayer->geometryType() == QgsWkbTypes::LineGeometry ) @@ -255,7 +252,7 @@ QVariant QgsLayerTreeModel::data( const QModelIndex &index, int role ) const QPixmap pixmap( icon.pixmap( iconSize, iconSize ) ); QPainter painter( &pixmap ); - painter.drawPixmap( 0, 0, iconSize, iconSize, QgsApplication::getThemePixmap( isModified ? QStringLiteral( "/mIconEditableEdits.svg" ) : QStringLiteral( "/mActionToggleEditing.svg" ) ) ); + painter.drawPixmap( 0, 0, iconSize, iconSize, QgsApplication::getThemePixmap( layer->isModified() ? QStringLiteral( "/mIconEditableEdits.svg" ) : QStringLiteral( "/mActionToggleEditing.svg" ) ) ); painter.end(); icon = QIcon( pixmap ); diff --git a/src/core/layertree/qgslayertreeutils.cpp b/src/core/layertree/qgslayertreeutils.cpp index 77d76381b315..8917980f3294 100644 --- a/src/core/layertree/qgslayertreeutils.cpp +++ b/src/core/layertree/qgslayertreeutils.cpp @@ -285,26 +285,8 @@ bool QgsLayerTreeUtils::layersModified( const QList &layerN if ( !layer ) continue; - switch ( layer->type() ) - { - case QgsMapLayerType::VectorLayer: - { - QgsVectorLayer *vl = qobject_cast( layer ); - if ( vl->isEditable() && vl->isModified() ) - return true; - } - break; - case QgsMapLayerType::MeshLayer: - { - QgsMeshLayer *ml = qobject_cast( layer ); - if ( ml->isEditable() && ml->isFrameModified() ) - return true; - } - break; - default: - continue; - break; - } + if ( layer->isEditable() && layer->isModified() ) + return true; } return false; } diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index 7fac115f2818..ee4beb4bfc4c 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -1003,7 +1003,7 @@ QgsMeshEditor *QgsMeshLayer::meshEditor() return mMeshEditor; } -bool QgsMeshLayer::isFrameModified() const +bool QgsMeshLayer::isModified() const { if ( mMeshEditor ) return mMeshEditor->isModified(); diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index 0d86d147e31b..a39ef5551f0c 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -779,8 +779,7 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer * * \since QGIS 3.22 */ - bool isFrameModified() const; - + bool isModified() const override; /** * Returns whether the mesh contains at mesh elements of given type diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index 5fe8f85c70b6..a4e30bea0d16 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -1901,6 +1901,11 @@ bool QgsMapLayer::isEditable() const return false; } +bool QgsMapLayer::isModified() const +{ + return false; +} + bool QgsMapLayer::isSpatial() const { return true; diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index 08ea4e2176c6..b025c7e3c8b0 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -554,6 +554,12 @@ class CORE_EXPORT QgsMapLayer : public QObject //! Returns TRUE if the layer can be edited. virtual bool isEditable() const; + /** + * Return TRUE if the layer has been modified since last commit/save, default implementation returns FALSE + * \since QGIS 3.22 in the base class QgsMapLayer + */ + virtual bool isModified() const; + /** * Returns TRUE if the layer is considered a spatial layer, ie it has some form of geometry associated with it. * \since QGIS 2.16 diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h index 6e6ab7baa124..2b00a7d8ca32 100644 --- a/src/core/vector/qgsvectorlayer.h +++ b/src/core/vector/qgsvectorlayer.h @@ -1663,7 +1663,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte bool isSpatial() const FINAL; //! Returns TRUE if the provider has been modified since the last commit - virtual bool isModified() const; + bool isModified() const override; /** * Returns TRUE if the field comes from the auxiliary layer, diff --git a/tests/src/core/testqgsmesheditor.cpp b/tests/src/core/testqgsmesheditor.cpp index b6c6f0a159bc..2bdf58d6abb0 100644 --- a/tests/src/core/testqgsmesheditor.cpp +++ b/tests/src/core/testqgsmesheditor.cpp @@ -127,7 +127,7 @@ void TestQgsMeshEditor::startStopEditing() QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 6 ); // roll back editing and continue editing - QVERIFY( meshLayerQuadTriangle->isFrameModified() ); + QVERIFY( meshLayerQuadTriangle->isModified() ); QVERIFY( meshLayerQuadTriangle->rollBackFrameEditing( transform, true ) ); QVERIFY( editor->mTriangularMesh->faceCentroids().count() == editor->mMesh->faceCount() ); QVERIFY( editor->mTriangularMesh->vertices().count() == editor->mMesh->vertexCount() ); @@ -151,7 +151,7 @@ void TestQgsMeshEditor::startStopEditing() QCOMPARE( meshLayerQuadTriangle->meshFaceCount(), 6 ); // roll back editing and stop editing - QVERIFY( meshLayerQuadTriangle->isFrameModified() ); + QVERIFY( meshLayerQuadTriangle->isModified() ); QVERIFY( meshLayerQuadTriangle->rollBackFrameEditing( transform, false ) ); QVERIFY( !meshLayerQuadTriangle->meshEditor() ); From 4681a8f53baf382502496c36799be4163e0e4012 Mon Sep 17 00:00:00 2001 From: vcloarec Date: Tue, 29 Jun 2021 21:23:27 -0400 Subject: [PATCH 4/9] QgsTemporaryCursorOverride --- src/app/qgisapp.cpp | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 39e476e9b7d5..9b73cf122a5a 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -11362,6 +11362,7 @@ bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel ) case QMessageBox::Save: { + QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor ); QApplication::setOverrideCursor( Qt::WaitCursor ); QgsCanvasRefreshBlocker refreshBlocker; if ( !mlayer->commitFrameEditing( transform, false ) ) @@ -11370,13 +11371,11 @@ bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel ) } mlayer->triggerRepaint(); - - QApplication::restoreOverrideCursor(); } break; case QMessageBox::Discard: { - QApplication::setOverrideCursor( Qt::WaitCursor ); + QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor ); QgsCanvasRefreshBlocker refreshBlocker; if ( !mlayer->rollBackFrameEditing( transform, false ) ) { @@ -11387,8 +11386,6 @@ bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel ) } mlayer->triggerRepaint(); - - QApplication::restoreOverrideCursor(); break; } @@ -11398,11 +11395,10 @@ bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel ) } else //mesh layer not modified { - QApplication::setOverrideCursor( Qt::WaitCursor ); + QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor ); QgsCanvasRefreshBlocker refreshBlocker; mlayer->rollBackFrameEditing( transform, false ); mlayer->triggerRepaint(); - QApplication::restoreOverrideCursor(); } if ( !res && mlayer == activeLayer() ) @@ -11463,7 +11459,7 @@ void QgisApp::saveVectorLayerEdits( QgsMapLayer *layer, bool leaveEditable, bool void QgisApp::saveMeshLayerEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint ) { QgsMeshLayer *mlayer = qobject_cast( layer ); - if ( !mlayer || !mlayer->isEditable() || !mlayer->isFrameModified() ) + if ( !mlayer || !mlayer->isEditable() || !mlayer->isModified() ) return; if ( mlayer == activeLayer() ) @@ -11682,7 +11678,7 @@ void QgisApp::updateLayerModifiedActions() case QgsMapLayerType::MeshLayer: { QgsMeshLayer *mlayer = qobject_cast( currentLayer ); - enableSaveLayerEdits = ( mlayer->isEditable() && mlayer->isFrameModified() ); + enableSaveLayerEdits = ( mlayer->isEditable() && mlayer->isModified() ); } break; default: @@ -11718,22 +11714,7 @@ QList QgisApp::editableLayers( bool modified ) const if ( !layer ) continue; - bool layerIsModified = false; - - switch ( layer->type() ) - { - case QgsMapLayerType::VectorLayer: - layerIsModified = qobject_cast( layer )->isModified(); - break; - case QgsMapLayerType::MeshLayer: - layerIsModified = qobject_cast( layer )->isFrameModified(); - break; - default: - continue; - break; - } - - if ( layer->isEditable() && ( !modified || layerIsModified ) ) + if ( layer->isEditable() && ( !modified || layer->isModified() ) ) editLayers << layer; } return editLayers; From eb5335097c4bdef2f8f491bc7873daa61d85a5f6 Mon Sep 17 00:00:00 2001 From: vcloarec Date: Tue, 29 Jun 2021 21:46:54 -0400 Subject: [PATCH 5/9] fix unreachable break --- src/app/qgisapp.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 9b73cf122a5a..4d24fffb9701 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -11160,13 +11160,10 @@ bool QgisApp::toggleEditing( QgsMapLayer *layer, bool allowCancel ) { case QgsMapLayerType::VectorLayer: return toggleEditingVectorLayer( qobject_cast( layer ), allowCancel ); - break; case QgsMapLayerType::MeshLayer: return toggleEditingMeshLayer( qobject_cast( layer ), allowCancel ); - break; default: return false; - break; } } @@ -11426,10 +11423,8 @@ void QgisApp::saveEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRep { case QgsMapLayerType::VectorLayer: return saveVectorLayerEdits( layer, leaveEditable, triggerRepaint ); - break; case QgsMapLayerType::MeshLayer: return saveMeshLayerEdits( layer, leaveEditable, triggerRepaint ); - break; default: break; } @@ -11484,10 +11479,8 @@ void QgisApp::cancelEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerR { case QgsMapLayerType::VectorLayer: return cancelVectorLayerEdits( layer, leaveEditable, triggerRepaint ); - break; case QgsMapLayerType::MeshLayer: return cancelMeshLayerEdits( layer, leaveEditable, triggerRepaint ); - break; default: break; } From 5a554b5417d044cf22025dfed3b39a250cdb367f Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 30 Jun 2021 08:37:38 -0400 Subject: [PATCH 6/9] supportsEditing as virtual method of QgsMapLayer --- .../auto_generated/mesh/qgsmeshlayer.sip.in | 11 ++--------- python/core/auto_generated/qgsmaplayer.sip.in | 19 ++++++++++++++++++- .../vector/qgsvectorlayer.sip.in | 3 ++- src/app/qgisapp.cpp | 12 +++++++----- src/core/mesh/qgsmeshlayer.cpp | 4 ++-- src/core/mesh/qgsmeshlayer.h | 10 +--------- src/core/qgsmaplayer.cpp | 5 +++++ src/core/qgsmaplayer.h | 13 +++++++++++-- src/core/vector/qgsvectorlayer.cpp | 2 +- src/core/vector/qgsvectorlayer.h | 2 +- tests/src/core/testqgsmeshlayer.cpp | 6 +++--- 11 files changed, 53 insertions(+), 34 deletions(-) diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index ad4b9420300a..6de867f629d2 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -159,6 +159,8 @@ QgsMeshLayer cannot be copied. virtual bool isEditable() const; + virtual bool supportsEditing() const; + QString providerType() const; %Docstring @@ -708,15 +710,6 @@ Returns the relative time of the dataset from the reference time of its group Returns the relative time (in milliseconds) of the dataset from the reference time of its group .. versionadded:: 3.16 -%End - - bool meshFrameSupportEditing() const; -%Docstring -Returns whether the mesh frame support editing - -:return: whether the mesh frame support editing - -.. versionadded:: 3.22 %End bool startFrameEditing( const QgsCoordinateTransform &transform ); diff --git a/python/core/auto_generated/qgsmaplayer.sip.in b/python/core/auto_generated/qgsmaplayer.sip.in index 63994d30a400..58722da14af7 100644 --- a/python/core/auto_generated/qgsmaplayer.sip.in +++ b/python/core/auto_generated/qgsmaplayer.sip.in @@ -550,6 +550,19 @@ Set the visibility of the given sublayer name. :param name: sublayer name :param visible: sublayer visibility +%End + + virtual bool supportsEditing() const; +%Docstring +Returns whether the layer supports editing or not. + +:return: ``False`` if the layer is read only or the data provider has no editing capabilities. + +.. note:: + + default implementation returns ``False``. + +.. versionadded:: 3.22 %End virtual bool isEditable() const; @@ -559,7 +572,11 @@ Returns ``True`` if the layer can be edited. virtual bool isModified() const; %Docstring -Return ``True`` if the layer has been modified since last commit/save, default implementation returns ``False`` +Return ``True`` if the layer has been modified since last commit/save. + +.. note:: + + default implementation returns ``False``. .. versionadded:: 3.22 %End diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in index cb7a10567db1..ced871df3fc9 100644 --- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1809,7 +1809,8 @@ Makes layer read-only (editing disabled) or not :return: ``False`` if the layer is in editing yet %End - bool supportsEditing(); + virtual bool supportsEditing() const; + %Docstring Returns whether the layer supports editing or not diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 4d24fffb9701..4622cb3f92f4 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -11141,9 +11141,7 @@ void QgisApp::toggleMapTips( bool enabled ) void QgisApp::toggleEditing() { QgsMapLayer *currentLayer = activeLayer(); - if ( currentLayer && - ( currentLayer->type() == QgsMapLayerType::VectorLayer || - currentLayer->type() == QgsMapLayerType::MeshLayer ) ) + if ( currentLayer && currentLayer->supportsEditing() ) { toggleEditing( currentLayer, true ); } @@ -11151,6 +11149,10 @@ void QgisApp::toggleEditing() { // active although there's no layer active!? mActionToggleEditing->setChecked( false ); + mActionToggleEditing->setEnabled( false ); + visibleMessageBar()->pushMessage( tr( "Start editing failed" ), + tr( "Layer cannot be edited" ), + Qgis::MessageLevel::Warning ); } } @@ -11328,7 +11330,7 @@ bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel ) if ( !mlayer ) return false; - if ( !mlayer->meshFrameSupportEditing() ) + if ( !mlayer->supportsEditing() ) return false; //TODO: when adapted widget will be ready, ask the user if he want to create a new one based on this one bool res = false; @@ -15616,7 +15618,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer ) mActionIdentify->setEnabled( true ); enableDigitizeTechniqueActions( false ); - bool canSupportEditing = mlayer->meshFrameSupportEditing(); + bool canSupportEditing = mlayer->supportsEditing(); bool isEditable = mlayer->isEditable(); mActionToggleEditing->setEnabled( canSupportEditing ); mActionToggleEditing->setChecked( canSupportEditing && isEditable ); diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index ee4beb4bfc4c..81617b8a3ef8 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -186,7 +186,7 @@ QString QgsMeshLayer::providerType() const return mProviderKey; } -bool QgsMeshLayer::meshFrameSupportEditing() const +bool QgsMeshLayer::supportsEditing() const { if ( !mDataProvider ) return false; @@ -890,7 +890,7 @@ qint64 QgsMeshLayer::datasetRelativeTimeInMilliseconds( const QgsMeshDatasetInde bool QgsMeshLayer::startFrameEditing( const QgsCoordinateTransform &transform ) { - if ( !meshFrameSupportEditing() ) + if ( !supportsEditing() ) { QgsMessageLog::logMessage( QObject::tr( "Mesh layer \"%1\" not support mesh editing" ).arg( name() ) ); return false; diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index a39ef5551f0c..11bbb1242c96 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -180,6 +180,7 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer QStringList subLayers() const override; QString htmlMetadata() const override; bool isEditable() const override; + bool supportsEditing() const override; //! Returns the provider type for this layer QString providerType() const; @@ -721,15 +722,6 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer */ qint64 datasetRelativeTimeInMilliseconds( const QgsMeshDatasetIndex &index ); - /** - * Returns whether the mesh frame support editing - * - * \return whether the mesh frame support editing - * - * \since QGIS 3.22 - */ - bool meshFrameSupportEditing() const; - /** * Starts edition of the mesh frame. Coordinate \a transform used to initialize the triangular mesh if needed. * This operation will disconnect the mesh layer from the data provider anf removes all existing dataset group diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index a4e30bea0d16..9e77be0826eb 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -818,6 +818,11 @@ void QgsMapLayer::setSubLayerVisibility( const QString &name, bool vis ) // NOOP } +bool QgsMapLayer::supportsEditing() const +{ + return false; +} + QgsCoordinateReferenceSystem QgsMapLayer::crs() const { return mCRS; diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index b025c7e3c8b0..e3769a83743e 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -551,12 +551,21 @@ class CORE_EXPORT QgsMapLayer : public QObject */ virtual void setSubLayerVisibility( const QString &name, bool visible ); + /** + * Returns whether the layer supports editing or not. + * \return FALSE if the layer is read only or the data provider has no editing capabilities. + * \note default implementation returns FALSE. + * \since QGIS 3.22 in the base class QgsMapLayer. + */ + virtual bool supportsEditing() const; + //! Returns TRUE if the layer can be edited. virtual bool isEditable() const; /** - * Return TRUE if the layer has been modified since last commit/save, default implementation returns FALSE - * \since QGIS 3.22 in the base class QgsMapLayer + * Return TRUE if the layer has been modified since last commit/save. + * \note default implementation returns FALSE. + * \since QGIS 3.22 in the base class QgsMapLayer. */ virtual bool isModified() const; diff --git a/src/core/vector/qgsvectorlayer.cpp b/src/core/vector/qgsvectorlayer.cpp index b9c25fc7850e..163d68998c48 100644 --- a/src/core/vector/qgsvectorlayer.cpp +++ b/src/core/vector/qgsvectorlayer.cpp @@ -3703,7 +3703,7 @@ bool QgsVectorLayer::setReadOnly( bool readonly ) return true; } -bool QgsVectorLayer::supportsEditing() +bool QgsVectorLayer::supportsEditing() const { if ( ! mDataProvider ) return false; diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h index 2b00a7d8ca32..abe31ee9ef56 100644 --- a/src/core/vector/qgsvectorlayer.h +++ b/src/core/vector/qgsvectorlayer.h @@ -1721,7 +1721,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * \return FALSE if the layer is read only or the data provider has no editing capabilities * \since QGIS 3.18 */ - bool supportsEditing(); + bool supportsEditing() const override; /** * Changes a feature's \a geometry within the layer's edit buffer diff --git a/tests/src/core/testqgsmeshlayer.cpp b/tests/src/core/testqgsmeshlayer.cpp index 8d9e338504bb..7d8be6625529 100644 --- a/tests/src/core/testqgsmeshlayer.cpp +++ b/tests/src/core/testqgsmeshlayer.cpp @@ -133,7 +133,7 @@ void TestQgsMeshLayer::initTestCase() QCOMPARE( mMemoryLayer->datasetGroupTreeRootItem()->childCount(), 5 ); QVERIFY( mMemoryLayer->dataProvider()->temporalCapabilities()->hasTemporalCapabilities() ); QVERIFY( mMemoryLayer->temporalProperties()->isActive() ); - QVERIFY( !mMemoryLayer->meshFrameSupportEditing() ); + QVERIFY( !mMemoryLayer->supportsEditing() ); QgsProject::instance()->addMapLayers( QList() << mMemoryLayer ); @@ -153,7 +153,7 @@ void TestQgsMeshLayer::initTestCase() mMdalLayer->dataProvider()->addDataset( mDataDir + "/quad_and_triangle_els_face_scalar.dat" ); mMdalLayer->dataProvider()->addDataset( mDataDir + "/quad_and_triangle_els_face_vector.dat" ); QCOMPARE( mMdalLayer->datasetGroupTreeRootItem()->childCount(), 5 ); - QVERIFY( mMdalLayer->meshFrameSupportEditing() ); + QVERIFY( mMdalLayer->supportsEditing() ); QVERIFY( mMdalLayer->isValid() ); QVERIFY( mMemoryLayer->temporalProperties()->isActive() ); @@ -229,7 +229,7 @@ void TestQgsMeshLayer::test_read_flower_mesh() QVERIFY( layer.dataProvider()->isValid() ); QCOMPARE( 8, layer.dataProvider()->vertexCount() ); QCOMPARE( 5, layer.dataProvider()->faceCount() ); - QVERIFY( layer.meshFrameSupportEditing() ); + QVERIFY( layer.supportsEditing() ); } void TestQgsMeshLayer::test_read_1d_mesh() From 90758037b204d90f11be7cb5534b43b56b3cacf5 Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 30 Jun 2021 19:51:32 -0400 Subject: [PATCH 7/9] move editing signals to QgsMapLayer and minor changes --- src/app/qgisapp.cpp | 46 +++++++++++++++--------- src/core/layertree/qgslayertreemodel.cpp | 6 ++-- src/core/mesh/qgsmeshlayer.cpp | 10 +++--- src/core/mesh/qgsmeshlayer.h | 33 ++++------------- src/core/qgsmaplayer.h | 24 +++++++++++-- src/core/vector/qgsvectorlayer.h | 9 ----- 6 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 4622cb3f92f4..9b05bdfc7f66 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -11164,9 +11164,14 @@ bool QgisApp::toggleEditing( QgsMapLayer *layer, bool allowCancel ) return toggleEditingVectorLayer( qobject_cast( layer ), allowCancel ); case QgsMapLayerType::MeshLayer: return toggleEditingMeshLayer( qobject_cast( layer ), allowCancel ); - default: - return false; + case QgsMapLayerType::RasterLayer: + case QgsMapLayerType::PluginLayer: + case QgsMapLayerType::VectorTileLayer: + case QgsMapLayerType::AnnotationLayer: + case QgsMapLayerType::PointCloudLayer: + break; } + return false; } bool QgisApp::toggleEditingVectorLayer( QgsVectorLayer *vlayer, bool allowCancel ) @@ -11362,7 +11367,6 @@ bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel ) case QMessageBox::Save: { QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor ); - QApplication::setOverrideCursor( Qt::WaitCursor ); QgsCanvasRefreshBlocker refreshBlocker; if ( !mlayer->commitFrameEditing( transform, false ) ) { @@ -11427,7 +11431,11 @@ void QgisApp::saveEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRep return saveVectorLayerEdits( layer, leaveEditable, triggerRepaint ); case QgsMapLayerType::MeshLayer: return saveMeshLayerEdits( layer, leaveEditable, triggerRepaint ); - default: + case QgsMapLayerType::RasterLayer: + case QgsMapLayerType::PluginLayer: + case QgsMapLayerType::VectorTileLayer: + case QgsMapLayerType::AnnotationLayer: + case QgsMapLayerType::PointCloudLayer: break; } } @@ -11483,7 +11491,11 @@ void QgisApp::cancelEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerR return cancelVectorLayerEdits( layer, leaveEditable, triggerRepaint ); case QgsMapLayerType::MeshLayer: return cancelMeshLayerEdits( layer, leaveEditable, triggerRepaint ); - default: + case QgsMapLayerType::RasterLayer: + case QgsMapLayerType::PluginLayer: + case QgsMapLayerType::VectorTileLayer: + case QgsMapLayerType::AnnotationLayer: + case QgsMapLayerType::PointCloudLayer: break; } } @@ -11661,8 +11673,7 @@ void QgisApp::updateLayerModifiedActions() case QgsMapLayerType::VectorLayer: { QgsVectorLayer *vlayer = qobject_cast( currentLayer ); - QgsVectorDataProvider *dprovider = vlayer->dataProvider(); - if ( dprovider ) + if ( QgsVectorDataProvider *dprovider = vlayer->dataProvider() ) { enableSaveLayerEdits = ( dprovider->capabilities() & QgsVectorDataProvider::ChangeAttributeValues && vlayer->isEditable() @@ -11676,7 +11687,11 @@ void QgisApp::updateLayerModifiedActions() enableSaveLayerEdits = ( mlayer->isEditable() && mlayer->isModified() ); } break; - default: + case QgsMapLayerType::RasterLayer: + case QgsMapLayerType::PluginLayer: + case QgsMapLayerType::VectorTileLayer: + case QgsMapLayerType::AnnotationLayer: + case QgsMapLayerType::PointCloudLayer: break; } } @@ -14603,8 +14618,7 @@ void QgisApp::layersWereAdded( const QList &layers ) { QgsDataProvider *provider = nullptr; - QgsVectorLayer *vlayer = qobject_cast( layer ); - if ( vlayer ) + if ( QgsVectorLayer *vlayer = qobject_cast( layer ) ) { // notify user about any font family substitution, but only when rendering labels (i.e. not when opening settings dialog) connect( vlayer, &QgsVectorLayer::labelingFontNotFound, this, &QgisApp::labelingFontNotFound ); @@ -14623,8 +14637,7 @@ void QgisApp::layersWereAdded( const QList &layers ) provider = vProvider; } - QgsRasterLayer *rlayer = qobject_cast( layer ); - if ( rlayer ) + if ( QgsRasterLayer *rlayer = qobject_cast( layer ) ) { // connect up any request the raster may make to update the statusbar message connect( rlayer, &QgsRasterLayer::statusChanged, this, &QgisApp::showStatusMessage ); @@ -14632,12 +14645,11 @@ void QgisApp::layersWereAdded( const QList &layers ) provider = rlayer->dataProvider(); } - QgsMeshLayer *mlayer = qobject_cast( layer ); - if ( mlayer ) + if ( QgsMeshLayer *mlayer = qobject_cast( layer ) ) { - connect( mlayer, &QgsMeshLayer::frameModified, this, &QgisApp::updateLayerModifiedActions ); - connect( mlayer, &QgsMeshLayer::frameEditingStarted, this, &QgisApp::layerEditStateChanged ); - connect( mlayer, &QgsMeshLayer::frameEditingStopped, this, &QgisApp::layerEditStateChanged ); + connect( mlayer, &QgsMeshLayer::layerModified, this, &QgisApp::updateLayerModifiedActions ); + connect( mlayer, &QgsMeshLayer::editingStarted, this, &QgisApp::layerEditStateChanged ); + connect( mlayer, &QgsMeshLayer::editingStopped, this, &QgisApp::layerEditStateChanged ); provider = mlayer->dataProvider(); } diff --git a/src/core/layertree/qgslayertreemodel.cpp b/src/core/layertree/qgslayertreemodel.cpp index 5bc2484848fd..28271e3b63f1 100644 --- a/src/core/layertree/qgslayertreemodel.cpp +++ b/src/core/layertree/qgslayertreemodel.cpp @@ -944,9 +944,9 @@ void QgsLayerTreeModel::connectToLayer( QgsLayerTreeLayer *nodeLayer ) if ( layer->type() == QgsMapLayerType::MeshLayer ) { QgsMeshLayer *ml = qobject_cast< QgsMeshLayer * >( layer ); - connect( ml, &QgsMeshLayer::frameEditingStarted, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); - connect( ml, &QgsMeshLayer::frameEditingStopped, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); - connect( ml, &QgsMeshLayer::frameModified, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); + connect( ml, &QgsMeshLayer::editingStarted, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); + connect( ml, &QgsMeshLayer::editingStopped, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); + connect( ml, &QgsMeshLayer::layerModified, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); } emit dataChanged( node2index( nodeLayer ), node2index( nodeLayer ) ); diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index 81617b8a3ef8..a61a5c51158b 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -705,7 +705,7 @@ void QgsMeshLayer::onDatasetGroupsAdded( const QList &datasetGroupIndexes ) void QgsMeshLayer::onMeshEdited() { mRendererCache.reset( new QgsMeshLayerRendererCache() ); - emit frameModified(); + emit layerModified(); triggerRepaint(); } @@ -927,7 +927,7 @@ bool QgsMeshLayer::startFrameEditing( const QgsCoordinateTransform &transform ) connect( mMeshEditor, &QgsMeshEditor::meshEdited, this, &QgsMeshLayer::onMeshEdited ); - emit frameEditingStarted(); + emit editingStarted(); return true; } @@ -944,13 +944,13 @@ bool QgsMeshLayer::commitFrameEditing( const QgsCoordinateTransform &transform, if ( continueEditing ) { mMeshEditor->initialize(); - emit frameModified(); + emit layerModified(); return res; } mMeshEditor->deleteLater(); mMeshEditor = nullptr; - emit frameEditingStopped(); + emit editingStopped(); mDataProvider->reloadData(); mDataProvider->populateMesh( mNativeMesh.get() ); @@ -979,7 +979,7 @@ bool QgsMeshLayer::rollBackFrameEditing( const QgsCoordinateTransform &transform { mMeshEditor->deleteLater(); mMeshEditor = nullptr; - emit frameEditingStopped(); + emit editingStopped(); mDatasetGroupStore.reset( new QgsMeshDatasetGroupStore( this ) ); mDatasetGroupStore->setPersistentProvider( mDataProvider, QStringList() ); diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index 11bbb1242c96..f1cd401cea92 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -731,21 +731,21 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer bool startFrameEditing( const QgsCoordinateTransform &transform ); /** - * Commit edition of the mesh frame, + * Commits edition of the mesh frame, * Rebuilds the triangular mesh and its spatial index with \a transform, * Continue editing with the same mesh editor if \a continueEditing is True * - * \return whether the commit succeeds + * \return TRUE if the commit succeeds * \since QGIS 3.22 */ bool commitFrameEditing( const QgsCoordinateTransform &transform, bool continueEditing = true ); /** - * Roll Back edition of the mesh frame. + * Rolls Back edition of the mesh frame. * Reload mesh from file, rebuilds the triangular mesh and its spatial index with \a transform, - * Continue editing with the same mesh editor if \a continueEditing is True + * Continue editing with the same mesh editor if \a continueEditing is TRUE * - * \return whether the commit succeeds + * \return TRUE if the rollback succeeds * \since QGIS 3.22 */ bool rollBackFrameEditing( const QgsCoordinateTransform &transform, bool continueEditing = true ); @@ -767,7 +767,7 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer QgsMeshEditor *meshEditor(); /** - * Returns whether the mesh fram has been modified since the last save + * Returns whether the mesh frame has been modified since the last save * * \since QGIS 3.22 */ @@ -832,27 +832,6 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer */ void timeSettingsChanged( ); - /** - * Emitted when the mesh frame is modified - * - * \since QGIS 3.22 - */ - void frameModified(); - - /** - * Emitted when the mesh frame editing is started - * - * \since QGIS 3.22 - */ - void frameEditingStarted(); - - /** - * Emitted when the mesh frame editing is stopped - * - * \since QGIS 3.22 - */ - void frameEditingStopped(); - private: // Private methods /** diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index e3769a83743e..7e02738b15bf 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -553,7 +553,7 @@ class CORE_EXPORT QgsMapLayer : public QObject /** * Returns whether the layer supports editing or not. - * \return FALSE if the layer is read only or the data provider has no editing capabilities. + * \returns FALSE if the layer is read only or the data provider has no editing capabilities. * \note default implementation returns FALSE. * \since QGIS 3.22 in the base class QgsMapLayer. */ @@ -563,9 +563,9 @@ class CORE_EXPORT QgsMapLayer : public QObject virtual bool isEditable() const; /** - * Return TRUE if the layer has been modified since last commit/save. + * Returns TRUE if the layer has been modified since last commit/save. * \note default implementation returns FALSE. - * \since QGIS 3.22 in the base class QgsMapLayer. + * \since QGIS 3.22 in the base class QgsMapLayer. */ virtual bool isModified() const; @@ -1545,6 +1545,24 @@ class CORE_EXPORT QgsMapLayer : public QObject */ void customPropertyChanged( const QString &key ); + /** + * Emitted when editing on this layer has started. + * \since QGIS 3.22 in the QgsMapLayer base class + */ + void editingStarted(); + + /** + * Emitted when edited changes have been successfully written to the data provider. + * \since QGIS 3.22 in the QgsMapLayer base class + */ + void editingStopped(); + + /** + * Emitted when modifications has been done on layer + * \since QGIS 3.22 in the QgsMapLayer base class + */ + void layerModified(); + private slots: void onNotified( const QString &message ); diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h index abe31ee9ef56..5ed2b758dde4 100644 --- a/src/core/vector/qgsvectorlayer.h +++ b/src/core/vector/qgsvectorlayer.h @@ -2551,9 +2551,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte */ void selectionChanged( const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect ); - //! Emitted when modifications has been done on layer - void layerModified(); - /** * Emitted whenever the allowCommitChanged() property of this layer changes. * @@ -2567,12 +2564,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte //! Emitted before editing on this layer is started. void beforeEditingStarted(); - //! Emitted when editing on this layer has started. - void editingStarted(); - - //! Emitted when edited changes have been successfully written to the data provider. - void editingStopped(); - /** * Emitted before changes are committed to the data provider. * From 3ac945c80c72848e3510acc725a711c35079ee9e Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 30 Jun 2021 19:57:16 -0400 Subject: [PATCH 8/9] SIP --- .../auto_generated/mesh/qgsmeshlayer.sip.in | 33 ++++--------------- python/core/auto_generated/qgsmaplayer.sip.in | 23 ++++++++++++- .../vector/qgsvectorlayer.sip.in | 15 --------- 3 files changed, 28 insertions(+), 43 deletions(-) diff --git a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in index 6de867f629d2..a29f9d0a6677 100644 --- a/python/core/auto_generated/mesh/qgsmeshlayer.sip.in +++ b/python/core/auto_generated/mesh/qgsmeshlayer.sip.in @@ -722,22 +722,22 @@ This operation will disconnect the mesh layer from the data provider anf removes bool commitFrameEditing( const QgsCoordinateTransform &transform, bool continueEditing = true ); %Docstring -Commit edition of the mesh frame, +Commits edition of the mesh frame, Rebuilds the triangular mesh and its spatial index with ``transform``, Continue editing with the same mesh editor if ``continueEditing`` is True -:return: whether the commit succeeds +:return: ``True`` if the commit succeeds .. versionadded:: 3.22 %End bool rollBackFrameEditing( const QgsCoordinateTransform &transform, bool continueEditing = true ); %Docstring -Roll Back edition of the mesh frame. +Rolls Back edition of the mesh frame. Reload mesh from file, rebuilds the triangular mesh and its spatial index with ``transform``, -Continue editing with the same mesh editor if ``continueEditing`` is True +Continue editing with the same mesh editor if ``continueEditing`` is ``True`` -:return: whether the commit succeeds +:return: ``True`` if the rollback succeeds .. versionadded:: 3.22 %End @@ -761,7 +761,7 @@ Returns a pointer to the mesh editor own by the mesh layer virtual bool isModified() const; %Docstring -Returns whether the mesh fram has been modified since the last save +Returns whether the mesh frame has been modified since the last save .. versionadded:: 3.22 %End @@ -825,27 +825,6 @@ Emitted when active vector group dataset is changed Emitted when time format is changed .. versionadded:: 3.8 -%End - - void frameModified(); -%Docstring -Emitted when the mesh frame is modified - -.. versionadded:: 3.22 -%End - - void frameEditingStarted(); -%Docstring -Emitted when the mesh frame editing is started - -.. versionadded:: 3.22 -%End - - void frameEditingStopped(); -%Docstring -Emitted when the mesh frame editing is stopped - -.. versionadded:: 3.22 %End private: // Private methods diff --git a/python/core/auto_generated/qgsmaplayer.sip.in b/python/core/auto_generated/qgsmaplayer.sip.in index 58722da14af7..42c7685244b7 100644 --- a/python/core/auto_generated/qgsmaplayer.sip.in +++ b/python/core/auto_generated/qgsmaplayer.sip.in @@ -572,7 +572,7 @@ Returns ``True`` if the layer can be edited. virtual bool isModified() const; %Docstring -Return ``True`` if the layer has been modified since last commit/save. +Returns ``True`` if the layer has been modified since last commit/save. .. note:: @@ -1748,6 +1748,27 @@ Emitted when the validity of this layer changed. Emitted when a custom property of the layer has been changed or removed. .. versionadded:: 3.18 +%End + + void editingStarted(); +%Docstring +Emitted when editing on this layer has started. + +.. versionadded:: 3.22 +%End + + void editingStopped(); +%Docstring +Emitted when edited changes have been successfully written to the data provider. + +.. versionadded:: 3.22 +%End + + void layerModified(); +%Docstring +Emitted when modifications has been done on layer + +.. versionadded:: 3.22 %End protected: diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in index ced871df3fc9..21d7d664421c 100644 --- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -2729,11 +2729,6 @@ Emitted when selection was changed :param selected: Newly selected feature ids :param deselected: Ids of all features which have previously been selected but are not any more :param clearAndSelect: In case this is set to ``True``, the old selection was dismissed and the new selection corresponds to selected -%End - - void layerModified(); -%Docstring -Emitted when modifications has been done on layer %End void allowCommitChanged(); @@ -2751,16 +2746,6 @@ Emitted when the layer is checked for modifications. Use for last-minute additio void beforeEditingStarted(); %Docstring Emitted before editing on this layer is started. -%End - - void editingStarted(); -%Docstring -Emitted when editing on this layer has started. -%End - - void editingStopped(); -%Docstring -Emitted when edited changes have been successfully written to the data provider. %End void beforeCommitChanges( bool stopEditing ); From d395199374909d4e9676bf80534855596d0b5b5f Mon Sep 17 00:00:00 2001 From: vcloarec Date: Wed, 30 Jun 2021 21:59:37 -0400 Subject: [PATCH 9/9] simplified --- src/app/qgisapp.cpp | 22 ++++------------------ src/core/layertree/qgslayertreemodel.cpp | 24 ++++++------------------ 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 9b05bdfc7f66..e866e1f3fefb 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -14616,44 +14616,30 @@ void QgisApp::layersWereAdded( const QList &layers ) const auto constLayers = layers; for ( QgsMapLayer *layer : constLayers ) { - QgsDataProvider *provider = nullptr; + connect( layer, &QgsMeshLayer::layerModified, this, &QgisApp::updateLayerModifiedActions ); + connect( layer, &QgsMeshLayer::editingStarted, this, &QgisApp::layerEditStateChanged ); + connect( layer, &QgsMeshLayer::editingStopped, this, &QgisApp::layerEditStateChanged ); if ( QgsVectorLayer *vlayer = qobject_cast( layer ) ) { // notify user about any font family substitution, but only when rendering labels (i.e. not when opening settings dialog) connect( vlayer, &QgsVectorLayer::labelingFontNotFound, this, &QgisApp::labelingFontNotFound ); - QgsVectorDataProvider *vProvider = vlayer->dataProvider(); // Do not check for layer editing capabilities because they may change // (for example when subsetString is added/removed) and signals need to // be in place in order to update the GUI - connect( vlayer, &QgsVectorLayer::layerModified, this, &QgisApp::updateLayerModifiedActions ); - connect( vlayer, &QgsVectorLayer::editingStarted, this, &QgisApp::layerEditStateChanged ); - connect( vlayer, &QgsVectorLayer::editingStopped, this, &QgisApp::layerEditStateChanged ); connect( vlayer, &QgsVectorLayer::readOnlyChanged, this, &QgisApp::layerEditStateChanged ); connect( vlayer, &QgsVectorLayer::raiseError, this, &QgisApp::onLayerError ); connect( vlayer, &QgsVectorLayer::styleLoaded, [this, vlayer]( QgsMapLayer::StyleCategories categories ) { vectorLayerStyleLoaded( vlayer, categories ); } ); - - provider = vProvider; } if ( QgsRasterLayer *rlayer = qobject_cast( layer ) ) { // connect up any request the raster may make to update the statusbar message connect( rlayer, &QgsRasterLayer::statusChanged, this, &QgisApp::showStatusMessage ); - - provider = rlayer->dataProvider(); - } - - if ( QgsMeshLayer *mlayer = qobject_cast( layer ) ) - { - connect( mlayer, &QgsMeshLayer::layerModified, this, &QgisApp::updateLayerModifiedActions ); - connect( mlayer, &QgsMeshLayer::editingStarted, this, &QgisApp::layerEditStateChanged ); - connect( mlayer, &QgsMeshLayer::editingStopped, this, &QgisApp::layerEditStateChanged ); - provider = mlayer->dataProvider(); } - if ( provider ) + if ( QgsDataProvider *provider = layer->dataProvider() ) { connect( provider, &QgsDataProvider::dataChanged, layer, [layer] { layer->triggerRepaint(); } ); connect( provider, &QgsDataProvider::dataChanged, this, [this] { refreshMapCanvas(); } ); diff --git a/src/core/layertree/qgslayertreemodel.cpp b/src/core/layertree/qgslayertreemodel.cpp index 28271e3b63f1..b9c8c93d5fde 100644 --- a/src/core/layertree/qgslayertreemodel.cpp +++ b/src/core/layertree/qgslayertreemodel.cpp @@ -930,24 +930,12 @@ void QgsLayerTreeModel::connectToLayer( QgsLayerTreeLayer *nodeLayer ) connect( layer, &QgsMapLayer::legendChanged, this, &QgsLayerTreeModel::layerLegendChanged, Qt::UniqueConnection ); connect( layer, &QgsMapLayer::flagsChanged, this, &QgsLayerTreeModel::layerFlagsChanged, Qt::UniqueConnection ); - if ( layer->type() == QgsMapLayerType::VectorLayer ) - { - // using unique connection because there may be temporarily more nodes for a layer than just one - // which would create multiple connections, however disconnect() would disconnect all multiple connections - // even if we wanted to disconnect just one connection in each call. - QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ); - connect( vl, &QgsVectorLayer::editingStarted, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); - connect( vl, &QgsVectorLayer::editingStopped, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); - connect( vl, &QgsVectorLayer::layerModified, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); - } - - if ( layer->type() == QgsMapLayerType::MeshLayer ) - { - QgsMeshLayer *ml = qobject_cast< QgsMeshLayer * >( layer ); - connect( ml, &QgsMeshLayer::editingStarted, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); - connect( ml, &QgsMeshLayer::editingStopped, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); - connect( ml, &QgsMeshLayer::layerModified, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); - } + // using unique connection because there may be temporarily more nodes for a layer than just one + // which would create multiple connections, however disconnect() would disconnect all multiple connections + // even if we wanted to disconnect just one connection in each call. + connect( layer, &QgsMeshLayer::editingStarted, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); + connect( layer, &QgsMeshLayer::editingStopped, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); + connect( layer, &QgsMeshLayer::layerModified, this, &QgsLayerTreeModel::layerNeedsUpdate, Qt::UniqueConnection ); emit dataChanged( node2index( nodeLayer ), node2index( nodeLayer ) ); }