[QGIS Commit] r10920 - in trunk/qgis: python/core src/core

svn_qgis at osgeo.org svn_qgis at osgeo.org
Sun Jun 14 10:17:19 EDT 2009


Author: wonder
Date: 2009-06-14 10:17:18 -0400 (Sun, 14 Jun 2009)
New Revision: 10920

Added:
   trunk/qgis/src/core/qgsvectorlayerundocommand.cpp
   trunk/qgis/src/core/qgsvectorlayerundocommand.h
Modified:
   trunk/qgis/python/core/qgsmaplayer.sip
   trunk/qgis/python/core/qgsvectorlayer.sip
   trunk/qgis/src/core/CMakeLists.txt
   trunk/qgis/src/core/qgsmaplayer.cpp
   trunk/qgis/src/core/qgsmaplayer.h
   trunk/qgis/src/core/qgsvectorlayer.cpp
   trunk/qgis/src/core/qgsvectorlayer.h
Log:
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.


Modified: trunk/qgis/python/core/qgsmaplayer.sip
===================================================================
--- trunk/qgis/python/core/qgsmaplayer.sip	2009-06-14 07:31:56 UTC (rev 10919)
+++ trunk/qgis/python/core/qgsmaplayer.sip	2009-06-14 14:17:18 UTC (rev 10920)
@@ -249,6 +249,9 @@
      */
     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 */

Modified: trunk/qgis/python/core/qgsvectorlayer.sip
===================================================================
--- trunk/qgis/python/core/qgsvectorlayer.sip	2009-06-14 07:31:56 UTC (rev 10919)
+++ trunk/qgis/python/core/qgsvectorlayer.sip	2009-06-14 14:17:18 UTC (rev 10920)
@@ -389,6 +389,25 @@
     */
     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() */

Modified: trunk/qgis/src/core/CMakeLists.txt
===================================================================
--- trunk/qgis/src/core/CMakeLists.txt	2009-06-14 07:31:56 UTC (rev 10919)
+++ trunk/qgis/src/core/CMakeLists.txt	2009-06-14 14:17:18 UTC (rev 10920)
@@ -51,6 +51,7 @@
   qgsvectordataprovider.cpp
   qgsvectorfilewriter.cpp
   qgsvectorlayer.cpp
+  qgsvectorlayerundocommand.cpp
   qgsvectoroverlay.cpp
 
   composer/qgscomposeritem.cpp

Modified: trunk/qgis/src/core/qgsmaplayer.cpp
===================================================================
--- trunk/qgis/src/core/qgsmaplayer.cpp	2009-06-14 07:31:56 UTC (rev 10919)
+++ trunk/qgis/src/core/qgsmaplayer.cpp	2009-06-14 14:17:18 UTC (rev 10920)
@@ -715,3 +715,11 @@
 
   return myErrorMessage;
 }
+
+
+
+
+QUndoStack* QgsMapLayer::undoStack()
+{
+  return &mUndoStack;
+}

Modified: trunk/qgis/src/core/qgsmaplayer.h
===================================================================
--- trunk/qgis/src/core/qgsmaplayer.h	2009-06-14 07:31:56 UTC (rev 10919)
+++ trunk/qgis/src/core/qgsmaplayer.h	2009-06-14 14:17:18 UTC (rev 10920)
@@ -23,6 +23,7 @@
 #include <map>
 
 #include <QObject>
+#include <QUndoStack>
 
 #include "qgsrectangle.h"
 
@@ -259,6 +260,9 @@
      */
     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 @@
     /** A flag that tells us whether to use the above vars to restrict layer visibility */
     bool mScaleBasedVisibility;
 
+    QUndoStack mUndoStack;
+
 };
 
 #endif

Modified: trunk/qgis/src/core/qgsvectorlayer.cpp
===================================================================
--- trunk/qgis/src/core/qgsvectorlayer.cpp	2009-06-14 07:31:56 UTC (rev 10919)
+++ trunk/qgis/src/core/qgsvectorlayer.cpp	2009-06-14 14:17:18 UTC (rev 10920)
@@ -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 @@
     mLabel( 0 ),
     mLabelOn( false ),
     mVertexMarkerOnlyForSelection(false),
-    mFetching( false )
+    mFetching( false ),
+    mActiveCommand( NULL )
 {
   mActions = new QgsAttributeAction;
 
@@ -1480,7 +1482,7 @@
   // 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 @@
 
   if ( mDataProvider )
   {
-
+    QgsGeometry geometry;
     if ( !mChangedGeometries.contains( atFeatureId ) )
     {
       // first time this geometry has changed since last commit
@@ -1511,12 +1513,17 @@
       {
         return false;
       }
-      mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId];
+      geometry = mCachedGeometries[atFeatureId];
+      //mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId];
     }
+    else
+    {
+      geometry = mChangedGeometries[atFeatureId];
+    }
+    geometry.insertVertex( x, y, beforeVertex );
+    mCachedGeometries[atFeatureId] = geometry;
+    editGeometryChange(atFeatureId, geometry);
 
-    mChangedGeometries[atFeatureId].insertVertex( x, y, beforeVertex );
-    mCachedGeometries[atFeatureId] = mChangedGeometries[atFeatureId];
-
     setModified( true, true ); // only geometry was changed
 
     return true;
@@ -1534,6 +1541,7 @@
 
   if ( mDataProvider )
   {
+    QgsGeometry geometry;
     if ( !mChangedGeometries.contains( atFeatureId ) )
     {
       // first time this geometry has changed since last commit
@@ -1541,11 +1549,17 @@
       {
         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 @@
 
   if ( mDataProvider )
   {
+    QgsGeometry geometry;
     if ( !mChangedGeometries.contains( atFeatureId ) )
     {
       // first time this geometry has changed since last commit
@@ -1571,10 +1586,16 @@
       {
         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 @@
     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 @@
   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 @@
       mCachedGeometries[selectedFeatureId] = *addedIt->geometry();
     }
   }
+  */
 
   //is the feature contained in the view extent (mCachedGeometries) ?
   QgsGeometryMap::iterator cachedIt = mCachedGeometries.find( selectedFeatureId );
@@ -1689,7 +1715,7 @@
     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 @@
       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 @@
   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 @@
       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 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 errorCode = translateGeom.translate( dx, dy );
       if ( errorCode == 0 )
       {
-        mChangedGeometries.insert( featureId, translateGeom );
+        editGeometryChange(featureId, translateGeom);
         setModified( true, true );
       }
       return errorCode;
@@ -1830,7 +1861,7 @@
     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 @@
     return false;
   }
 
-  mChangedGeometries[ fid ] = *geom;
+  editGeometryChange( fid, *geom );
   mCachedGeometries[fid] = *geom;
   setModified( true, true );
   return true;
@@ -2602,29 +2633,7 @@
   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 @@
     return false;
 
   mMaxUpdatedIndex++;
+
+  if (mActiveCommand != NULL)
+  {
+    mActiveCommand->storeAttributeAdd( mMaxUpdatedIndex, field );
+  }
+
   mUpdatedFields.insert( mMaxUpdatedIndex, field );
   mAddedAttributeIds.insert( mMaxUpdatedIndex );
 
@@ -2670,6 +2685,11 @@
        !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 @@
     return true;
 
   mSelectedFeatureIds.remove( fid ); // remove it from selection
-  mDeletedFeatureIds.insert( fid );
+  editFeatureDelete( fid );
 
   setModified( true, false );
 
@@ -2975,7 +2995,7 @@
 
     mUpdatedFields.clear();
     mMaxUpdatedIndex = -1;
-
+    undoStack()->clear();
     emit editingStopped();
   }
 
@@ -3715,3 +3735,314 @@
   }
   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 );
+    }
+  }
+
+}

Modified: trunk/qgis/src/core/qgsvectorlayer.h
===================================================================
--- trunk/qgis/src/core/qgsvectorlayer.h	2009-06-14 07:31:56 UTC (rev 10919)
+++ trunk/qgis/src/core/qgsvectorlayer.h	2009-06-14 14:17:18 UTC (rev 10920)
@@ -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 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 @@
     */
     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 @@
     /**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 @@
     QSet<int> mFetchConsidered;
     QgsGeometryMap::iterator mFetchChangedGeomIt;
     QgsFeatureList::iterator mFetchAddedFeaturesIt;
+
+    QgsUndoCommand * mActiveCommand;
 };
 
 #endif

Added: trunk/qgis/src/core/qgsvectorlayerundocommand.cpp
===================================================================
--- trunk/qgis/src/core/qgsvectorlayerundocommand.cpp	                        (rev 0)
+++ trunk/qgis/src/core/qgsvectorlayerundocommand.cpp	2009-06-14 14:17:18 UTC (rev 10920)
@@ -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);
+}

Added: trunk/qgis/src/core/qgsvectorlayerundocommand.h
===================================================================
--- trunk/qgis/src/core/qgsvectorlayerundocommand.h	                        (rev 0)
+++ trunk/qgis/src/core/qgsvectorlayerundocommand.h	2009-06-14 14:17:18 UTC (rev 10920)
@@ -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



More information about the QGIS-commit mailing list