mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
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:
parent
0a3adf80b7
commit
7fd5ba816a
@ -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 */
|
||||
|
@ -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() */
|
||||
|
@ -51,6 +51,7 @@ SET(QGIS_CORE_SRCS
|
||||
qgsvectordataprovider.cpp
|
||||
qgsvectorfilewriter.cpp
|
||||
qgsvectorlayer.cpp
|
||||
qgsvectorlayerundocommand.cpp
|
||||
qgsvectoroverlay.cpp
|
||||
|
||||
composer/qgscomposeritem.cpp
|
||||
|
@ -715,3 +715,11 @@ QString QgsMapLayer::saveNamedStyle( const QString theURI, bool & theResultFlag
|
||||
|
||||
return myErrorMessage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QUndoStack* QgsMapLayer::undoStack()
|
||||
{
|
||||
return &mUndoStack;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
116
src/core/qgsvectorlayerundocommand.cpp
Normal file
116
src/core/qgsvectorlayerundocommand.cpp
Normal 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);
|
||||
}
|
147
src/core/qgsvectorlayerundocommand.h
Normal file
147
src/core/qgsvectorlayerundocommand.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user