Added infrastructure for vector editing undo/redo functionality.

Note - when implementing edit tools for vector layer:
All editation should be done between beginEditCommand() and endEditCommand()
calls so that the operation are stored. 

Note - when doing changes inside QgsVectorLayer code:
When doing any changes inside QgsVectorLayer they should be done using edit*() functions
and _not_ directly e.g. mChangedGeometries[fid] = (...) otherwise the change won't be
stored in the undo stack and it would lead to invalid behaviour of undo.


git-svn-id: http://svn.osgeo.org/qgis/trunk@10920 c8812cc2-4d05-0410-92ff-de0c093fc19c
This commit is contained in:
wonder 2009-06-14 14:17:18 +00:00
parent 0a3adf80b7
commit 7fd5ba816a
9 changed files with 718 additions and 49 deletions

View File

@ -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 */

View File

@ -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() */

View File

@ -51,6 +51,7 @@ SET(QGIS_CORE_SRCS
qgsvectordataprovider.cpp
qgsvectorfilewriter.cpp
qgsvectorlayer.cpp
qgsvectorlayerundocommand.cpp
qgsvectoroverlay.cpp
composer/qgscomposeritem.cpp

View File

@ -715,3 +715,11 @@ QString QgsMapLayer::saveNamedStyle( const QString theURI, bool & theResultFlag
return myErrorMessage;
}
QUndoStack* QgsMapLayer::undoStack()
{
return &mUndoStack;
}

View File

@ -23,6 +23,7 @@
#include <map>
#include <QObject>
#include <QUndoStack>
#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

View File

@ -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<QgsPoint>& 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<QgsPoint>& 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<QgsPoint>& 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<QgsPoint>& 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<QgsPoint>& 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<QgsPoint>& 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<int, QgsUndoCommand::GeometryChangeEntry>& geometryChange = cmd->mGeometryChange;
QgsFeatureIds& deletedFeatureIdChange = cmd->mDeletedFeatureIdChange;
QgsFeatureList& addedFeatures = cmd->mAddedFeatures;
QMap<int, QgsUndoCommand::AttributeChanges>& attributeChange = cmd->mAttributeChange;
QgsFieldMap& addedAttributes = cmd->mAddedAttributes;
QgsFieldMap& deletedAttributes = cmd->mDeletedAttributes;
// geometry changes
QMap<int, QgsUndoCommand::GeometryChangeEntry>::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<int, QgsUndoCommand::AttributeChanges>::iterator attrFeatIt = attributeChange.begin();
for ( ; attrFeatIt != attributeChange.end(); ++attrFeatIt )
{
int fid = attrFeatIt.key();
// for every changed attribute in feature
QMap<int, QgsUndoCommand::AttributeChangeEntry>::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<int, QgsUndoCommand::GeometryChangeEntry>& geometryChange = cmd->mGeometryChange;
QgsFeatureIds& deletedFeatureIdChange = cmd->mDeletedFeatureIdChange;
QgsFeatureList& addedFeatures = cmd->mAddedFeatures;
QMap<int, QgsUndoCommand::AttributeChanges>& 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<int, QgsUndoCommand::GeometryChangeEntry>::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<int, QgsUndoCommand::AttributeChanges>::iterator attrFeatIt = attributeChange.begin();
for ( ; attrFeatIt != attributeChange.end(); ++attrFeatIt )
{
int fid = attrFeatIt.key();
QMap<int, QgsUndoCommand::AttributeChangeEntry>::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 );
}
}
}

View File

@ -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<int> QgsAttributeList;
typedef QSet<int> QgsFeatureIds;
typedef QSet<int> 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<int> mFetchConsidered;
QgsGeometryMap::iterator mFetchChangedGeomIt;
QgsFeatureList::iterator mFetchAddedFeaturesIt;
QgsUndoCommand * mActiveCommand;
};
#endif

View File

@ -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);
}

View File

@ -0,0 +1,147 @@
#ifndef QGSVECTORLAYERUNDOCOMMAND_H
#define QGSVECTORLAYERUNDOCOMMAND_H
#include <QUndoCommand>
#include <QVariant>
#include <QSet>
#include <QList>
#include "qgsfield.h"
#include "qgsfeature.h"
class QgsGeometry;
class QgsVectorLayer;
// TODO: copied from qgsvectorlayer.h
typedef QList<int> QgsAttributeList;
typedef QSet<int> QgsFeatureIds;
typedef QSet<int> 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<int, AttributeChangeEntry> 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<int, GeometryChangeEntry> mGeometryChange;
/** Map of changes of atrributes for features which describes changes of attributes */
QMap<int, AttributeChanges> 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