Skip to content

Commit 0da0e7d

Browse files
author
wonder
committed
[FEATURE] Added undo/redo functionality for vector layer editing.
There are undo/redo actions in Edit menu, in Advanced digitizing toolbar and there is a new dock widget displaying undo stack of active layer. git-svn-id: http://svn.osgeo.org/qgis/trunk/qgis@10921 c8812cc2-4d05-0410-92ff-de0c093fc19c
1 parent 9156371 commit 0da0e7d

23 files changed

+383
-13
lines changed

images/themes/default/mActionRedo.png

511 Bytes
Loading

images/themes/default/mActionUndo.png

502 Bytes
Loading

src/app/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ SET(QGIS_APP_SRCS
6060
qgsshortcutsmanager.cpp
6161
qgssinglesymboldialog.cpp
6262
qgssnappingdialog.cpp
63+
qgsundowidget.cpp
6364
qgsuniquevaluedialog.cpp
6465
qgsvectorlayerproperties.cpp
6566

@@ -148,6 +149,7 @@ SET (QGIS_APP_MOC_HDRS
148149
qgsvectorlayerproperties.h
149150
qgsdbtablemodel.h
150151
qgsspatialitetablemodel.h
152+
qgsundowidget.h
151153

152154
composer/qgscomposer.h
153155
composer/qgscomposeritemwidget.h

src/app/attributetable/qgsattributetablememorymodel.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ bool QgsAttributeTableMemoryModel::setData( const QModelIndex &index, const QVar
120120
// QgsDebugMsg(mFeatureMap[rowToId(index.row())].id());
121121
mFeatureMap[rowToId( index.row() )].changeAttribute( mAttributes[ index.column()], value );
122122
// propagate back to the layer
123+
mLayer->beginEditCommand( tr("Attribute changed") );
123124
mLayer->changeAttributeValue( rowToId( index.row() ), mAttributes[ index.column()], value, true );
125+
mLayer->endEditCommand();
124126
}
125127

126128
if ( !mLayer->isModified() )

src/app/attributetable/qgsattributetablemodel.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,9 @@ bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &
393393
{
394394
mLastRowId = rowToId( index.row() );
395395
mLastRow = ( QgsAttributeMap * )( &( mFeat.attributeMap() ) );
396-
396+
mLayer->beginEditCommand( tr("Attribute changed") );
397397
mLayer->changeAttributeValue( rowToId( index.row() ), mAttributes[ index.column()], value, true );
398+
mLayer->endEditCommand();
398399
}
399400

400401
if ( !mLayer->isModified() )

src/app/qgisapp.cpp

+60-7
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
#include "qgsrenderer.h"
133133
#include "qgsserversourceselect.h"
134134
#include "qgsshortcutsmanager.h"
135+
#include "qgsundowidget.h"
135136
#include "qgsvectordataprovider.h"
136137
#include "qgsvectorlayer.h"
137138
#include "ogr/qgsopenvectorlayerdialog.h"
@@ -363,6 +364,11 @@ QgisApp::QgisApp( QSplashScreen *splash, QWidget * parent, Qt::WFlags fl )
363364
mInternalClipboard = new QgsClipboard; // create clipboard
364365
mQgisInterface = new QgisAppInterface( this ); // create the interfce
365366

367+
// create undo widget
368+
mUndoWidget = new QgsUndoWidget( NULL, mMapCanvas);
369+
addDockWidget(Qt::LeftDockWidgetArea, mUndoWidget);
370+
mUndoWidget->hide();
371+
366372
// set application's icon
367373
setWindowIcon( QPixmap( qgis_xpm ) );
368374

@@ -583,11 +589,6 @@ void QgisApp::createActions()
583589
// Edit Menu Items
584590

585591
#if 0
586-
mActionUndo = new QAction( tr( "&Undo" ), this );
587-
shortcuts->registerAction( mActionUndo, tr( "Ctrl+Z" ) );
588-
mActionUndo->setStatusTip( tr( "Undo the last operation" ) );
589-
connect( mActionUndo, SIGNAL( triggered ), this, SLOT( undo() ) );
590-
591592
mActionCut = new QAction( tr( "Cu&t" ), this );
592593
shortcuts->registerAction( mActionCut, tr( "Ctrl+X" ) );
593594
mActionCut->setStatusTip( tr( "Cut the current selection's contents to the clipboard" ) );
@@ -604,6 +605,18 @@ void QgisApp::createActions()
604605
connect( mActionPaste, SIGNAL( triggered ), this, SLOT( paste() ) );
605606
#endif
606607

608+
mActionUndo = new QAction( getThemeIcon( "mActionUndo.png"), tr( "&Undo" ), this );
609+
shortcuts->registerAction( mActionUndo, tr( "Ctrl+Z" ) );
610+
mActionUndo->setStatusTip( tr( "Undo the last operation" ) );
611+
mActionUndo->setEnabled( false );
612+
// action connected to mUndoWidget::undo slot in setupConnections()
613+
614+
mActionRedo = new QAction( getThemeIcon( "mActionRedo.png"), tr( "&Redo" ), this );
615+
shortcuts->registerAction( mActionRedo, tr( "Ctrl+Shift+Z" ) );
616+
mActionRedo->setStatusTip( tr( "Redo the last operation" ) );
617+
mActionRedo->setEnabled( false );
618+
// action connected to mUndoWidget::redo slot in setupConnections()
619+
607620
mActionCutFeatures = new QAction( getThemeIcon( "mActionEditCut.png" ), tr( "Cut Features" ), this );
608621
shortcuts->registerAction( mActionCutFeatures, tr( "Ctrl+X" ) );
609622
mActionCutFeatures->setStatusTip( tr( "Cut selected features" ) );
@@ -1132,11 +1145,14 @@ void QgisApp::createMenus()
11321145
mEditMenu = menuBar()->addMenu( tr( "&Edit" ) );
11331146

11341147
#if 0
1135-
mEditMenu->addAction( mActionUndo );
11361148
mEditMenu->addAction( mActionCut );
11371149
mEditMenu->addAction( mActionCopy );
11381150
mEditMenu->addAction( mActionPaste );
11391151
#endif
1152+
mEditMenu->addAction( mActionUndo );
1153+
mEditMenu->addAction( mActionRedo );
1154+
mActionEditSeparator0 = mEditMenu->addSeparator();
1155+
11401156
mEditMenu->addAction( mActionCutFeatures );
11411157
mEditMenu->addAction( mActionCopyFeatures );
11421158
mEditMenu->addAction( mActionPasteFeatures );
@@ -1360,6 +1376,8 @@ void QgisApp::createToolBars()
13601376
mAdvancedDigitizeToolBar = addToolBar( tr( "Advanced Digitizing" ) );
13611377
mAdvancedDigitizeToolBar->setIconSize( myIconSize );
13621378
mAdvancedDigitizeToolBar->setObjectName( "Advanced Digitizing" );
1379+
mAdvancedDigitizeToolBar->addAction( mActionUndo );
1380+
mAdvancedDigitizeToolBar->addAction( mActionRedo );
13631381
mAdvancedDigitizeToolBar->addAction( mActionSimplifyFeature );
13641382
mAdvancedDigitizeToolBar->addAction( mActionAddRing );
13651383
mAdvancedDigitizeToolBar->addAction( mActionAddIsland );
@@ -1638,6 +1656,8 @@ void QgisApp::setupConnections()
16381656
mMapLegend, SLOT( addLayer( QgsMapLayer * ) ) );
16391657
connect( mMapLegend, SIGNAL( currentLayerChanged( QgsMapLayer* ) ),
16401658
this, SLOT( activateDeactivateLayerRelatedActions( QgsMapLayer* ) ) );
1659+
connect( mMapLegend, SIGNAL( currentLayerChanged( QgsMapLayer* ) ),
1660+
mUndoWidget, SLOT( layerChanged( QgsMapLayer* ) ) );
16411661

16421662

16431663
//signal when mouse moved over window (coords display in status bar)
@@ -1664,7 +1684,12 @@ void QgisApp::setupConnections()
16641684

16651685
connect( QgsProject::instance(), SIGNAL( layerLoaded( int, int ) ), this, SLOT( showProgress( int, int ) ) );
16661686

1687+
// setup undo/redo actions
1688+
connect( mActionUndo, SIGNAL( triggered() ), mUndoWidget, SLOT( undo() ) );
1689+
connect( mActionRedo, SIGNAL( triggered() ), mUndoWidget, SLOT( redo() ) );
1690+
connect( mUndoWidget, SIGNAL( undoStackChanged() ), this, SLOT(updateUndoActions()) );
16671691
}
1692+
16681693
void QgisApp::createCanvas()
16691694
{
16701695
// "theMapCanvas" used to find this canonical instance later
@@ -4083,13 +4108,14 @@ void QgisApp::deleteSelected()
40834108
return;
40844109
}
40854110

4086-
4111+
vlayer->beginEditCommand( tr("Features deleted") );
40874112
if ( !vlayer->deleteSelectedFeatures() )
40884113
{
40894114
QMessageBox::information( this, tr( "Problem deleting features" ),
40904115
tr( "A problem occured during deletion of features" ) );
40914116
}
40924117

4118+
vlayer->endEditCommand();
40934119
// notify the project we've made a change
40944120
QgsProject::instance()->dirty( true );
40954121
}
@@ -4228,6 +4254,8 @@ void QgisApp::mergeSelectedFeatures()
42284254
}
42294255
}
42304256

4257+
vl->beginEditCommand( tr("Merged features") );
4258+
42314259
//create new feature
42324260
QgsFeature newFeature;
42334261
newFeature.setGeometry(unionGeom);
@@ -4241,6 +4269,8 @@ void QgisApp::mergeSelectedFeatures()
42414269

42424270
vl->addFeature(newFeature, false);
42434271

4272+
vl->endEditCommand();;
4273+
42444274
if(mapCanvas())
42454275
{
42464276
mapCanvas()->refresh();
@@ -4361,7 +4391,9 @@ void QgisApp::editCut( QgsMapLayer * layerContainingSelection )
43614391
{
43624392
QgsFeatureList features = selectionVectorLayer->selectedFeatures();
43634393
clipboard()->replaceWithCopyOf( selectionVectorLayer->dataProvider()->fields(), features );
4394+
selectionVectorLayer->beginEditCommand( tr("Features cut") );
43644395
selectionVectorLayer->deleteSelectedFeatures();
4396+
selectionVectorLayer->endEditCommand();
43654397
}
43664398
}
43674399
}
@@ -4410,7 +4442,9 @@ void QgisApp::editPaste( QgsMapLayer * destinationLayer )
44104442

44114443
if ( pasteVectorLayer != 0 )
44124444
{
4445+
pasteVectorLayer->beginEditCommand( tr("Features pasted") );
44134446
pasteVectorLayer->addFeatures( clipboard()->copyOf() );
4447+
pasteVectorLayer->endEditCommand();
44144448
mMapCanvas->refresh();
44154449
}
44164450
}
@@ -5420,6 +5454,8 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
54205454
mActionLayerProperties->setEnabled( false );
54215455
mActionAddToOverview->setEnabled( false );
54225456
mActionCopyFeatures->setEnabled( false );
5457+
mActionUndo->setEnabled( false );
5458+
mActionRedo->setEnabled( false );
54235459
return;
54245460
}
54255461

@@ -6139,3 +6175,20 @@ QPixmap QgisApp::getThemePixmap( const QString theName )
61396175
return QPixmap( myDefaultPath );
61406176
}
61416177
}
6178+
6179+
void QgisApp::updateUndoActions()
6180+
{
6181+
bool canUndo = false, canRedo = false;
6182+
QgsMapLayer* layer = this->activeLayer();
6183+
if (layer)
6184+
{
6185+
QgsVectorLayer* vlayer = dynamic_cast<QgsVectorLayer*>( layer );
6186+
if ( vlayer && vlayer->isEditable() )
6187+
{
6188+
canUndo = vlayer->undoStack()->canUndo();
6189+
canRedo = vlayer->undoStack()->canRedo();
6190+
}
6191+
}
6192+
mActionUndo->setEnabled( canUndo );
6193+
mActionRedo->setEnabled( canRedo );
6194+
}

src/app/qgisapp.h

+8
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class QgsPythonDialog;
5656
class QgsPythonUtils;
5757
class QgsRasterLayer;
5858
class QgsRectangle;
59+
class QgsUndoWidget;
5960
class QgsVectorLayer;
6061

6162
#include <QMainWindow>
@@ -341,6 +342,8 @@ class QgisApp : public QMainWindow
341342
//! Zoom to selected features
342343
void zoomToSelected();
343344

345+
void updateUndoActions();
346+
344347
//! cuts selected features on the active layer to the clipboard
345348
/**
346349
\param layerContainingSelection The layer that the selection will be taken from
@@ -710,6 +713,9 @@ class QgisApp : public QMainWindow
710713
QAction *mActionFileSeparator4;
711714
QAction *mActionExit;
712715

716+
QAction *mActionUndo;
717+
QAction *mActionRedo;
718+
QAction *mActionEditSeparator0;
713719
QAction *mActionCutFeatures;
714720
QAction *mActionCopyFeatures;
715721
QAction *mActionPasteFeatures;
@@ -958,6 +964,8 @@ class QgisApp : public QMainWindow
958964

959965
static QgisApp *smInstance;
960966

967+
QgsUndoWidget* mUndoWidget;
968+
961969
};
962970

963971
#endif

src/app/qgsmaptooladdfeature.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ void QgsMapToolAddFeature::canvasReleaseEvent( QMouseEvent * e )
459459
bool isDisabledAttributeValuesDlg = settings.value( "/qgis/digitizing/disable_enter_attribute_values_dialog", false ).toBool();
460460
if ( isDisabledAttributeValuesDlg )
461461
{
462+
vlayer->beginEditCommand( tr("Feature added") );
462463
if ( vlayer->addFeature( *f ) )
463464
{
464465
//add points to other features to keep topology up-to-date
@@ -468,12 +469,14 @@ void QgsMapToolAddFeature::canvasReleaseEvent( QMouseEvent * e )
468469
vlayer->addTopologicalPoints( f->geometry() );
469470
}
470471
}
472+
vlayer->endEditCommand();
471473
}
472474
else
473475
{
474476
QgsAttributeDialog * mypDialog = new QgsAttributeDialog( vlayer, f );
475477
if ( mypDialog->exec() )
476478
{
479+
vlayer->beginEditCommand( tr("Feature added") );
477480
if ( vlayer->addFeature( *f ) )
478481
{
479482
//add points to other features to keep topology up-to-date
@@ -483,6 +486,7 @@ void QgsMapToolAddFeature::canvasReleaseEvent( QMouseEvent * e )
483486
vlayer->addTopologicalPoints( f->geometry() );
484487
}
485488
}
489+
vlayer->endEditCommand();
486490
}
487491
delete mypDialog;
488492
}

src/app/qgsmaptooladdisland.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,9 @@ void QgsMapToolAddIsland::canvasReleaseEvent( QMouseEvent * e )
101101

102102
//close polygon
103103
mCaptureList.push_back( *mCaptureList.begin() );
104-
104+
vlayer->beginEditCommand( tr("Island added") );
105105
int errorCode = vlayer->addIsland( mCaptureList );
106+
vlayer->endEditCommand();
106107
QString errorMessage;
107108

108109
if ( errorCode != 0 )

src/app/qgsmaptooladdring.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ void QgsMapToolAddRing::canvasReleaseEvent( QMouseEvent * e )
8181
//close polygon
8282
mCaptureList.push_back( *mCaptureList.begin() );
8383

84+
vlayer->beginEditCommand( tr("Ring added") );
8485
int addRingReturnCode = vlayer->addRing( mCaptureList );
86+
vlayer->endEditCommand();
8587
if ( addRingReturnCode != 0 )
8688
{
8789
QString errorMessage;

src/app/qgsmaptooladdvertex.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,12 @@ void QgsMapToolAddVertex::canvasReleaseEvent( QMouseEvent * e )
122122

123123
//and change the feature points
124124
QList<QgsSnappingResult>::iterator sr_it = mRecentSnappingResults.begin();
125+
vlayer->beginEditCommand( tr("Added vertex") );
125126
for ( ; sr_it != mRecentSnappingResults.end(); ++sr_it )
126127
{
127128
vlayer->insertVertex( snappedPointLayerCoord.x(), snappedPointLayerCoord.y(), sr_it->snappedAtGeometry, sr_it->afterVertexNr );
128129
}
130+
vlayer->endEditCommand();
129131
}
130132
}
131133

src/app/qgsmaptooldeletepart.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ void QgsMapToolDeletePart::deletePart( int fId, int beforeVertexNr, QgsVectorLay
106106

107107
if ( g->deletePart( partNum ) )
108108
{
109+
vlayer->beginEditCommand( tr("Part of multipart feature deleted") );
109110
vlayer->changeGeometry( fId, g );
111+
vlayer->endEditCommand();
110112
mCanvas->refresh();
111113
}
112114
else

src/app/qgsmaptooldeletering.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ void QgsMapToolDeleteRing::deleteRing( int fId, int beforeVertexNr, QgsVectorLay
111111

112112
if ( g->deleteRing( ringNum, partNum ) )
113113
{
114+
vlayer->beginEditCommand( tr("Ring deleted") );
114115
vlayer->changeGeometry( fId, g );
116+
vlayer->endEditCommand();
115117
mCanvas->refresh();
116118
}
117119

src/app/qgsmaptooldeletevertex.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,12 @@ void QgsMapToolDeleteVertex::canvasReleaseEvent( QMouseEvent * e )
7979
if ( vlayer && mRecentSnappingResults.size() > 0 )
8080
{
8181
QList<QgsSnappingResult>::iterator sr_it = mRecentSnappingResults.begin();
82+
vlayer->beginEditCommand( tr("Vertex deleted") );
8283
for ( ; sr_it != mRecentSnappingResults.end(); ++sr_it )
8384
{
8485
vlayer->deleteVertex( sr_it->snappedAtGeometry, sr_it->snappedVertexNr );
8586
}
87+
vlayer->endEditCommand();
8688
}
8789

8890
mCanvas->refresh();

src/app/qgsmaptoolidentify.cpp

+9-1
Original file line numberDiff line numberDiff line change
@@ -489,17 +489,25 @@ void QgsMapToolIdentify::editFeature( QgsFeature &f )
489489

490490
QgsAttributeMap src = f.attributeMap();
491491

492+
layer->beginEditCommand( tr("Attribute changed") );
492493
QgsAttributeDialog *ad = new QgsAttributeDialog( layer, &f );
493494
if ( ad->exec() )
494495
{
495496
const QgsAttributeMap &dst = f.attributeMap();
496-
497497
for ( QgsAttributeMap::const_iterator it = dst.begin(); it != dst.end(); it++ )
498498
{
499499
if ( !src.contains( it.key() ) || it.value() != src[it.key()] )
500+
{
500501
layer->changeAttributeValue( f.id(), it.key(), it.value() );
502+
}
501503
}
504+
layer->endEditCommand();
502505
}
506+
else
507+
{
508+
layer->destroyEditCommand();
509+
}
510+
503511
delete ad;
504512
mCanvas->refresh();
505513
}

src/app/qgsmaptoolmovefeature.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,12 @@ void QgsMapToolMoveFeature::canvasReleaseEvent( QMouseEvent * e )
137137

138138
double dx = stopPointLayerCoords.x() - startPointLayerCoords.x();
139139
double dy = stopPointLayerCoords.y() - startPointLayerCoords.y();
140-
140+
vlayer->beginEditCommand( tr("Feature moved") );
141141
vlayer->translateFeature( mMovedFeature, dx, dy );
142-
143142
delete mRubberBand;
144143
mRubberBand = 0;
145144
mCanvas->refresh();
145+
vlayer->endEditCommand();
146146
}
147147

148148
//! called when map tool is being deactivated

0 commit comments

Comments
 (0)