Skip to content
Permalink
Browse files

Implement undo logic for streamed digitizing mode

When the backspace key is held down, points will be repeatedly
removed from streamed digitized sections, right up till the start
of the streaming section. Then the point removal will temporarily
pause until the user releases and re-holds down the backspace key.

This allows users to selectively remove portions of the geometry
captured with the streaming mode by holding down the undo key,
without risking accidental undo of non-streamed portions.
  • Loading branch information
nyalldawson committed Mar 11, 2021
1 parent 135dd71 commit bffa42ae74deac1558307111aaed3ea0dc7dd8b4
@@ -232,9 +232,12 @@ Variant to supply more information in the case of snapping
.. versionadded:: 2.14
%End

void undo();
void undo( bool isAutoRepeat = false );
%Docstring
Removes the last vertex from mRubberBand and mCaptureList
Removes the last vertex from mRubberBand and mCaptureList.

Since QGIS 3.20, if ``isAutoRepeat`` is set to ``True`` then the undo operation will be treated
as a auto repeated undo as if the user has held down the undo key for an extended period of time.
%End

void startCapturing();
@@ -710,7 +710,7 @@ QList<QgsPointLocator::Match> QgsMapToolCapture::snappingMatches() const
return mSnappingMatches;
}

void QgsMapToolCapture::undo()
void QgsMapToolCapture::undo( bool isAutoRepeat )
{
mTracingStartPoint = QgsPointXY();

@@ -719,6 +719,10 @@ void QgsMapToolCapture::undo()
if ( size() <= 1 && mTempRubberBand->pointsCount() != 0 )
return;

if ( isAutoRepeat && mIgnoreSubsequentAutoRepeatUndo )
return;
mIgnoreSubsequentAutoRepeatUndo = false;

QgsPoint lastPoint = mTempRubberBand->lastPoint();

if ( mTempRubberBand->stringType() == QgsWkbTypes::CircularString && mTempRubberBand->pointsCount() > 2 )
@@ -742,12 +746,22 @@ void QgsMapToolCapture::undo()
}
else
{
const int curvesBefore = mCaptureCurve.nCurves();
const bool lastCurveIsLineString = qgsgeometry_cast< QgsLineString * >( mCaptureCurve.curveAt( curvesBefore - 1 ) );

int pointsCountBefore = mCaptureCurve.numPoints();
mCaptureCurve.deleteVertex( vertexToRemove );
int pointsCountAfter = mCaptureCurve.numPoints();
for ( ; pointsCountAfter < pointsCountBefore; pointsCountAfter++ )
if ( !mSnappingMatches.empty() )
mSnappingMatches.removeLast();

// if we have removed the last point in a linestring curve, then we "stick" here and ignore subsequent
// autorepeat undo actions until the user releases the undo key and holds it down again. This allows
// users to selectively remove portions of the geometry captured with the streaming mode by holding down
// the undo key, without risking accidental undo of non-streamed portions.
if ( mCaptureCurve.nCurves() < curvesBefore && lastCurveIsLineString )
mIgnoreSubsequentAutoRepeatUndo = true;
}

updateExtraSnapLayer();
@@ -773,7 +787,7 @@ void QgsMapToolCapture::keyPressEvent( QKeyEvent *e )
{
if ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete )
{
undo();
undo( e->isAutoRepeat() );

// Override default shortcut management in MapCanvas
e->ignore();
@@ -326,8 +326,13 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
*/
int addVertex( const QgsPointXY &mapPoint, const QgsPointLocator::Match &match );

//! Removes the last vertex from mRubberBand and mCaptureList
void undo();
/**
* Removes the last vertex from mRubberBand and mCaptureList.
*
* Since QGIS 3.20, if \a isAutoRepeat is set to TRUE then the undo operation will be treated
* as a auto repeated undo as if the user has held down the undo key for an extended period of time.
*/
void undo( bool isAutoRepeat = false );

/**
* Start capturing
@@ -466,6 +471,7 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
bool mAllowAddingStreamingPoints = false;
bool mStartNewCurve = false;

bool mIgnoreSubsequentAutoRepeatUndo = false;

};

@@ -76,6 +76,7 @@ class TestQgsMapToolAddFeatureLine : public QObject
void testLineString();
void testCompoundCurve();
void testStream();
void testUndo();

private:
QgisApp *mQgisApp = nullptr;
@@ -903,5 +904,90 @@ void TestQgsMapToolAddFeatureLine::testStream()
mLayerLine->undoStack()->undo();
}

void TestQgsMapToolAddFeatureLine::testUndo()
{
// test undo logic
TestQgsMapToolAdvancedDigitizingUtils utils( mCaptureTool );

mCanvas->setCurrentLayer( mLayerLine );

QSet<QgsFeatureId> oldFids = utils.existingFeatureIds();

QgsSnappingConfig cfg = mCanvas->snappingUtils()->config();
cfg.setEnabled( false );
mCanvas->snappingUtils()->setConfig( cfg );

oldFids = utils.existingFeatureIds();
utils.mouseClick( 5, 6.5, Qt::LeftButton );
utils.mouseClick( 6.25, 6.5, Qt::LeftButton );
utils.mouseClick( 6.75, 6.5, Qt::LeftButton );

mCaptureTool->setStreamDigitizingEnabled( true );
utils.mouseMove( 7.0, 6.6 );
utils.mouseMove( 7.1, 6.7 );
utils.mouseMove( 7.2, 6.6 );
utils.mouseMove( 7.3, 6.5 );
utils.mouseMove( 7.5, 6.9 );
utils.mouseMove( 7.6, 6.3 );
utils.mouseClick( 7.75, 6.5, Qt::LeftButton );
mCaptureTool->setStreamDigitizingEnabled( false );
utils.mouseClick( 7.5, 5.0, Qt::LeftButton );
mCaptureTool->setStreamDigitizingEnabled( true );
utils.mouseMove( 7.4, 5.0 );
utils.mouseMove( 7.3, 5.1 );
utils.mouseMove( 7.2, 5.0 );
utils.mouseMove( 7.1, 4.9 );

// check capture curve initially -- the streamed sections MUST become their own curve in the geometry, and not be compined with the straight line segments!
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7, 7.2 6.59, 7.3 6.5, 7.5 6.91, 7.59 6.3),(7.59 6.3, 7.5 5),(7.5 5, 7.41 5, 7.3 5.09, 7.2 5, 7.09 4.91))" ) );

// now lets try undoing...
utils.keyClick( Qt::Key_Backspace );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7, 7.2 6.59, 7.3 6.5, 7.5 6.91, 7.59 6.3),(7.59 6.3, 7.5 5),(7.5 5, 7.41 5, 7.3 5.09, 7.2 5))" ) );
// simulate auto repeating undo from a held-down backspace key
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7, 7.2 6.59, 7.3 6.5, 7.5 6.91, 7.59 6.3),(7.59 6.3, 7.5 5),(7.5 5, 7.41 5, 7.3 5.09))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7, 7.2 6.59, 7.3 6.5, 7.5 6.91, 7.59 6.3),(7.59 6.3, 7.5 5),(7.5 5, 7.41 5))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7, 7.2 6.59, 7.3 6.5, 7.5 6.91, 7.59 6.3),(7.59 6.3, 7.5 5))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
// we've now finished undoing the streamed digitizing section, so undo should pause until the user releases the backspace key and then re-presses it
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7, 7.2 6.59, 7.3 6.5, 7.5 6.91, 7.59 6.3),(7.59 6.3, 7.5 5))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7, 7.2 6.59, 7.3 6.5, 7.5 6.91, 7.59 6.3),(7.59 6.3, 7.5 5))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), false );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7, 7.2 6.59, 7.3 6.5, 7.5 6.91, 7.59 6.3))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), false );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7, 7.2 6.59, 7.3 6.5, 7.5 6.91))" ) );
// simulate holding down another key
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7, 7.2 6.59, 7.3 6.5))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7, 7.2 6.59))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59, 7.09 6.7))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5),(6.75 6.5, 7 6.59))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
// should get "stuck" here again
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5, 6.75 6.5))" ) );
// release and repress
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), false );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5, 6.25 6.5))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );
QCOMPARE( mCaptureTool->captureCurve()->asWkt( 2 ), QStringLiteral( "CompoundCurve ((5 6.5))" ) );
utils.keyClick( Qt::Key_Backspace, Qt::KeyboardModifiers(), true );

utils.mouseClick( 7.0, 5.0, Qt::RightButton );
mCaptureTool->setStreamDigitizingEnabled( false );
}

QGSTEST_MAIN( TestQgsMapToolAddFeatureLine )
#include "testqgsmaptooladdfeatureline.moc"
@@ -97,12 +97,12 @@ class TestQgsMapToolAdvancedDigitizingUtils
mouseRelease( mapX, mapY, button, stateKey, snap );
}

void keyClick( int key, Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers() )
void keyClick( int key, Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(), bool autoRepeat = false )
{
QKeyEvent e1( QEvent::KeyPress, key, stateKey );
QKeyEvent e1( QEvent::KeyPress, key, stateKey, QString(), autoRepeat );
mMapTool->keyPressEvent( &e1 );

QKeyEvent e2( QEvent::KeyRelease, key, stateKey );
QKeyEvent e2( QEvent::KeyRelease, key, stateKey, QString(), autoRepeat );
mMapTool->keyReleaseEvent( &e2 );
}

0 comments on commit bffa42a

Please sign in to comment.