diff --git a/python/core/qgsmaplayer.sip b/python/core/qgsmaplayer.sip index b347a7a517a..ffbbe2ac507 100644 --- a/python/core/qgsmaplayer.sip +++ b/python/core/qgsmaplayer.sip @@ -249,6 +249,9 @@ public: */ virtual QString saveNamedStyle( const QString theURI, bool & theResultFlag ); + /** Return pointer to layer's undo stack */ + QUndoStack* undoStack(); + public slots: /** Event handler for when a coordinate transform fails due to bad vertex error */ diff --git a/python/core/qgsvectorlayer.sip b/python/core/qgsvectorlayer.sip index 36e0e9c49a6..580e554f105 100644 --- a/python/core/qgsvectorlayer.sip +++ b/python/core/qgsvectorlayer.sip @@ -389,6 +389,25 @@ public: */ QgsVectorOverlay* findOverlayByType( const QString& typeName ); + + /** + * Create edit command for undo/redo operations + * @param text text which is to be displayed in undo window + */ + void beginEditCommand(QString text); + + /** Finish edit command and add it to undo/redo stack */ + void endEditCommand(); + + /** Destroy active command and deletes all changes in it */ + void destroyEditCommand(); + + /** Execute undo operation. To be called only from QgsVectorLayerUndoCommand. */ + // (not necessary) void undoEditCommand(QgsUndoCommand* cmd); + + /** Execute redo operation. To be called only from QgsVectorLayerUndoCommand. */ + // (not necessary) void redoEditCommand(QgsUndoCommand* cmd); + public slots: /** Select feature by its ID, optionally emit signal selectionChanged() */ diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 96646a53722..26a296f1f94 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -51,6 +51,7 @@ SET(QGIS_CORE_SRCS qgsvectordataprovider.cpp qgsvectorfilewriter.cpp qgsvectorlayer.cpp + qgsvectorlayerundocommand.cpp qgsvectoroverlay.cpp composer/qgscomposeritem.cpp diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index ef74700cdb5..2886d18c6fb 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -715,3 +715,11 @@ QString QgsMapLayer::saveNamedStyle( const QString theURI, bool & theResultFlag return myErrorMessage; } + + + + +QUndoStack* QgsMapLayer::undoStack() +{ + return &mUndoStack; +} diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index d2e53ba97dc..e581d135814 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -23,6 +23,7 @@ #include #include +#include #include "qgsrectangle.h" @@ -259,6 +260,9 @@ class CORE_EXPORT QgsMapLayer : public QObject */ virtual bool writeSymbology( QDomNode&, QDomDocument& doc, QString& errorMessage ) const = 0; + /** Return pointer to layer's undo stack */ + QUndoStack* undoStack(); + public slots: /** Event handler for when a coordinate transform fails due to bad vertex error */ @@ -356,6 +360,8 @@ class CORE_EXPORT QgsMapLayer : public QObject /** A flag that tells us whether to use the above vars to restrict layer visibility */ bool mScaleBasedVisibility; + QUndoStack mUndoStack; + }; #endif diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index 3dbb61d63ed..539c86edb8f 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -67,6 +67,7 @@ #include "qgssinglesymbolrenderer.h" #include "qgscoordinatereferencesystem.h" #include "qgsvectordataprovider.h" +#include "qgsvectorlayerundocommand.h" #include "qgsvectoroverlay.h" #include "qgslogger.h" #include "qgsmaplayerregistry.h" @@ -102,7 +103,8 @@ QgsVectorLayer::QgsVectorLayer( QString vectorLayerPath, mLabel( 0 ), mLabelOn( false ), mVertexMarkerOnlyForSelection(false), - mFetching( false ) + mFetching( false ), + mActiveCommand( NULL ) { mActions = new QgsAttributeAction; @@ -1480,7 +1482,7 @@ bool QgsVectorLayer::addFeature( QgsFeature& f, bool alsoUpdateExtent ) // providers will use their own new feature ID when we commit the new feature) // and add to the known added features. f.setFeatureId( addedIdLowWaterMark ); - mAddedFeatures.append( f ); + editFeatureAdd( f ); mCachedGeometries[f.id()] = *f.geometry(); setModified( true ); @@ -1503,7 +1505,7 @@ bool QgsVectorLayer::insertVertex( double x, double y, int atFeatureId, int befo if ( mDataProvider ) { - + QgsGeometry geometry; if ( !mChangedGeometries.contains( atFeatureId ) ) { // first time this geometry has changed since last commit @@ -1511,11 +1513,16 @@ bool QgsVectorLayer::insertVertex( double x, double y, int atFeatureId, int befo { return false; } - mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId]; + geometry = mCachedGeometries[atFeatureId]; + //mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId]; } - - mChangedGeometries[atFeatureId].insertVertex( x, y, beforeVertex ); - mCachedGeometries[atFeatureId] = mChangedGeometries[atFeatureId]; + else + { + geometry = mChangedGeometries[atFeatureId]; + } + geometry.insertVertex( x, y, beforeVertex ); + mCachedGeometries[atFeatureId] = geometry; + editGeometryChange(atFeatureId, geometry); setModified( true, true ); // only geometry was changed @@ -1534,6 +1541,7 @@ bool QgsVectorLayer::moveVertex( double x, double y, int atFeatureId, int atVert if ( mDataProvider ) { + QgsGeometry geometry; if ( !mChangedGeometries.contains( atFeatureId ) ) { // first time this geometry has changed since last commit @@ -1541,11 +1549,17 @@ bool QgsVectorLayer::moveVertex( double x, double y, int atFeatureId, int atVert { return false; } - mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId]; + geometry = mCachedGeometries[atFeatureId]; + //mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId]; + } + else + { + geometry = mChangedGeometries[atFeatureId]; } - mChangedGeometries[atFeatureId].moveVertex( x, y, atVertex ); - mCachedGeometries[atFeatureId] = mChangedGeometries[atFeatureId]; + geometry.moveVertex( x, y, atVertex ); + mCachedGeometries[atFeatureId] = geometry; + editGeometryChange(atFeatureId, geometry); setModified( true, true ); // only geometry was changed @@ -1564,6 +1578,7 @@ bool QgsVectorLayer::deleteVertex( int atFeatureId, int atVertex ) if ( mDataProvider ) { + QgsGeometry geometry; if ( !mChangedGeometries.contains( atFeatureId ) ) { // first time this geometry has changed since last commit @@ -1571,10 +1586,16 @@ bool QgsVectorLayer::deleteVertex( int atFeatureId, int atVertex ) { return false; } - mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId]; + geometry = mCachedGeometries[atFeatureId]; + } + else + { + geometry = mChangedGeometries[atFeatureId]; } - mChangedGeometries[atFeatureId].deleteVertex( atVertex ); + geometry.deleteVertex( atVertex ); + mCachedGeometries[atFeatureId] = geometry; + editGeometryChange(atFeatureId, geometry); setModified( true, true ); // only geometry was changed @@ -1637,7 +1658,8 @@ int QgsVectorLayer::addRing( const QList& ring ) addRingReturnCode = f.geometry()->addRing( ring ); if ( addRingReturnCode == 0 ) { - mChangedGeometries.insert( f.id(), *f.geometry() ); + editGeometryChange( f.id(), *f.geometry() ); + setModified( true, true ); break; } @@ -1667,12 +1689,15 @@ int QgsVectorLayer::addIsland( const QList& ring ) QgsGeometryMap::iterator changedIt = mChangedGeometries.find( selectedFeatureId ); if ( changedIt != mChangedGeometries.end() ) { - int returnValue = changedIt->addIsland( ring ); - mCachedGeometries[selectedFeatureId] = *changedIt; + QgsGeometry geom = *changedIt; + int returnValue = geom.addIsland( ring ); + editGeometryChange( selectedFeatureId, geom ); + mCachedGeometries[selectedFeatureId] = geom; return returnValue; } //look if id of selected feature belongs to an added feature + /* for ( QgsFeatureList::iterator addedIt = mAddedFeatures.begin(); addedIt != mAddedFeatures.end(); ++addedIt ) { if ( addedIt->id() == selectedFeatureId ) @@ -1681,6 +1706,7 @@ int QgsVectorLayer::addIsland( const QList& ring ) mCachedGeometries[selectedFeatureId] = *addedIt->geometry(); } } + */ //is the feature contained in the view extent (mCachedGeometries) ? QgsGeometryMap::iterator cachedIt = mCachedGeometries.find( selectedFeatureId ); @@ -1689,7 +1715,7 @@ int QgsVectorLayer::addIsland( const QList& ring ) int errorCode = cachedIt->addIsland( ring ); if ( errorCode == 0 ) { - mChangedGeometries.insert( selectedFeatureId, *cachedIt ); + editGeometryChange( selectedFeatureId, *cachedIt ); mCachedGeometries[selectedFeatureId] = *cachedIt; setModified( true, true ); } @@ -1705,7 +1731,7 @@ int QgsVectorLayer::addIsland( const QList& ring ) if ( fGeom ) { int errorCode = fGeom->addIsland( ring ); - mChangedGeometries.insert( selectedFeatureId, *fGeom ); + editGeometryChange( selectedFeatureId, *fGeom ); setModified( true, true ); delete fGeom; return errorCode; @@ -1722,10 +1748,14 @@ int QgsVectorLayer::translateFeature( int featureId, double dx, double dy ) QgsGeometryMap::iterator changedIt = mChangedGeometries.find( featureId ); if ( changedIt != mChangedGeometries.end() ) { - return changedIt->translate( dx, dy ); + QgsGeometry geom = *changedIt; + int errorCode = geom.translate( dx, dy ); + editGeometryChange( featureId, geom ); + return errorCode; } //look if id of selected feature belongs to an added feature + /* for ( QgsFeatureList::iterator addedIt = mAddedFeatures.begin(); addedIt != mAddedFeatures.end(); ++addedIt ) { if ( addedIt->id() == featureId ) @@ -1733,6 +1763,7 @@ int QgsVectorLayer::translateFeature( int featureId, double dx, double dy ) return addedIt->geometry()->translate( dx, dy ); } } + */ //else look in mCachedGeometries to make access faster QgsGeometryMap::iterator cachedIt = mCachedGeometries.find( featureId ); @@ -1741,7 +1772,7 @@ int QgsVectorLayer::translateFeature( int featureId, double dx, double dy ) int errorCode = cachedIt->translate( dx, dy ); if ( errorCode == 0 ) { - mChangedGeometries.insert( featureId, *cachedIt ); + editGeometryChange( featureId, *cachedIt ); setModified( true, true ); } return errorCode; @@ -1757,7 +1788,7 @@ int QgsVectorLayer::translateFeature( int featureId, double dx, double dy ) int errorCode = translateGeom.translate( dx, dy ); if ( errorCode == 0 ) { - mChangedGeometries.insert( featureId, translateGeom ); + editGeometryChange(featureId, translateGeom); setModified( true, true ); } return errorCode; @@ -1830,7 +1861,7 @@ int QgsVectorLayer::splitFeatures( const QList& splitLine, bool topolo if ( splitFunctionReturn == 0 ) { //change this geometry - mChangedGeometries.insert( select_it->id(), *( select_it->geometry() ) ); + editGeometryChange( select_it->id(), *( select_it->geometry() ) ); //update of cached geometries is necessary because we use addTopologicalPoints() later mCachedGeometries[select_it->id()] = *( select_it->geometry() ); @@ -2590,7 +2621,7 @@ bool QgsVectorLayer::changeGeometry( int fid, QgsGeometry* geom ) return false; } - mChangedGeometries[ fid ] = *geom; + editGeometryChange( fid, *geom ); mCachedGeometries[fid] = *geom; setModified( true, true ); return true; @@ -2602,29 +2633,7 @@ bool QgsVectorLayer::changeAttributeValue( int fid, int field, QVariant value, b if ( !isEditable() ) return false; - if ( fid >= 0 ) - { - // changed attribute of existing feature - if ( !mChangedAttributeValues.contains( fid ) ) - { - mChangedAttributeValues.insert( fid, QgsAttributeMap() ); - } - - mChangedAttributeValues[fid].insert( field, value ); - } - else - { - // updated added feature - int i; - for ( i = 0; i < mAddedFeatures.size() && mAddedFeatures[i].id() != fid; i++ ) - ; - - if ( i == mAddedFeatures.size() ) - return false; - - mAddedFeatures[i].changeAttribute( field, value ); - } - + editAttributeChange( fid, field, value ); setModified( true, false ); if ( emitSignal ) @@ -2648,6 +2657,12 @@ bool QgsVectorLayer::addAttribute( const QgsField &field ) return false; mMaxUpdatedIndex++; + + if (mActiveCommand != NULL) + { + mActiveCommand->storeAttributeAdd( mMaxUpdatedIndex, field ); + } + mUpdatedFields.insert( mMaxUpdatedIndex, field ); mAddedAttributeIds.insert( mMaxUpdatedIndex ); @@ -2670,6 +2685,11 @@ bool QgsVectorLayer::deleteAttribute( int index ) !mDataProvider->fields().contains( index ) ) return false; + if (mActiveCommand != NULL) + { + mActiveCommand->storeAttributeDelete( index, mUpdatedFields[index] ); + } + mDeletedAttributeIds.insert( index ); mAddedAttributeIds.remove( index ); mUpdatedFields.remove( index ); @@ -2690,7 +2710,7 @@ bool QgsVectorLayer::deleteFeature( int fid ) return true; mSelectedFeatureIds.remove( fid ); // remove it from selection - mDeletedFeatureIds.insert( fid ); + editFeatureDelete( fid ); setModified( true, false ); @@ -2975,7 +2995,7 @@ bool QgsVectorLayer::commitChanges() mUpdatedFields.clear(); mMaxUpdatedIndex = -1; - + undoStack()->clear(); emit editingStopped(); } @@ -3715,3 +3735,314 @@ QgsVectorOverlay* QgsVectorLayer::findOverlayByType( const QString& typeName ) } return 0; //not found } + + +void QgsVectorLayer::editGeometryChange( int featureId, QgsGeometry& geometry ) +{ + if (mActiveCommand != NULL) + { + mActiveCommand->storeGeometryChange( featureId, mChangedGeometries[ featureId ], geometry ); + } + mChangedGeometries[ featureId ] = geometry; +} + + +void QgsVectorLayer::editFeatureAdd(QgsFeature& feature) +{ + if (mActiveCommand != NULL) + { + mActiveCommand->storeFeatureAdd( feature ); + } + mAddedFeatures.append( feature ); +} + +void QgsVectorLayer::editFeatureDelete(int featureId) +{ + if (mActiveCommand != NULL) + { + mActiveCommand->storeFeatureDelete( featureId ); + } + mDeletedFeatureIds.insert( featureId ); +} + +void QgsVectorLayer::editAttributeChange( int featureId, int field, QVariant value) +{ + if (mActiveCommand != NULL) + { + QVariant original; + bool isFirstChange = true; + if (featureId < 0) + { + // work with added feature + for (int i = 0; i < mAddedFeatures.size(); i++ ) + { + if ( mAddedFeatures[i].id() == featureId && mAddedFeatures[i].attributeMap().contains(field) ) + { + original = mAddedFeatures[i].attributeMap()[field]; + isFirstChange = false; + break; + } + } + } + else + { + if (mChangedAttributeValues.contains(featureId) && mChangedAttributeValues[featureId].contains(field)) + { + original = mChangedAttributeValues[featureId][field]; + isFirstChange = false; + } + } + mActiveCommand->storeAttributeChange( featureId, field, original, value, isFirstChange ); + } + + if ( featureId >= 0 ) + { + // changed attribute of existing feature + if ( !mChangedAttributeValues.contains( featureId ) ) + { + mChangedAttributeValues.insert( featureId, QgsAttributeMap() ); + } + + mChangedAttributeValues[featureId].insert( field, value ); + } + else + { + // updated added feature + for ( int i = 0; i < mAddedFeatures.size(); i++ ) + { + if ( mAddedFeatures[i].id() == featureId ) + { + mAddedFeatures[i].changeAttribute( field, value ); + break; + } + } + } +} + +void QgsVectorLayer::beginEditCommand(QString text) +{ + if (mActiveCommand == NULL) + { + mActiveCommand = new QgsUndoCommand(this, text); + } +} + +void QgsVectorLayer::endEditCommand() +{ + if (mActiveCommand != NULL) + { + undoStack()->push(mActiveCommand); + mActiveCommand = NULL; + } + +} + +void QgsVectorLayer::destroyEditCommand() +{ + if (mActiveCommand != NULL) + { + delete mActiveCommand; + mActiveCommand = NULL; + } + +} + +void QgsVectorLayer::redoEditCommand(QgsUndoCommand* cmd) +{ + QMap& geometryChange = cmd->mGeometryChange; + QgsFeatureIds& deletedFeatureIdChange = cmd->mDeletedFeatureIdChange; + QgsFeatureList& addedFeatures = cmd->mAddedFeatures; + QMap& attributeChange = cmd->mAttributeChange; + QgsFieldMap& addedAttributes = cmd->mAddedAttributes; + QgsFieldMap& deletedAttributes = cmd->mDeletedAttributes; + + + // geometry changes + QMap::iterator it = geometryChange.begin(); + for ( ; it != geometryChange.end(); ++it ) + { + if (it.value().target == NULL) + { + mChangedGeometries.remove(it.key()); + } + else + { + mChangedGeometries[it.key()] = *(it.value().target); + } + } + + // deleted features + QgsFeatureIds::iterator delIt = deletedFeatureIdChange.begin(); + for ( ; delIt != deletedFeatureIdChange.end(); ++delIt ) + { + mDeletedFeatureIds.insert(*delIt); + } + + // added features + QgsFeatureList::iterator addIt = addedFeatures.begin(); + for ( ; addIt != addedFeatures.end(); ++addIt ) + { + mAddedFeatures.append(*addIt); + } + + // changed attributes + QMap::iterator attrFeatIt = attributeChange.begin(); + for ( ; attrFeatIt != attributeChange.end(); ++attrFeatIt ) + { + int fid = attrFeatIt.key(); + // for every changed attribute in feature + QMap::iterator attrChIt = attrFeatIt.value().begin(); + for ( ; attrChIt != attrFeatIt.value().end(); ++attrChIt ) + { + if (fid >= 0) + { + // existing feature + if (attrChIt.value().target.isNull()) + { + mChangedAttributeValues[fid].remove(attrChIt.key()); + } + else + { + mChangedAttributeValues[fid][attrChIt.key()] = attrChIt.value().target; + QgsFeature f; + featureAtId(fid, f, false, true); + f.changeAttribute( attrChIt.key(), attrChIt.value().target ); + } + } + else + { + // added feature + for ( int i = 0; i < mAddedFeatures.size(); i++ ) + { + if (mAddedFeatures[i].id() == fid) + { + mAddedFeatures[i].changeAttribute( attrChIt.key(), attrChIt.value().target ); + break; + } + } + + } + + } + } + + // added attributes + QgsFieldMap::iterator attrIt = addedAttributes.begin(); + for ( ; attrIt != addedAttributes.end(); ++attrIt ) + { + int attrIndex = attrIt.key(); + mAddedAttributeIds.insert( attrIndex ); + mUpdatedFields.insert( attrIndex, attrIt.value() ); + } + + // deleted attributes + QgsFieldMap::iterator dAttrIt = deletedAttributes.begin(); + for ( ; dAttrIt != deletedAttributes.end(); ++dAttrIt ) + { + int attrIndex = dAttrIt.key(); + mDeletedAttributeIds.insert(attrIndex); + mUpdatedFields.remove(attrIndex); + } + +} + +void QgsVectorLayer::undoEditCommand(QgsUndoCommand* cmd) +{ + QMap& geometryChange = cmd->mGeometryChange; + QgsFeatureIds& deletedFeatureIdChange = cmd->mDeletedFeatureIdChange; + QgsFeatureList& addedFeatures = cmd->mAddedFeatures; + QMap& attributeChange = cmd->mAttributeChange; + QgsFieldMap& addedAttributes = cmd->mAddedAttributes; + QgsFieldMap& deletedAttributes = cmd->mDeletedAttributes; + + // deleted attributes + QgsFieldMap::iterator dAttrIt = deletedAttributes.begin(); + for ( ; dAttrIt != deletedAttributes.end(); ++dAttrIt ) + { + int attrIndex = dAttrIt.key(); + mDeletedAttributeIds.remove( attrIndex ); + mUpdatedFields.insert( attrIndex, dAttrIt.value() ); + } + + // added attributes + QgsFieldMap::iterator attrIt = addedAttributes.begin(); + for ( ; attrIt != addedAttributes.end(); ++attrIt ) + { + int attrIndex = attrIt.key(); + mAddedAttributeIds.remove( attrIndex ); + mUpdatedFields.remove( attrIndex ); + } + + // geometry changes + QMap::iterator it = geometryChange.begin(); + for ( ; it != geometryChange.end(); ++it ) + { + if (it.value().original == NULL) + { + mChangedGeometries.remove(it.key()); + } + else + { + mChangedGeometries[it.key()] = *(it.value().original); + } + } + + // deleted features + QgsFeatureIds::iterator delIt = deletedFeatureIdChange.begin(); + for ( ; delIt != deletedFeatureIdChange.end(); ++delIt ) + { + mDeletedFeatureIds.remove(*delIt); + } + + // added features + QgsFeatureList::iterator addIt = addedFeatures.begin(); + for ( ; addIt != addedFeatures.end(); ++addIt ) + { + QgsFeatureList::iterator addedIt = mAddedFeatures.begin(); + for ( ; addedIt != mAddedFeatures.end(); ++addedIt ) + { + if (addedIt->id() == addIt->id()) + { + mAddedFeatures.erase(addedIt); + break; // feature was found so move to next one + } + } + } + + // updated attributes + QMap::iterator attrFeatIt = attributeChange.begin(); + for ( ; attrFeatIt != attributeChange.end(); ++attrFeatIt ) + { + int fid = attrFeatIt.key(); + QMap::iterator attrChIt = attrFeatIt.value().begin(); + for ( ; attrChIt != attrFeatIt.value().end(); ++attrChIt ) + { + if (fid >= 0) + { + if (attrChIt.value().isFirstChange) + { + mChangedAttributeValues[fid].remove(attrChIt.key()); + } + else + { + mChangedAttributeValues[fid][attrChIt.key()] = attrChIt.value().original; + } + } + else + { + // added feature TODO: + for ( int i = 0; i < mAddedFeatures.size(); i++ ) + { + if ( mAddedFeatures[i].id() == fid ) + { + mAddedFeatures[i].changeAttribute( attrChIt.key(), attrChIt.value().original ); + break; + } + } + + } + emit attributeValueChanged( fid, attrChIt.key(), attrChIt.value().original ); + } + } + +} diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index 964757250f0..50240ab13b6 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -28,7 +28,6 @@ #include "qgsmaplayer.h" #include "qgsfeature.h" #include "qgssnapper.h" -#include "qgsfeature.h" #include "qgsfield.h" class QPainter; @@ -42,16 +41,21 @@ class QgsMapToPixel; class QgsLabel; class QgsRectangle; class QgsRenderer; +class QgsUndoCommand; class QgsVectorDataProvider; class QgsVectorOverlay; -class QgsGeometry; class QgsRectangle; + typedef QList QgsAttributeList; typedef QSet QgsFeatureIds; typedef QSet QgsAttributeIds; + + + + /** \ingroup core * Vector layer backed by a data source provider. */ @@ -453,6 +457,25 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer */ QgsVectorOverlay* findOverlayByType( const QString& typeName ); + + /** + * Create edit command for undo/redo operations + * @param text text which is to be displayed in undo window + */ + void beginEditCommand(QString text); + + /** Finish edit command and add it to undo/redo stack */ + void endEditCommand(); + + /** Destroy active command and deletes all changes in it */ + void destroyEditCommand(); + + /** Execute undo operation. To be called only from QgsVectorLayerUndoCommand. */ + void undoEditCommand(QgsUndoCommand* cmd); + + /** Execute redo operation. To be called only from QgsVectorLayerUndoCommand. */ + void redoEditCommand(QgsUndoCommand* cmd); + public slots: /** Select feature by its ID, optionally emit signal selectionChanged() */ void select( int featureId, bool emitSignal = TRUE ); @@ -577,6 +600,19 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer /**Update feature with uncommited geometry updates*/ void updateFeatureGeometry( QgsFeature &f ); + /** Record changed geometry, store in active command (if any) */ + void editGeometryChange( int featureId, QgsGeometry& geometry ); + + /** Record added feature, store in active command (if any) */ + void editFeatureAdd(QgsFeature& feature); + + /** Record deleted feature, store in active command (if any) */ + void editFeatureDelete(int featureId); + + /** Record changed attribute, store in active command (if any) */ + void editAttributeChange( int featureId, int field, QVariant value ); + + private: // Private attributes /** Update threshold for drawing features as they are read. A value of zero indicates @@ -678,6 +714,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer QSet mFetchConsidered; QgsGeometryMap::iterator mFetchChangedGeomIt; QgsFeatureList::iterator mFetchAddedFeaturesIt; + + QgsUndoCommand * mActiveCommand; }; #endif diff --git a/src/core/qgsvectorlayerundocommand.cpp b/src/core/qgsvectorlayerundocommand.cpp new file mode 100644 index 00000000000..6f055802cde --- /dev/null +++ b/src/core/qgsvectorlayerundocommand.cpp @@ -0,0 +1,116 @@ + +#include "qgsvectorlayerundocommand.h" + +#include "qgsgeometry.h" +#include "qgsvectorlayer.h" + +#include "qgslogger.h" + +QgsUndoCommand::GeometryChangeEntry::GeometryChangeEntry() + : original(NULL), target(NULL) +{ +} + +void QgsUndoCommand::GeometryChangeEntry::setOriginalGeometry(QgsGeometry& orig) +{ + if (orig.type() != QGis::UnknownGeometry) + { + original = new QgsGeometry(orig); + } + else + { + original = NULL; + } +} + +void QgsUndoCommand::GeometryChangeEntry::setTargetGeometry(QgsGeometry& dest) +{ + if (dest.type() != QGis::UnknownGeometry) + { + target = new QgsGeometry(dest); + } + else + { + target = NULL; + } +} + + +QgsUndoCommand::GeometryChangeEntry::~GeometryChangeEntry() +{ + delete original; + delete target; +} + + +QgsUndoCommand::QgsUndoCommand(QgsVectorLayer* vlayer, QString text) + : QUndoCommand() +{ + setText(text); + mLayer = vlayer; + mFirstRun = true; +} + +void QgsUndoCommand::redo() +{ + // when the command is added to the undo stack, the redo() function is called + // but we have already done the changes in the layer, so we ignore the first redo() call + if (mFirstRun) + { + mFirstRun = false; + return; + } + + mLayer->redoEditCommand(this); +} + +void QgsUndoCommand::undo() +{ + mLayer->undoEditCommand(this); +} + + +void QgsUndoCommand::storeGeometryChange(int featureId, QgsGeometry& original, QgsGeometry& target) +{ + if ( mGeometryChange.contains( featureId ) ) + { + // geometry has been modified already: just alter the resulting geometry + mGeometryChange[featureId].setTargetGeometry(target); + } + else + { + // create new entry about changed geometry + mGeometryChange.insert( featureId, GeometryChangeEntry() ); + mGeometryChange[featureId].setOriginalGeometry(original); + mGeometryChange[featureId].setTargetGeometry(target); + } +} + +void QgsUndoCommand::storeAttributeChange(int featureId, int field, QVariant original, QVariant target, bool isFirstChange) +{ + AttributeChangeEntry entry; + entry.isFirstChange = isFirstChange; + entry.original = original; + entry.target = target; + mAttributeChange[featureId].insert(field, entry); +} + +void QgsUndoCommand::storeAttributeAdd(int index, const QgsField & value) +{ + mAddedAttributes.insert( index, value ); +} + +void QgsUndoCommand::storeAttributeDelete(int index, const QgsField & orig) +{ + mDeletedAttributes.insert(index, orig ); +} + +void QgsUndoCommand::storeFeatureDelete(int featureId) +{ + mDeletedFeatureIdChange.insert(featureId); +} + +void QgsUndoCommand::storeFeatureAdd(QgsFeature& feature) +{ + mAddedFeatures.append(feature); +} diff --git a/src/core/qgsvectorlayerundocommand.h b/src/core/qgsvectorlayerundocommand.h new file mode 100644 index 00000000000..d111036b315 --- /dev/null +++ b/src/core/qgsvectorlayerundocommand.h @@ -0,0 +1,147 @@ + +#ifndef QGSVECTORLAYERUNDOCOMMAND_H +#define QGSVECTORLAYERUNDOCOMMAND_H + +#include + +#include +#include +#include + +#include "qgsfield.h" +#include "qgsfeature.h" + +class QgsGeometry; +class QgsVectorLayer; + + +// TODO: copied from qgsvectorlayer.h +typedef QList QgsAttributeList; +typedef QSet QgsFeatureIds; +typedef QSet QgsAttributeIds; + + + +/** + * Class to support universal undo command sequence for application, basic for + */ +class QgsUndoCommand : public QUndoCommand +{ + public: + + /** change structure for attribute for undo/redo purpose */ + class AttributeChangeEntry + { + public: + bool isFirstChange; + QVariant original; + QVariant target; + }; + + typedef QMap AttributeChanges; + + /** change structure to geometry for undo/redo purpose */ + class GeometryChangeEntry + { + public: + GeometryChangeEntry(); + ~GeometryChangeEntry(); + + void setOriginalGeometry(QgsGeometry& orig); + void setTargetGeometry(QgsGeometry& target); + + QgsGeometry* original; + QgsGeometry* target; + }; + + + QgsUndoCommand(QgsVectorLayer* layer, QString text); + + /** + * Necessary function to provide undo operation + */ + void undo(); + + /** + * Necessary function to provide redo operation + */ + void redo(); + + /** + * Function to store changes in geometry to be returned to this state after undo/redo + * @param featureId id of feature edited + * @param original original geometry of feature which was changed + * @param target changed geometry which was changed + */ + void storeGeometryChange(int featureId, QgsGeometry& original, QgsGeometry& target); + + /** + * Stores changes of attributes for the feature to be returned to this state after undo/redo + * @param featureId id of feature for which this chaged is stored + * @param field field identifier of field which was changed + * @param original original value of attribute before change + * @param target target value of attribute after change + * @param isFirstChange flag if this change is the first one + */ + void storeAttributeChange(int featureId, int field, QVariant original, QVariant target, bool isFirstChange); + + /** + * Add id of feature to deleted list to be reverted if needed afterwards + * @param featureId id of feature which is to be deleted + */ + void storeFeatureDelete(int featureId); + + /** + * Add new feature to list of new features to be stored for undo/redo operations. + * @param feature feature which is to be added + */ + void storeFeatureAdd(QgsFeature& feature); + + /** + * Add new attribute to list of attributes to be used for attributes of features for undo/redo operations. + * @param index index of attribute which is to be added + * @param value field description which is to be stored + */ + void storeAttributeAdd(int index, const QgsField & value); + + /** + * Add deleted attribute which is to be stored for undo/redo operations. + * @param index index od attribute definition which is to be deleted + * @param orig deleted field's description + */ + void storeAttributeDelete(int index, const QgsField & orig); + + private: + /** Variable to disable first run of undo, because it's automaticaly done after push */ + bool mFirstRun; + + /** Layer on which operations should be performed */ + QgsVectorLayer* mLayer; + + /** Map of changes of geometry for features it describes changes of geometry */ + QMap mGeometryChange; + + /** Map of changes of atrributes for features which describes changes of attributes */ + QMap mAttributeChange; + + /** Deleted feature IDs which are not commited. Note a feature can be added and then deleted + again before the change is committed - in that case the added feature would be removed + from mAddedFeatures only and *not* entered here. + */ + QgsFeatureIds mDeletedFeatureIdChange; + + /** added attributes fields which are not commited */ + QgsFieldMap mAddedAttributes; + + /** deleted attributes fields which are not commited */ + QgsFieldMap mDeletedAttributes; + + /** New features which are not commited. Note a feature can be added and then changed, + therefore the details here can be overridden by mChangedAttributeValues and mChangedGeometries. + */ + QgsFeatureList mAddedFeatures; + + friend class QgsVectorLayer; +}; + +#endif