1 /***************************************************************************
2      testqgsvertextool.cpp
3      ----------------------
4     Date                 : 2017-03-01
5     Copyright            : (C) 2017 by Martin Dobias
6     Email                : wonder dot sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgstest.h"
17 
18 #include "qgsadvanceddigitizingdockwidget.h"
19 #include "qgsgeometry.h"
20 #include "qgsgeos.h"
21 #include "qgsmapcanvas.h"
22 #include "qgsmapcanvassnappingutils.h"
23 #include "qgsproject.h"
24 #include "qgsvectorlayer.h"
25 #include "qgsmapmouseevent.h"
26 #include "vertextool/qgsvertextool.h"
27 #include "qgslinestring.h"
28 #include "qgscircularstring.h"
29 #include "qgssnappingconfig.h"
30 #include "qgssettingsregistrycore.h"
31 #include "testqgsmaptoolutils.h"
32 
operator ==(const QgsGeometry & g1,const QgsGeometry & g2)33 bool operator==( const QgsGeometry &g1, const QgsGeometry &g2 )
34 {
35   if ( g1.isNull() && g2.isNull() )
36     return true;
37   else
38     return g1.isGeosEqual( g2 );
39 }
40 
41 namespace QTest
42 {
43   // pretty printing of geometries in comparison tests
toString(const QgsGeometry & geom)44   template<> char *toString( const QgsGeometry &geom )
45   {
46     QByteArray ba = geom.asWkt().toLatin1();
47     return qstrdup( ba.data() );
48   }
49 }
50 
51 /**
52  * \ingroup UnitTests
53  * This is a unit test for the vertex tool
54  */
55 class TestQgsVertexTool : public QObject
56 {
57     Q_OBJECT
58   public:
59     TestQgsVertexTool();
60 
61   private slots:
62     void initTestCase();// will be called before the first testfunction is executed.
63     void cleanupTestCase();// will be called after the last testfunction was executed.
64 
65     void testSelectVerticesByPolygon();
66     void testTopologicalEditingMoveVertexZ();
67     void testTopologicalEditingMoveVertexOnSegmentZ();
68     void testTopologicalEditingMoveVertexOnIntersectionZ();
69     void testMoveVertex();
70     void testMoveEdge();
71     void testAddVertex();
72     void testAddVertexAtEndpoint();
73     void testAddVertexDoubleClick();
74     void testAddVertexDoubleClickWithShift();
75     void testAvoidIntersections();
76     void testDeleteVertex();
77     void testConvertVertex();
78     void testMoveMultipleVertices();
79     void testMoveMultipleVertices2();
80     void testMoveVertexTopo();
81     void testDeleteVertexTopo();
82     void testAddVertexTopo();
83     void testMoveEdgeTopo();
84     void testAddVertexTopoFirstSegment();
85     void testActiveLayerPriority();
86     void testSelectedFeaturesPriority();
87     void testVertexToolCompoundCurve();
88 
89 
90   private:
mapToScreen(double mapX,double mapY)91     QPoint mapToScreen( double mapX, double mapY )
92     {
93       const QgsPointXY pt = mCanvas->mapSettings().mapToPixel().transform( mapX, mapY );
94       return QPoint( std::round( pt.x() ), std::round( pt.y() ) );
95     }
96 
mouseMove(double mapX,double mapY)97     void mouseMove( double mapX, double mapY )
98     {
99       QgsMapMouseEvent e( mCanvas, QEvent::MouseMove, mapToScreen( mapX, mapY ) );
100       mVertexTool->cadCanvasMoveEvent( &e );
101     }
102 
mousePress(double mapX,double mapY,Qt::MouseButton button,Qt::KeyboardModifiers stateKey=Qt::KeyboardModifiers (),bool snap=false)103     void mousePress( double mapX, double mapY, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(), bool snap = false )
104     {
105       QgsMapMouseEvent e1( mCanvas, QEvent::MouseButtonPress, mapToScreen( mapX, mapY ), button, button, stateKey );
106       if ( snap )
107         e1.snapPoint();
108       mVertexTool->cadCanvasPressEvent( &e1 );
109     }
110 
mouseRelease(double mapX,double mapY,Qt::MouseButton button,Qt::KeyboardModifiers stateKey=Qt::KeyboardModifiers (),bool snap=false)111     void mouseRelease( double mapX, double mapY, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(), bool snap = false )
112     {
113       QgsMapMouseEvent e2( mCanvas, QEvent::MouseButtonRelease, mapToScreen( mapX, mapY ), button, Qt::MouseButton(), stateKey );
114       if ( snap )
115         e2.snapPoint();
116       mVertexTool->cadCanvasReleaseEvent( &e2 );
117     }
118 
mouseClick(double mapX,double mapY,Qt::MouseButton button,Qt::KeyboardModifiers stateKey=Qt::KeyboardModifiers (),bool snap=false)119     void mouseClick( double mapX, double mapY, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(), bool snap = false )
120     {
121       mousePress( mapX, mapY, button, stateKey, snap );
122       mouseRelease( mapX, mapY, button, stateKey, snap );
123     }
124 
mouseDoubleClick(double mapX,double mapY,Qt::MouseButton button,Qt::KeyboardModifiers stateKey=Qt::KeyboardModifiers (),bool snap=false)125     void mouseDoubleClick( double mapX, double mapY, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(), bool snap = false )
126     {
127       // this is how Qt passes the events: 1. mouse press, 2. mouse release, 3. mouse double-click, 4. mouse release
128 
129       mouseClick( mapX, mapY, button, stateKey, snap );
130 
131       QgsMapMouseEvent e( mCanvas, QEvent::MouseButtonDblClick, mapToScreen( mapX, mapY ), button, button, stateKey );
132       if ( snap )
133         e.snapPoint();
134       mVertexTool->canvasDoubleClickEvent( &e );
135 
136       mouseRelease( mapX, mapY, button, stateKey );
137     }
138 
keyClick(int key)139     void keyClick( int key )
140     {
141       QKeyEvent e1( QEvent::KeyPress, key, Qt::KeyboardModifiers() );
142       mVertexTool->keyPressEvent( &e1 );
143 
144       QKeyEvent e2( QEvent::KeyRelease, key, Qt::KeyboardModifiers() );
145       mVertexTool->keyReleaseEvent( &e2 );
146     }
147 
148   private:
149     QgsMapCanvas *mCanvas = nullptr;
150     QgisApp *mQgisApp = nullptr;
151     QgsAdvancedDigitizingDockWidget *mAdvancedDigitizingDockWidget = nullptr;
152     QgsVertexTool *mVertexTool = nullptr;
153     QgsVectorLayer *mLayerLine = nullptr;
154     QgsVectorLayer *mLayerMultiLine = nullptr;
155     QgsVectorLayer *mLayerPolygon = nullptr;
156     QgsVectorLayer *mLayerMultiPolygon = nullptr;
157     QgsVectorLayer *mLayerPoint = nullptr;
158     QgsVectorLayer *mLayerLineZ = nullptr;
159     QgsVectorLayer *mLayerCompoundCurve = nullptr;
160     QgsVectorLayer *mLayerLineReprojected = nullptr;
161     QgsFeatureId mFidLineZF1 = 0;
162     QgsFeatureId mFidLineZF2 = 0;
163     QgsFeatureId mFidLineZF3 = 0;
164     QgsFeatureId mFidLineF1 = 0;
165     QgsFeatureId mFidMultiLineF1 = 0;
166     QgsFeatureId mFidLineF13857 = 0;
167     QgsFeatureId mFidPolygonF1 = 0;
168     QgsFeatureId mFidMultiPolygonF1 = 0;
169     QgsFeatureId mFidPointF1 = 0;
170     QgsFeatureId mFidCompoundCurveF1 = 0;
171     QgsFeatureId mFidCompoundCurveF2 = 0;
172 };
173 
174 TestQgsVertexTool::TestQgsVertexTool() = default;
175 
176 
177 //runs before all tests
initTestCase()178 void TestQgsVertexTool::initTestCase()
179 {
180   qDebug() << "TestQgisAppClipboard::initTestCase()";
181   // init QGIS's paths - true means that all path will be inited from prefix
182   QgsApplication::init();
183   QgsApplication::initQgis();
184   mQgisApp = new QgisApp();
185 
186   // Set up the QSettings environment
187   QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
188   QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
189   QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
190 
191   mCanvas = new QgsMapCanvas();
192 
193   mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:27700" ) ) );
194 
195   mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mCanvas );
196 
197   // make testing layers
198   mLayerLine = new QgsVectorLayer( QStringLiteral( "LineString?crs=EPSG:27700" ), QStringLiteral( "layer line" ), QStringLiteral( "memory" ) );
199   QVERIFY( mLayerLine->isValid() );
200   mLayerMultiLine = new QgsVectorLayer( QStringLiteral( "MultiLineString?crs=EPSG:27700" ), QStringLiteral( "layer multiline" ), QStringLiteral( "memory" ) );
201   QVERIFY( mLayerMultiLine->isValid() );
202   mLayerLineReprojected = new QgsVectorLayer( QStringLiteral( "LineString?crs=EPSG:3857" ), QStringLiteral( "layer line" ), QStringLiteral( "memory" ) );
203   QVERIFY( mLayerLineReprojected->isValid() );
204   mLayerPolygon = new QgsVectorLayer( QStringLiteral( "Polygon?crs=EPSG:27700" ), QStringLiteral( "layer polygon" ), QStringLiteral( "memory" ) );
205   QVERIFY( mLayerPolygon->isValid() );
206   mLayerMultiPolygon = new QgsVectorLayer( QStringLiteral( "MultiPolygon?crs=EPSG:27700" ), QStringLiteral( "layer multipolygon" ), QStringLiteral( "memory" ) );
207   QVERIFY( mLayerMultiPolygon->isValid() );
208   mLayerPoint = new QgsVectorLayer( QStringLiteral( "Point?crs=EPSG:27700" ), QStringLiteral( "layer point" ), QStringLiteral( "memory" ) );
209   QVERIFY( mLayerPoint->isValid() );
210   mLayerLineZ = new QgsVectorLayer( QStringLiteral( "LineStringZ?crs=EPSG:27700" ), QStringLiteral( "layer line" ), QStringLiteral( "memory" ) );
211   QVERIFY( mLayerLineZ->isValid() );
212   mLayerCompoundCurve = new QgsVectorLayer( QStringLiteral( "CompoundCurve?crs=27700" ), QStringLiteral( "layer compound curve" ), QStringLiteral( "memory" ) );
213   QVERIFY( mLayerCompoundCurve->isValid() );
214   QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerMultiLine << mLayerPolygon << mLayerMultiPolygon << mLayerPoint << mLayerLineZ << mLayerCompoundCurve );
215 
216   QgsFeature lineF1;
217   lineF1.setGeometry( QgsGeometry::fromWkt( "LineString (2 1, 1 1, 1 3)" ) );
218 
219   QgsFeature multiLineF1;
220   multiLineF1.setGeometry( QgsGeometry::fromWkt( "MultiLineString ((3 1, 3 2),(3 3, 3 4))" ) );
221 
222   const QgsCoordinateTransform ct( mLayerLine->crs(), mLayerLineReprojected->crs(), QgsCoordinateTransformContext() );
223   QgsGeometry line3857 = lineF1.geometry();
224   line3857.transform( ct );
225   QgsFeature lineF13857;
226   lineF13857.setGeometry( line3857 );
227 
228   QgsFeature polygonF1;
229   polygonF1.setGeometry( QgsGeometry::fromWkt( "Polygon ((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
230 
231   QgsFeature multiPolygonF1;
232   multiPolygonF1.setGeometry( QgsGeometry::fromWkt( "MultiPolygon (((1 5, 2 5, 2 6.5, 2 8, 1 8, 1 6.5, 1 5),(1.25 5.5, 1.25 6, 1.75 6, 1.75 5.5, 1.25 5.5),(1.25 7, 1.75 7, 1.75 7.5, 1.25 7.5, 1.25 7)),((3 5, 3 6.5, 3 8, 4 8, 4 6.5, 4 5, 3 5),(3.25 5.5, 3.75 5.5, 3.75 6, 3.25 6, 3.25 5.5),(3.25 7, 3.75 7, 3.75 7.5, 3.25 7.5, 3.25 7)))" ) );
233 
234   QgsFeature pointF1;
235   pointF1.setGeometry( QgsGeometry::fromWkt( "Point (2 3)" ) );
236 
237   QgsFeature linez1, linez2, linez3;
238   linez1.setGeometry( QgsGeometry::fromWkt( "LineStringZ (5 5 1, 6 6 1, 7 5 1)" ) );
239   linez2.setGeometry( QgsGeometry::fromWkt( "LineStringZ (5 7 5, 7 7 10)" ) );
240   linez3.setGeometry( QgsGeometry::fromWkt( "LineStringZ (5 5.5 5, 7 5.5 10)" ) );
241 
242   QgsFeature curveF1;
243   curveF1.setGeometry( QgsGeometry::fromWkt( "CompoundCurve (CircularString (14 14, 10 10, 17 10))" ) );
244   QgsFeature curveF2;
245   curveF2.setGeometry( QgsGeometry::fromWkt( "CompoundCurve ((16 11, 17 11, 17 13))" ) );
246 
247   mLayerLine->startEditing();
248   mLayerLine->addFeature( lineF1 );
249   mFidLineF1 = lineF1.id();
250   QCOMPARE( mLayerLine->featureCount(), ( long )1 );
251 
252   mLayerMultiLine->startEditing();
253   mLayerMultiLine->addFeature( multiLineF1 );
254   mFidMultiLineF1 = multiLineF1.id();
255   QCOMPARE( mLayerMultiLine->featureCount(), ( long )1 );
256 
257   mLayerLineReprojected->startEditing();
258   mLayerLineReprojected->addFeature( lineF13857 );
259   mFidLineF13857 = lineF13857.id();
260   QCOMPARE( mLayerLineReprojected->featureCount(), ( long )1 );
261 
262   mLayerPolygon->startEditing();
263   mLayerPolygon->addFeature( polygonF1 );
264   mFidPolygonF1 = polygonF1.id();
265   QCOMPARE( mLayerPolygon->featureCount(), ( long )1 );
266 
267   mLayerMultiPolygon->startEditing();
268   mLayerMultiPolygon->addFeature( multiPolygonF1 );
269   mFidMultiPolygonF1 = multiPolygonF1.id();
270   QCOMPARE( mLayerMultiPolygon->featureCount(), ( long )1 );
271 
272   mLayerPoint->startEditing();
273   mLayerPoint->addFeature( pointF1 );
274   mFidPointF1 = pointF1.id();
275   QCOMPARE( mLayerPoint->featureCount(), ( long )1 );
276 
277   mLayerLineZ->startEditing();
278   mLayerLineZ->addFeature( linez1 );
279   mLayerLineZ->addFeature( linez2 );
280   mLayerLineZ->addFeature( linez3 );
281   mFidLineZF1 = linez1.id();
282   mFidLineZF2 = linez2.id();
283   mFidLineZF3 = linez3.id();
284   QCOMPARE( mLayerLineZ->featureCount(), ( long ) 3 );
285 
286   mLayerCompoundCurve->startEditing();
287   mLayerCompoundCurve->addFeature( curveF1 );
288   mLayerCompoundCurve->addFeature( curveF2 );
289   mFidCompoundCurveF1 = curveF1.id();
290   mFidCompoundCurveF2 = curveF2.id();
291   QCOMPARE( mLayerCompoundCurve->featureCount(), ( long ) 2 );
292 
293   // just one added feature in each undo stack
294   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
295   QCOMPARE( mLayerMultiLine->undoStack()->index(), 1 );
296   QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
297   QCOMPARE( mLayerMultiPolygon->undoStack()->index(), 1 );
298   QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
299   // except for layerLineZ
300   QCOMPARE( mLayerLineZ->undoStack()->index(), 3 );
301   QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 2 );
302 
303   mCanvas->setFrameStyle( QFrame::NoFrame );
304   mCanvas->resize( 512, 512 );
305   mCanvas->setExtent( QgsRectangle( 0, 0, 8, 8 ) );
306   mCanvas->show(); // to make the canvas resize
307   mCanvas->hide();
308   QCOMPARE( mCanvas->mapSettings().outputSize(), QSize( 512, 512 ) );
309   QCOMPARE( mCanvas->mapSettings().visibleExtent(), QgsRectangle( 0, 0, 8, 8 ) );
310 
311   mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerMultiLine << mLayerLineReprojected << mLayerPolygon << mLayerMultiPolygon << mLayerPoint << mLayerLineZ << mLayerCompoundCurve );
312 
313   QgsMapCanvasSnappingUtils *snappingUtils = new QgsMapCanvasSnappingUtils( mCanvas, this );
314   mCanvas->setSnappingUtils( snappingUtils );
315 
316   snappingUtils->locatorForLayer( mLayerLine )->init();
317   snappingUtils->locatorForLayer( mLayerMultiLine )->init();
318   snappingUtils->locatorForLayer( mLayerLineReprojected )->init();
319   snappingUtils->locatorForLayer( mLayerPolygon )->init();
320   snappingUtils->locatorForLayer( mLayerMultiPolygon )->init();
321   snappingUtils->locatorForLayer( mLayerPoint )->init();
322   snappingUtils->locatorForLayer( mLayerLineZ )->init();
323   snappingUtils->locatorForLayer( mLayerCompoundCurve )->init();
324 
325   // create vertex tool
326   mVertexTool = new QgsVertexTool( mCanvas, mAdvancedDigitizingDockWidget );
327 
328   mCanvas->setMapTool( mVertexTool );
329 }
330 
331 //runs after all tests
cleanupTestCase()332 void TestQgsVertexTool::cleanupTestCase()
333 {
334   delete mVertexTool;
335   delete mAdvancedDigitizingDockWidget;
336   delete mCanvas;
337   QgsApplication::exitQgis();
338 }
339 
testTopologicalEditingMoveVertexZ()340 void TestQgsVertexTool::testTopologicalEditingMoveVertexZ()
341 {
342   const bool topologicalEditing = QgsProject::instance()->topologicalEditing();
343   QgsProject::instance()->setTopologicalEditing( true );
344   QgsSnappingConfig cfg = mCanvas->snappingUtils()->config();
345   cfg.setMode( QgsSnappingConfig::AllLayers );
346   cfg.setTolerance( 10 );
347   cfg.setTypeFlag( static_cast<QgsSnappingConfig::SnappingTypeFlag>( QgsSnappingConfig::VertexFlag | QgsSnappingConfig::SegmentFlag ) );
348   cfg.setEnabled( true );
349   mCanvas->snappingUtils()->setConfig( cfg );
350 
351   mouseClick( 6, 6, Qt::LeftButton, Qt::KeyboardModifiers(), true );
352   mouseClick( 5, 7, Qt::LeftButton, Qt::KeyboardModifiers(), true );
353 
354   QCOMPARE( mLayerLineZ->getFeature( mFidLineZF1 ).geometry().asWkt(), QString( "LineStringZ (5 5 1, 5 7 5, 7 5 1)" ) );
355   QCOMPARE( mLayerLineZ->getFeature( mFidLineZF2 ).geometry().asWkt(), QString( "LineStringZ (5 7 5, 7 7 10)" ) );
356 
357   QgsProject::instance()->setTopologicalEditing( topologicalEditing );
358   mLayerLineZ->undoStack()->undo();
359   cfg.setEnabled( false );
360   mCanvas->snappingUtils()->setConfig( cfg );
361 }
362 
testTopologicalEditingMoveVertexOnSegmentZ()363 void TestQgsVertexTool::testTopologicalEditingMoveVertexOnSegmentZ()
364 {
365   QgsSettingsRegistryCore::settingsDigitizingDefaultZValue.setValue( 333 );
366 
367   const bool topologicalEditing = QgsProject::instance()->topologicalEditing();
368   QgsProject::instance()->setTopologicalEditing( true );
369   QgsSnappingConfig cfg = mCanvas->snappingUtils()->config();
370   cfg.setMode( QgsSnappingConfig::AllLayers );
371   cfg.setTolerance( 10 );
372   cfg.setTypeFlag( static_cast<QgsSnappingConfig::SnappingTypeFlag>( QgsSnappingConfig::VertexFlag | QgsSnappingConfig::SegmentFlag ) );
373   cfg.setEnabled( true );
374   mCanvas->snappingUtils()->setConfig( cfg );
375 
376   mouseClick( 6, 6, Qt::LeftButton, Qt::KeyboardModifiers(), true );
377   mouseClick( 6, 7, Qt::LeftButton, Qt::KeyboardModifiers(), true );
378 
379   QCOMPARE( mLayerLineZ->getFeature( mFidLineZF1 ).geometry().asWkt(), QString( "LineStringZ (5 5 1, 6 7 7.5, 7 5 1)" ) );
380   QCOMPARE( mLayerLineZ->getFeature( mFidLineZF2 ).geometry().asWkt(), QString( "LineStringZ (5 7 5, 6 7 7.5, 7 7 10)" ) );
381 
382   QgsProject::instance()->setTopologicalEditing( topologicalEditing );
383   // Two undo steps, one for the vertex move, one for the topological point
384   mLayerLineZ->undoStack()->undo();
385   mLayerLineZ->undoStack()->undo();
386   cfg.setEnabled( false );
387   mCanvas->snappingUtils()->setConfig( cfg );
388 }
389 
testTopologicalEditingMoveVertexOnIntersectionZ()390 void TestQgsVertexTool::testTopologicalEditingMoveVertexOnIntersectionZ()
391 {
392   QgsSettingsRegistryCore::settingsDigitizingDefaultZValue.setValue( 333 );
393 
394   const bool topologicalEditing = QgsProject::instance()->topologicalEditing();
395   QgsProject::instance()->setTopologicalEditing( true );
396   QgsSnappingConfig cfg = mCanvas->snappingUtils()->config();
397   cfg.setMode( QgsSnappingConfig::AllLayers );
398   cfg.setTolerance( 10 );
399   cfg.setTypeFlag( static_cast<QgsSnappingConfig::SnappingTypeFlag>( QgsSnappingConfig::VertexFlag | QgsSnappingConfig::SegmentFlag ) );
400   cfg.setIntersectionSnapping( true );
401   cfg.setEnabled( true );
402   mCanvas->snappingUtils()->setConfig( cfg );
403 
404   mouseClick( 5, 5.5, Qt::LeftButton, Qt::KeyboardModifiers(), true );
405   mouseClick( 5.5, 5.5, Qt::LeftButton, Qt::KeyboardModifiers(), true );
406 
407   // The undo stack gets two entries, one for the vertex move and one for the topological point
408   QCOMPARE( mLayerLineZ->undoStack()->index(), 5 );
409   QCOMPARE( mLayerLineZ->getFeature( mFidLineZF1 ).geometry().asWkt(), QString( "LineStringZ (5 5 1, 5.5 5.5 333, 6 6 1, 7 5 1)" ) );
410   QCOMPARE( mLayerLineZ->getFeature( mFidLineZF3 ).geometry().asWkt(), QString( "LineStringZ (5.5 5.5 5, 7 5.5 10)" ) );
411 
412   QgsProject::instance()->setTopologicalEditing( topologicalEditing );
413   // Two undo steps, one for the vertex move, one for the topological point
414   mLayerLineZ->undoStack()->undo();
415   mLayerLineZ->undoStack()->undo();
416   cfg.setEnabled( false );
417   mCanvas->snappingUtils()->setConfig( cfg );
418 }
419 
testMoveVertex()420 void TestQgsVertexTool::testMoveVertex()
421 {
422   QCOMPARE( mCanvas->mapSettings().outputSize(), QSize( 512, 512 ) );
423   QCOMPARE( mCanvas->mapSettings().visibleExtent(), QgsRectangle( 0, 0, 8, 8 ) );
424 
425   // move vertex of linestring
426 
427   mouseClick( 2, 1, Qt::LeftButton );
428   mouseClick( 2, 2, Qt::LeftButton );
429 
430   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
431   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 2, 1 1, 1 3)" ) );
432 
433   mLayerLine->undoStack()->undo();
434   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
435 
436   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
437 
438   mouseClick( 1, 1, Qt::LeftButton );
439   mouseClick( 0.5, 0.5, Qt::LeftButton );
440 
441   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
442   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 0.5 0.5, 1 3)" ) );
443 
444   mLayerLine->undoStack()->undo();
445 
446   // move point
447 
448   mouseClick( 2, 3, Qt::LeftButton );
449   mouseClick( 1, 4, Qt::LeftButton );
450 
451   QCOMPARE( mLayerPoint->undoStack()->index(), 2 );
452   QCOMPARE( mLayerPoint->getFeature( mFidPointF1 ).geometry(), QgsGeometry::fromWkt( "POINT(1 4)" ) );
453 
454   mLayerPoint->undoStack()->undo();
455 
456   QCOMPARE( mLayerPoint->getFeature( mFidPointF1 ).geometry(), QgsGeometry::fromWkt( "POINT(2 3)" ) );
457 
458   // move vertex of polygon
459 
460   mouseClick( 4, 1, Qt::LeftButton );
461   mouseClick( 5, 2, Qt::LeftButton );
462 
463   QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
464   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((5 2, 7 1, 7 4, 4 4, 5 2))" ) );
465 
466   mLayerPolygon->undoStack()->undo();
467 
468   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
469 
470   mouseClick( 4, 4, Qt::LeftButton );
471   mouseClick( 5, 5, Qt::LeftButton );
472 
473   QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
474   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 5 5, 4 1))" ) );
475 
476   mLayerPolygon->undoStack()->undo();
477 
478   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
479 
480   // cancel moving of a linestring with right mouse button
481   mouseClick( 2, 1, Qt::LeftButton );
482   mouseClick( 2, 2, Qt::RightButton );
483 
484   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
485   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
486 
487   // clicks somewhere away from features - should do nothing
488   mouseClick( 2, 2, Qt::LeftButton );
489   mouseClick( 2, 4, Qt::LeftButton );
490 
491   // no other unexpected changes happened
492   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
493   QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
494   QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
495 }
496 
testMoveEdge()497 void TestQgsVertexTool::testMoveEdge()
498 {
499   // move edge of linestring
500 
501   mouseClick( 1.2, 1, Qt::LeftButton );
502   mouseClick( 1.2, 2, Qt::LeftButton );
503 
504   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
505   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 2, 1 2, 1 3)" ) );
506 
507   mLayerLine->undoStack()->undo();
508   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
509 
510   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
511 
512   // move edge of polygon
513 
514   mouseClick( 5, 1, Qt::LeftButton );
515   mouseClick( 6, 1, Qt::LeftButton );
516 
517   QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
518   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((5 1, 8 1, 7 4, 4 4, 5 1))" ) );
519 
520   mLayerPolygon->undoStack()->undo();
521 
522   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
523 
524   // no other unexpected changes happened
525   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
526   QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
527   QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
528 }
529 
testAddVertex()530 void TestQgsVertexTool::testAddVertex()
531 {
532   // add vertex in linestring
533 
534   mouseClick( 1.5, 1, Qt::LeftButton );
535   mouseClick( 1.5, 2, Qt::LeftButton );
536 
537   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
538   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1.5 2, 1 1, 1 3)" ) );
539 
540   mLayerLine->undoStack()->undo();
541   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
542 
543   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
544 
545   // add vertex in polygon
546 
547   mouseClick( 4, 2.5, Qt::LeftButton );
548   mouseClick( 3, 2.5, Qt::LeftButton );
549 
550   QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
551   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 3 2.5, 4 1))" ) );
552 
553   mLayerPolygon->undoStack()->undo();
554 
555   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
556 
557   // no other unexpected changes happened
558   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
559   QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
560   QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
561 }
562 
testAddVertexAtEndpoint()563 void TestQgsVertexTool::testAddVertexAtEndpoint()
564 {
565   // offset of the endpoint marker - currently set as 15px away from the last vertex in direction of the line
566   const double offsetInMapUnits = 15 * mCanvas->mapSettings().mapUnitsPerPixel();
567 
568   // add vertex at the end
569   // for polyline
570   mouseMove( 1, 3 ); // first we need to move to the vertex
571   mouseClick( 1, 3 + offsetInMapUnits, Qt::LeftButton );
572   mouseClick( 2, 3, Qt::LeftButton );
573   mouseClick( 2, 3, Qt::RightButton ); // we need a right click to stop adding new nodes
574 
575   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
576   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3, 2 3)" ) );
577 
578   mLayerLine->undoStack()->undo();
579   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
580 
581   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
582 
583   // add vertex at the start
584 
585   mouseMove( 2, 1 ); // first we need to move to the vertex
586   mouseClick( 2 + offsetInMapUnits, 1, Qt::LeftButton );
587   mouseClick( 2, 2, Qt::LeftButton );
588   mouseClick( 2, 2, Qt::RightButton ); // we need a right click to stop adding new nodes
589 
590   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
591   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 2, 2 1, 1 1, 1 3)" ) );
592 
593   mLayerLine->undoStack()->undo();
594   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
595 
596   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
597 
598   // add three vertices at once
599 
600   mouseMove( 2, 1 ); // first we need to move to the vertex
601   mouseClick( 2 + offsetInMapUnits, 1, Qt::LeftButton );
602   mouseClick( 2, 2, Qt::LeftButton );
603   mouseClick( 2, 3, Qt::LeftButton );
604   mouseClick( 2, 4, Qt::LeftButton );
605   mouseClick( 2, 2, Qt::RightButton ); // we need a right click to stop adding new nodes
606 
607   QCOMPARE( mLayerLine->undoStack()->index(), 4 );
608   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 4, 2 3, 2 2, 2 1, 1 1, 1 3)" ) );
609 
610   mLayerLine->undoStack()->undo();
611   mLayerLine->undoStack()->undo();
612   mLayerLine->undoStack()->undo();
613   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
614 
615   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
616 
617 }
618 
testAddVertexDoubleClick()619 void TestQgsVertexTool::testAddVertexDoubleClick()
620 {
621   // add vertex in linestring with double-click and then place the point to the new location
622 
623   mouseDoubleClick( 1, 1.5, Qt::LeftButton );
624   mouseClick( 2, 2, Qt::LeftButton );
625 
626   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
627   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 2 2, 1 3)" ) );
628 
629   mLayerLine->undoStack()->undo();
630   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
631 
632   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
633 
634   // add vertex in polygon
635   mouseDoubleClick( 4, 2, Qt::LeftButton );
636   mouseClick( 3, 2.5, Qt::LeftButton );
637 
638   QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
639   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 3 2.5, 4 1))" ) );
640 
641   mLayerPolygon->undoStack()->undo();
642 
643   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
644 
645   // no other unexpected changes happened
646   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
647   QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
648   QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
649 
650 }
651 
testAddVertexDoubleClickWithShift()652 void TestQgsVertexTool::testAddVertexDoubleClickWithShift()
653 {
654   // add vertex in linestring with shift + double-click to immediately place the new vertex
655 
656   mouseDoubleClick( 1, 1.5, Qt::LeftButton, Qt::ShiftModifier );
657 
658   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
659   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 1.5, 1 3)" ) );
660 
661   mLayerLine->undoStack()->undo();
662   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
663 
664   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
665 
666   // add vertex in polygon
667   mouseDoubleClick( 4, 2, Qt::LeftButton, Qt::ShiftModifier );
668 
669   QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
670   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 2, 4 1))" ) );
671 
672   mLayerPolygon->undoStack()->undo();
673 
674   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
675 
676   // no other unexpected changes happened
677   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
678   QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
679   QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
680 
681 }
682 
testDeleteVertex()683 void TestQgsVertexTool::testDeleteVertex()
684 {
685   // delete vertex in linestring
686 
687   mouseClick( 1, 1, Qt::LeftButton );
688   keyClick( Qt::Key_Delete );
689 
690   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
691   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 3)" ) );
692 
693   mLayerLine->undoStack()->undo();
694   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
695 
696   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
697 
698   // delete vertex in polygon
699 
700   mouseClick( 7, 4, Qt::LeftButton );
701   keyClick( Qt::Key_Delete );
702 
703   QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
704   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 4 4, 4 1))" ) );
705 
706   mLayerPolygon->undoStack()->undo();
707 
708   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
709 
710   // delete vertex in point - deleting its geometry
711 
712   mouseClick( 2, 3, Qt::LeftButton );
713   keyClick( Qt::Key_Delete );
714 
715   QCOMPARE( mLayerPoint->undoStack()->index(), 2 );
716   QCOMPARE( mLayerPoint->getFeature( mFidPointF1 ).geometry(), QgsGeometry() );
717 
718   mLayerPoint->undoStack()->undo();
719 
720   QCOMPARE( mLayerPoint->getFeature( mFidPointF1 ).geometry(), QgsGeometry::fromWkt( "POINT(2 3)" ) );
721 
722   // delete a vertex by dragging a selection rect
723 
724   mousePress( 0.5, 2.5, Qt::LeftButton );
725   mouseMove( 1.5, 3.5 );
726   mouseRelease( 1.5, 3.5, Qt::LeftButton );
727   keyClick( Qt::Key_Delete );
728 
729   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
730   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1)" ) );
731 
732   mLayerLine->undoStack()->undo();
733   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
734 
735   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
736 
737   // delete multiline part by dragging
738 
739   mousePress( 2.5, 0.5, Qt::LeftButton );
740   mouseMove( 3.5, 2.5 );
741   mouseRelease( 3.5, 2.5, Qt::LeftButton );
742   keyClick( Qt::Key_Delete );
743 
744   QCOMPARE( mLayerMultiLine->undoStack()->index(), 2 );
745   QCOMPARE( mLayerMultiLine->getFeature( mFidMultiLineF1 ).geometry(), QgsGeometry::fromWkt( "MultiLineString ((3 3, 3 4))" ) );
746 
747   mLayerMultiLine->undoStack()->undo();
748   QCOMPARE( mLayerMultiLine->undoStack()->index(), 1 );
749 
750   // delete multiline part by dragging and leaving only one vertex to the part
751 
752   mousePress( 2.5, 0.5, Qt::LeftButton );
753   mouseMove( 3.5, 1.5 );
754   mouseRelease( 3.5, 1.5, Qt::LeftButton );
755   keyClick( Qt::Key_Delete );
756 
757   QCOMPARE( mLayerMultiLine->undoStack()->index(), 2 );
758   QCOMPARE( mLayerMultiLine->getFeature( mFidMultiLineF1 ).geometry(), QgsGeometry::fromWkt( "MultiLineString ((3 3, 3 4))" ) );
759 
760   mLayerMultiLine->undoStack()->undo();
761   QCOMPARE( mLayerMultiLine->undoStack()->index(), 1 );
762 
763   // delete inner ring by dragging
764 
765   mousePress( 1.1, 5.1, Qt::LeftButton );
766   mouseMove( 1.8, 6.2 );
767   mouseRelease( 1.8, 6.2, Qt::LeftButton );
768   keyClick( Qt::Key_Delete );
769 
770   QCOMPARE( mLayerMultiPolygon->undoStack()->index(), 2 );
771   QCOMPARE( mLayerMultiPolygon->getFeature( mFidMultiPolygonF1 ).geometry(), QgsGeometry::fromWkt( "MultiPolygon (((1 5, 2 5, 2 6.5, 2 8, 1 8, 1 6.5, 1 5),(1.25 7, 1.75 7, 1.75 7.5, 1.25 7.5, 1.25 7)),((3 5, 3 6.5, 3 8, 4 8, 4 6.5, 4 5, 3 5),(3.25 5.5, 3.75 5.5, 3.75 6, 3.25 6, 3.25 5.5),(3.25 7, 3.75 7, 3.75 7.5, 3.25 7.5, 3.25 7)))" ) );
772 
773   mLayerMultiPolygon->undoStack()->undo();
774   QCOMPARE( mLayerMultiPolygon->undoStack()->index(), 1 );
775 
776   // delete inner ring by dragging and leaving less than 4 vertices to the ring
777 
778   mousePress( 1.1, 5.1, Qt::LeftButton );
779   mouseMove( 1.8, 5.7 );
780   mouseRelease( 1.8, 5.7, Qt::LeftButton );
781   keyClick( Qt::Key_Delete );
782 
783   QCOMPARE( mLayerMultiPolygon->undoStack()->index(), 2 );
784   QCOMPARE( mLayerMultiPolygon->getFeature( mFidMultiPolygonF1 ).geometry(), QgsGeometry::fromWkt( "MultiPolygon (((1 5, 2 5, 2 6.5, 2 8, 1 8, 1 6.5, 1 5),(1.25 7, 1.75 7, 1.75 7.5, 1.25 7.5, 1.25 7)),((3 5, 3 6.5, 3 8, 4 8, 4 6.5, 4 5, 3 5),(3.25 5.5, 3.75 5.5, 3.75 6, 3.25 6, 3.25 5.5),(3.25 7, 3.75 7, 3.75 7.5, 3.25 7.5, 3.25 7)))" ) );
785 
786   mLayerMultiPolygon->undoStack()->undo();
787   QCOMPARE( mLayerMultiPolygon->undoStack()->index(), 1 );
788 
789   // delete part and rings by dragging
790 
791   mousePress( 0.5, 4.5, Qt::LeftButton );
792   mouseMove( 2.5, 8.5 );
793   mouseRelease( 2.5, 8.5, Qt::LeftButton );
794   keyClick( Qt::Key_Delete );
795 
796   QCOMPARE( mLayerMultiPolygon->undoStack()->index(), 2 );
797   QCOMPARE( mLayerMultiPolygon->getFeature( mFidMultiPolygonF1 ).geometry(), QgsGeometry::fromWkt( "MultiPolygon (((3 5, 3 6.5, 3 8, 4 8, 4 6.5, 4 5, 3 5),(3.25 5.5, 3.75 5.5, 3.75 6, 3.25 6, 3.25 5.5),(3.25 7, 3.75 7, 3.75 7.5, 3.25 7.5, 3.25 7)))" ) );
798 
799   mLayerMultiPolygon->undoStack()->undo();
800   QCOMPARE( mLayerMultiPolygon->undoStack()->index(), 1 );
801 
802   // combined delete rings from different parts by dragging
803 
804   mousePress( 0.5, 4.5, Qt::LeftButton );
805   mouseMove( 4.5, 6.2 );
806   mouseRelease( 4.5, 6.2, Qt::LeftButton );
807   keyClick( Qt::Key_Delete );
808 
809   QCOMPARE( mLayerMultiPolygon->undoStack()->index(), 2 );
810   QCOMPARE( mLayerMultiPolygon->getFeature( mFidMultiPolygonF1 ).geometry(), QgsGeometry::fromWkt( "MultiPolygon (((1 6.5, 2 6.5, 2 8, 1 8, 1 6.5),(1.25 7, 1.75 7, 1.75 7.5, 1.25 7.5, 1.25 7)),((4 6.5, 3 6.5, 3 8, 4 8, 4 6.5),(3.25 7, 3.75 7, 3.75 7.5, 3.25 7.5, 3.25 7)))" ) );
811 
812   mLayerMultiPolygon->undoStack()->undo();
813   QCOMPARE( mLayerMultiPolygon->undoStack()->index(), 1 );
814 
815   // no other unexpected changes happened
816   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
817   QCOMPARE( mLayerPolygon->undoStack()->index(), 1 );
818   QCOMPARE( mLayerPoint->undoStack()->index(), 1 );
819 }
820 
821 
testConvertVertex()822 void TestQgsVertexTool::testConvertVertex()
823 {
824   QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 2 );
825 
826   // convert vertex in compoundCurve while moving vertex
827   QCOMPARE( mLayerCompoundCurve->getFeature( mFidCompoundCurveF1 ).geometry(), QgsGeometry::fromWkt( "CompoundCurve ( CircularString (14 14, 10 10, 17 10))" ) );
828   mouseClick( 10, 10, Qt::LeftButton );
829   keyClick( Qt::Key_O );
830   QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 3 );
831   QCOMPARE( mLayerCompoundCurve->getFeature( mFidCompoundCurveF1 ).geometry(), QgsGeometry::fromWkt( "CompoundCurve ((14 14, 10 10, 17 10))" ) );
832   mLayerCompoundCurve->undoStack()->undo();
833   QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 2 );
834   QCOMPARE( mLayerCompoundCurve->getFeature( mFidCompoundCurveF1 ).geometry(), QgsGeometry::fromWkt( "CompoundCurve ( CircularString (14 14, 10 10, 17 10))" ) );
835 
836   // convert vertex in compoundCurve by selection
837   QCOMPARE( mLayerCompoundCurve->getFeature( mFidCompoundCurveF1 ).geometry(), QgsGeometry::fromWkt( "CompoundCurve ( CircularString (14 14, 10 10, 17 10))" ) );
838   mousePress( 9.5, 9.5, Qt::LeftButton );
839   mouseMove( 10.5, 10.5 );
840   mouseRelease( 10.5, 10.5, Qt::LeftButton );
841   keyClick( Qt::Key_O );
842   QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 3 );
843   QCOMPARE( mLayerCompoundCurve->getFeature( mFidCompoundCurveF1 ).geometry(), QgsGeometry::fromWkt( "CompoundCurve ((14 14, 10 10, 17 10))" ) );
844   mLayerCompoundCurve->undoStack()->undo();
845   QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 2 );
846   QCOMPARE( mLayerCompoundCurve->getFeature( mFidCompoundCurveF1 ).geometry(), QgsGeometry::fromWkt( "CompoundCurve ( CircularString (14 14, 10 10, 17 10))" ) );
847 }
848 
testMoveMultipleVertices()849 void TestQgsVertexTool::testMoveMultipleVertices()
850 {
851   // select two vertices
852   mousePress( 0.5, 0.5, Qt::LeftButton );
853   mouseMove( 1.5, 3.5 );
854   mouseRelease( 1.5, 3.5, Qt::LeftButton );
855 
856   // move them by -1,-1
857   mouseClick( 1, 1, Qt::LeftButton );
858   mouseClick( 0, 0, Qt::LeftButton );
859 
860   // extra click away from everything to clear the selection
861   mouseClick( 8, 8, Qt::LeftButton );
862 
863   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
864   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 0 0, 0 2)" ) );
865 
866   mLayerLine->undoStack()->undo();
867   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
868 
869   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
870 
871   QCOMPARE( mLayerLineReprojected->getFeature( mFidLineF13857 ).geometry().asWkt( 0 ), QStringLiteral( "LineString (-841256 6405990, -841259 6405988)" ) );
872   mLayerLineReprojected->undoStack()->undo();
873   QCOMPARE( mLayerLineReprojected->getFeature( mFidLineF13857 ).geometry().asWkt( 0 ), QStringLiteral( "LineString (-841256 6405990, -841258 6405990)" ) );
874 }
875 
testMoveMultipleVertices2()876 void TestQgsVertexTool::testMoveMultipleVertices2()
877 {
878   // this time select two vertices with shift
879   mouseClick( 1, 1, Qt::LeftButton, Qt::ShiftModifier );
880   mouseClick( 2, 1, Qt::LeftButton, Qt::ShiftModifier );
881 
882   // move them by +1, +1
883   mouseClick( 1, 1, Qt::LeftButton );
884   mouseClick( 2, 2, Qt::LeftButton );
885 
886   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
887   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(3 2, 2 2, 1 3)" ) );
888 
889   mLayerLine->undoStack()->undo();
890   QCOMPARE( mLayerLine->undoStack()->index(), 1 );
891 
892   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
893 }
894 
testMoveVertexTopo()895 void TestQgsVertexTool::testMoveVertexTopo()
896 {
897   // test moving of vertices of two features at once
898 
899   QgsProject::instance()->setTopologicalEditing( true );
900 
901   // connect linestring with polygon at point (2, 1)
902   mouseClick( 4, 1, Qt::LeftButton );
903   mouseClick( 2, 1, Qt::LeftButton );
904 
905   // move shared vertex of linestring and polygon
906   mouseClick( 2, 1, Qt::LeftButton );
907   mouseClick( 3, 3, Qt::LeftButton );
908 
909   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(3 3, 1 1, 1 3)" ) );
910   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((3 3, 7 1, 7 4, 4 4, 3 3))" ) );
911 
912   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
913   QCOMPARE( mLayerPolygon->undoStack()->index(), 3 );  // one more move of vertex from earlier
914   mLayerLine->undoStack()->undo();
915   mLayerPolygon->undoStack()->undo();
916   mLayerPolygon->undoStack()->undo();
917 
918   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
919   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
920 
921   QgsProject::instance()->setTopologicalEditing( false );
922 }
923 
testDeleteVertexTopo()924 void TestQgsVertexTool::testDeleteVertexTopo()
925 {
926   // test deletion of vertices with topological editing enabled
927 
928   QgsProject::instance()->setTopologicalEditing( true );
929 
930   // connect linestring with polygon at point (2, 1)
931   mouseClick( 4, 1, Qt::LeftButton );
932   mouseClick( 2, 1, Qt::LeftButton );
933 
934   // move shared vertex of linestring and polygon
935   mouseClick( 2, 1, Qt::LeftButton );
936   keyClick( Qt::Key_Delete );
937 
938   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(1 1, 1 3)" ) );
939   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((7 1, 7 4, 4 4, 7 1))" ) );
940 
941   QCOMPARE( mLayerLine->undoStack()->index(), 2 );
942   QCOMPARE( mLayerPolygon->undoStack()->index(), 3 );  // one more move of vertex from earlier
943   mLayerLine->undoStack()->undo();
944   mLayerPolygon->undoStack()->undo();
945   mLayerPolygon->undoStack()->undo();
946 
947   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
948   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
949 
950   QgsProject::instance()->setTopologicalEditing( false );
951 }
952 
testAddVertexTopo()953 void TestQgsVertexTool::testAddVertexTopo()
954 {
955   // test addition of a vertex on a segment shared with another geometry
956 
957   // add a temporary polygon
958   QgsFeature fTmp;
959   fTmp.setGeometry( QgsGeometry::fromWkt( "POLYGON((4 4, 7 4, 7 6, 4 6, 4 4))" ) );
960   const bool resAdd = mLayerPolygon->addFeature( fTmp );
961   QVERIFY( resAdd );
962   const QgsFeatureId fTmpId = fTmp.id();
963 
964   QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
965 
966   QgsProject::instance()->setTopologicalEditing( true );
967 
968   mouseClick( 5.5, 4, Qt::LeftButton );
969   mouseClick( 5, 5, Qt::LeftButton );
970 
971   QCOMPARE( mLayerPolygon->undoStack()->index(), 3 );
972 
973   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 5 5, 4 4, 4 1))" ) );
974   QCOMPARE( mLayerPolygon->getFeature( fTmpId ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 4, 5 5, 7 4, 7 6, 4 6, 4 4))" ) );
975 
976   mLayerPolygon->undoStack()->undo();
977   mLayerPolygon->undoStack()->undo();
978 
979   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
980 
981   QgsProject::instance()->setTopologicalEditing( false );
982 }
983 
testMoveEdgeTopo()984 void TestQgsVertexTool::testMoveEdgeTopo()
985 {
986   // test move of an edge shared with another feature
987 
988   // add a temporary polygon
989   QgsFeature fTmp;
990   fTmp.setGeometry( QgsGeometry::fromWkt( "POLYGON((4 4, 7 4, 7 6, 4 6, 4 4))" ) );
991   const bool resAdd = mLayerPolygon->addFeature( fTmp );
992   QVERIFY( resAdd );
993   const QgsFeatureId fTmpId = fTmp.id();
994 
995   QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
996 
997   QgsProject::instance()->setTopologicalEditing( true );
998 
999   // move shared segment
1000   mouseClick( 6, 4, Qt::LeftButton );
1001   mouseClick( 6, 5, Qt::LeftButton );
1002 
1003   QCOMPARE( mLayerPolygon->undoStack()->index(), 3 );
1004 
1005   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 5, 4 5, 4 1))" ) );
1006   QCOMPARE( mLayerPolygon->getFeature( fTmpId ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 5, 7 5, 7 6, 4 6, 4 5))" ) );
1007 
1008   mLayerPolygon->undoStack()->undo();
1009 
1010   // another test to move a shared segment - but this time we just pick two individual points of a feature
1011   // and do vertex move
1012 
1013   QgsProject::instance()->setTopologicalEditing( false );
1014 
1015   // this time select two vertices with shift
1016   mouseClick( 4, 4, Qt::LeftButton, Qt::ShiftModifier );
1017   mouseClick( 7, 4, Qt::LeftButton, Qt::ShiftModifier );
1018 
1019   QgsProject::instance()->setTopologicalEditing( true );
1020 
1021   // now move the shared segment
1022   mouseClick( 4, 4, Qt::LeftButton );
1023   mouseClick( 4, 3, Qt::LeftButton );
1024 
1025   QCOMPARE( mLayerPolygon->undoStack()->index(), 3 );
1026 
1027   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 3, 4 3, 4 1))" ) );
1028   QCOMPARE( mLayerPolygon->getFeature( fTmpId ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 3, 7 3, 7 6, 4 6, 4 3))" ) );
1029 
1030   mLayerPolygon->undoStack()->undo();
1031 
1032   //
1033 
1034   mLayerPolygon->undoStack()->undo();
1035 
1036   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
1037 
1038   QgsProject::instance()->setTopologicalEditing( false );
1039 }
1040 
testAddVertexTopoFirstSegment()1041 void TestQgsVertexTool::testAddVertexTopoFirstSegment()
1042 {
1043   // check that when adding a vertex to the first segment of a polygon's ring with topo editing
1044   // enabled, the geometry does not get corrupted (#20774)
1045 
1046   QgsProject::instance()->setTopologicalEditing( true );
1047 
1048   mouseClick( 5.5, 1, Qt::LeftButton );
1049   mouseClick( 5, 2, Qt::LeftButton );
1050 
1051   QCOMPARE( mLayerPolygon->undoStack()->index(), 2 );
1052 
1053   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 5 2, 7 1, 7 4, 4 4, 4 1))" ) );
1054 
1055   mLayerPolygon->undoStack()->undo();
1056 
1057   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
1058 
1059   QgsProject::instance()->setTopologicalEditing( false );
1060 }
1061 
testAvoidIntersections()1062 void TestQgsVertexTool::testAvoidIntersections()
1063 {
1064   // check that when adding a vertex to the first segment of a polygon's ring with topo editing
1065   // enabled, the geometry does not get corrupted (#20774)
1066 
1067   QgsProject::instance()->setTopologicalEditing( true );
1068   const QgsProject::AvoidIntersectionsMode mode( QgsProject::instance()->avoidIntersectionsMode() );
1069   QgsProject::instance()->setAvoidIntersectionsMode( QgsProject::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer );
1070 
1071   QgsPolygonXY polygon2;
1072   QgsPolylineXY polygon2exterior;
1073   polygon2exterior << QgsPointXY( 8, 2 ) << QgsPointXY( 9, 2 ) << QgsPointXY( 9, 3 ) << QgsPointXY( 8, 3 ) << QgsPointXY( 8, 2 );
1074   polygon2 << polygon2exterior;
1075   QgsFeature polygonF2;
1076   polygonF2.setGeometry( QgsGeometry::fromPolygonXY( polygon2 ) );
1077 
1078   mLayerPolygon->addFeature( polygonF2 );
1079   const QgsFeatureId mFidPolygonF2 = polygonF2.id();
1080   QCOMPARE( mLayerPolygon->featureCount(), ( long )2 );
1081 
1082   mouseClick( 7, 1, Qt::LeftButton );
1083   mouseClick( 9, 2, Qt::LeftButton );
1084 
1085   QCOMPARE( mLayerPolygon->undoStack()->index(), 3 );
1086 
1087   QCOMPARE( QgsGeometry::fromWkt( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry().asWkt( 1 ) ), QgsGeometry::fromWkt( "Polygon ((4 4, 7 4, 8 3, 8 2, 9 2, 4 1, 4 4))" ) ); // avoid rounding errors
1088   QCOMPARE( QgsGeometry::fromWkt( mLayerPolygon->getFeature( mFidPolygonF2 ).geometry().asWkt( 1 ) ), QgsGeometry::fromWkt( "Polygon ((8 2, 9 2, 9 3, 8 3, 8 2))" ) );
1089 
1090   mLayerPolygon->undoStack()->undo();
1091 
1092   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
1093 
1094   // Move polygons and check that geometry are not avoided
1095   // select polygons
1096   mousePress( 3, 5, Qt::LeftButton );
1097   mouseMove( 9.5, 0.5 );
1098   mouseRelease( 9.5, 0.5, Qt::LeftButton );
1099 
1100   // move polygons
1101   mouseClick( 8, 2, Qt::LeftButton );
1102   mouseClick( 5, 2, Qt::LeftButton );
1103 
1104   QCOMPARE( QgsGeometry::fromWkt( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry().asWkt( 1 ) ), QgsGeometry::fromWkt( "Polygon ((1 1, 4 1, 4 4, 1 4, 1 1))" ) );
1105   QCOMPARE( QgsGeometry::fromWkt( mLayerPolygon->getFeature( mFidPolygonF2 ).geometry().asWkt( 1 ) ), QgsGeometry::fromWkt( "Polygon ((5 2, 6 2, 6 3, 5 3, 5 2))" ) );
1106 
1107   mLayerPolygon->undoStack()->undo();
1108   mLayerPolygon->undoStack()->undo(); // delete feature
1109 
1110   QCOMPARE( mLayerPolygon->featureCount(), ( long )1 );
1111   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((4 1, 7 1, 7 4, 4 4, 4 1))" ) );
1112 
1113   // If topologicalEditing and avoidIntersections are activated we must take care that the topological points are well added.
1114   QgsPolygonXY polygon_topo1;
1115   QgsPolylineXY polygon_topo1exterior;
1116   polygon_topo1exterior << QgsPointXY( 0, 10 ) << QgsPointXY( 0, 20 ) << QgsPointXY( 5, 15 ) << QgsPointXY( 0, 10 );
1117   polygon_topo1 << polygon_topo1exterior;
1118   QgsFeature polygonF_topo1;
1119   polygonF_topo1.setGeometry( QgsGeometry::fromPolygonXY( polygon_topo1 ) );
1120 
1121   mLayerPolygon->addFeature( polygonF_topo1 );
1122   const QgsFeatureId mFidPolygonF_topo1 = polygonF_topo1.id();
1123 
1124   QgsPolygonXY polygon_topo2;
1125   QgsPolylineXY polygon_topo2exterior;
1126   polygon_topo2exterior << QgsPointXY( 10, 15 ) << QgsPointXY( 15, 10 ) << QgsPointXY( 15, 20 ) << QgsPointXY( 10, 15 );
1127   polygon_topo2 << polygon_topo2exterior;
1128   QgsFeature polygonF_topo2;
1129   polygonF_topo2.setGeometry( QgsGeometry::fromPolygonXY( polygon_topo2 ) );
1130 
1131   mLayerPolygon->addFeature( polygonF_topo2 );
1132   const QgsFeatureId mFidPolygonF_topo2 = polygonF_topo2.id();
1133 
1134   QCOMPARE( mLayerPolygon->featureCount(), ( long )3 );
1135 
1136   mouseClick( 5, 15, Qt::LeftButton );
1137   mouseClick( 12.5, 15, Qt::LeftButton );
1138 
1139   if ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 9 )
1140   {
1141     QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF_topo1 ).geometry().asWkt( 1 ), "Polygon ((0 10, 0 20, 10.7 15.7, 10 15, 10.7 14.3, 0 10))" );
1142   }
1143   else
1144   {
1145     QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF_topo1 ).geometry().asWkt( 1 ), "Polygon ((0 20, 10.7 15.7, 10 15, 10.7 14.3, 0 10, 0 20))" );
1146   }
1147   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF_topo2 ).geometry().asWkt( 1 ), "Polygon ((10 15, 10.7 14.3, 15 10, 15 20, 10.7 15.7, 10 15))" );
1148 
1149   mLayerPolygon->undoStack()->undo(); // undo topological points
1150   mLayerPolygon->undoStack()->undo(); // undo move
1151   mLayerPolygon->undoStack()->undo(); // delete feature polygonF_topo2
1152   mLayerPolygon->undoStack()->undo(); // delete feature polygonF_topo1
1153   QCOMPARE( mLayerPolygon->featureCount(), ( long )1 );
1154 
1155 
1156   QgsProject::instance()->setTopologicalEditing( false );
1157   QgsProject::instance()->setAvoidIntersectionsMode( mode );
1158 }
testActiveLayerPriority()1159 void TestQgsVertexTool::testActiveLayerPriority()
1160 {
1161   // check that features from current layer get priority when picking points
1162 
1163   // create a temporary line layer that has a common vertex with existing line layer at (1, 1)
1164   QgsVectorLayer *layerLine2 = new QgsVectorLayer( QStringLiteral( "LineString?crs=EPSG:27700" ), QStringLiteral( "layer line 2" ), QStringLiteral( "memory" ) );
1165   QVERIFY( layerLine2->isValid() );
1166   QgsPolylineXY line1;
1167   line1 << QgsPointXY( 0, 1 ) << QgsPointXY( 1, 1 ) << QgsPointXY( 1, 0 );
1168   QgsFeature lineF1;
1169   lineF1.setGeometry( QgsGeometry::fromPolylineXY( line1 ) );
1170   layerLine2->startEditing();
1171   layerLine2->addFeature( lineF1 );
1172   const QgsFeatureId fidLineF1 = lineF1.id();
1173   QCOMPARE( layerLine2->featureCount(), ( long )1 );
1174   QgsProject::instance()->addMapLayer( layerLine2 );
1175   mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerPolygon << mLayerPoint << mLayerCompoundCurve << layerLine2 );
1176 
1177   // make one layer active and check its vertex is used
1178 
1179   mCanvas->snappingUtils()->locatorForLayer( layerLine2 )->init();
1180 
1181   mCanvas->setCurrentLayer( mLayerLine );
1182 
1183   mouseClick( 1, 1, Qt::LeftButton );
1184   mouseClick( 0, 0, Qt::LeftButton );
1185 
1186   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 0 0, 1 3)" ) );
1187   QCOMPARE( layerLine2->getFeature( fidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(0 1, 1 1, 1 0)" ) );
1188   mLayerLine->undoStack()->undo();
1189 
1190   // make the other layer active and check its vertex is used
1191 
1192   mCanvas->setCurrentLayer( layerLine2 );
1193 
1194   mouseClick( 1, 1, Qt::LeftButton );
1195   mouseClick( 0, 0, Qt::LeftButton );
1196 
1197   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
1198   QCOMPARE( layerLine2->getFeature( fidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(0 1, 0 0, 1 0)" ) );
1199   layerLine2->undoStack()->undo();
1200 
1201   mCanvas->setCurrentLayer( nullptr );
1202 
1203   // get rid of the temporary layer
1204   mCanvas->setLayers( QList<QgsMapLayer *>() << mLayerLine << mLayerPolygon << mLayerPoint << mLayerCompoundCurve );
1205   QgsProject::instance()->removeMapLayer( layerLine2 );
1206 }
1207 
testSelectedFeaturesPriority()1208 void TestQgsVertexTool::testSelectedFeaturesPriority()
1209 {
1210   // preparation: make the polygon feature touch line feature
1211   mouseClick( 4, 1, Qt::LeftButton );
1212   mouseClick( 2, 1, Qt::LeftButton );
1213 
1214   //
1215   // test that clicking a location with selected and non-selected feature will always pick the selected feature
1216   //
1217 
1218   mLayerLine->selectByIds( QgsFeatureIds() << mFidLineF1 );
1219   mLayerPolygon->selectByIds( QgsFeatureIds() );
1220 
1221   mouseClick( 2, 1, Qt::LeftButton );
1222   mouseClick( 3, 1, Qt::LeftButton );
1223 
1224   // check that move of (2,1) to (3,1) affects only line layer
1225   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(3 1, 1 1, 1 3)" ) );
1226   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((2 1, 7 1, 7 4, 4 4, 2 1))" ) );
1227   mLayerLine->undoStack()->undo();
1228 
1229   mLayerLine->selectByIds( QgsFeatureIds() );
1230   mLayerPolygon->selectByIds( QgsFeatureIds() << mFidPolygonF1 );
1231 
1232   mouseClick( 2, 1, Qt::LeftButton );
1233   mouseClick( 3, 1, Qt::LeftButton );
1234 
1235   // check that move of (2,1) to (3,1) affects only polygon layer
1236   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
1237   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((3 1, 7 1, 7 4, 4 4, 3 1))" ) );
1238   mLayerPolygon->undoStack()->undo();
1239 
1240   //
1241   // test that dragging rectangle to pick vertices in location with selected and non-selected feature
1242   // will always pick vertices only from the selected feature
1243   //
1244 
1245   mLayerLine->selectByIds( QgsFeatureIds() );
1246   mLayerPolygon->selectByIds( QgsFeatureIds() );
1247 
1248   mousePress( 1.5, 0.5, Qt::LeftButton );
1249   mouseMove( 2.5, 1.5 );
1250   mouseRelease( 2.5, 1.5, Qt::LeftButton );
1251   keyClick( Qt::Key_Delete );
1252 
1253   // check we have deleted vertex at (2,1) from both line and polygon features
1254   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(1 1, 1 3)" ) );
1255   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((7 1, 7 4, 4 4, 7 1))" ) );
1256   mLayerLine->undoStack()->undo();
1257   mLayerPolygon->undoStack()->undo();
1258 
1259   mLayerLine->selectByIds( QgsFeatureIds() << mFidLineF1 );
1260   mLayerPolygon->selectByIds( QgsFeatureIds() );
1261 
1262   mousePress( 1.5, 0.5, Qt::LeftButton );
1263   mouseMove( 2.5, 1.5 );
1264   mouseRelease( 2.5, 1.5, Qt::LeftButton );
1265   keyClick( Qt::Key_Delete );
1266 
1267   // check we have deleted vertex at (2,1) just from line feature
1268   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(1 1, 1 3)" ) );
1269   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((2 1, 7 1, 7 4, 4 4, 2 1))" ) );
1270   mLayerLine->undoStack()->undo();
1271 
1272   mLayerLine->selectByIds( QgsFeatureIds() );
1273   mLayerPolygon->selectByIds( QgsFeatureIds() << mFidPolygonF1 );
1274 
1275   mousePress( 1.5, 0.5, Qt::LeftButton );
1276   mouseMove( 2.5, 1.5 );
1277   mouseRelease( 2.5, 1.5, Qt::LeftButton );
1278   keyClick( Qt::Key_Delete );
1279 
1280   // check we have deleted vertex at (2,1) just from polygon feature
1281   QCOMPARE( mLayerLine->getFeature( mFidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
1282   QCOMPARE( mLayerPolygon->getFeature( mFidPolygonF1 ).geometry(), QgsGeometry::fromWkt( "POLYGON((7 1, 7 4, 4 4, 7 1))" ) );
1283   mLayerPolygon->undoStack()->undo();
1284 
1285   mLayerPolygon->undoStack()->undo();  // undo the initial change
1286 }
1287 
testVertexToolCompoundCurve()1288 void TestQgsVertexTool::testVertexToolCompoundCurve()
1289 {
1290   // move vertex on CompoundCurve layer
1291   // for curve
1292   mouseClick( 10, 10, Qt::LeftButton );
1293   mouseClick( 18, 17, Qt::LeftButton );
1294 
1295   QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 3 );
1296   QCOMPARE( mLayerCompoundCurve->getFeature( mFidCompoundCurveF1 ).geometry(), QgsGeometry::fromWkt( "CompoundCurve ( CircularString (14 14, 18 17, 17 10))" ) );
1297 
1298   mLayerCompoundCurve->undoStack()->undo();
1299 
1300   // for polyline
1301   mouseClick( 16, 11, Qt::LeftButton );
1302   mouseClick( 18, 13, Qt::LeftButton );
1303 
1304   QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 3 );
1305   QCOMPARE( mLayerCompoundCurve->getFeature( mFidCompoundCurveF2 ).geometry(), QgsGeometry::fromWkt( "CompoundCurve ((18 13, 17 11, 17 13))" ) );
1306 
1307   mLayerCompoundCurve->undoStack()->undo();
1308 
1309   // add vertex in compoundcurve
1310 
1311   mouseDoubleClick( 11, 13, Qt::LeftButton );
1312   mouseClick( 18, 17, Qt::LeftButton );
1313 
1314   QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 3 );
1315   QCOMPARE( mLayerCompoundCurve->getFeature( mFidCompoundCurveF1 ).geometry(),
1316             QgsGeometry::fromWkt( "CompoundCurve ( CircularString (14 14, 18 17, 13.75126265847083928 13.78427124746189492, 10 10, 17 10))" ) );
1317 
1318   mLayerCompoundCurve->undoStack()->undo();
1319 
1320   // offset of the endpoint marker - currently set as 15px away from the last vertex in direction of the line
1321   const double offsetInMapUnits = 15 * mCanvas->mapSettings().mapUnitsPerPixel();
1322 
1323   // for polyline
1324   mouseMove( 17, 13 );
1325   mouseClick( 17, 13 + offsetInMapUnits, Qt::LeftButton );
1326   mouseClick( 0, 0, Qt::LeftButton );
1327   mouseClick( 0, 0, Qt::RightButton );
1328 
1329   // verifying that it's possible to add a extra vertex to a LineString in a CompoundCurveLayer
1330   QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 3 );
1331   QCOMPARE( mLayerCompoundCurve->getFeature( mFidCompoundCurveF2 ).geometry(), QgsGeometry::fromWkt( "CompoundCurve ((16 11, 17 11, 17 13, 0 0))" ) );
1332 
1333   mLayerCompoundCurve->undoStack()->undo();
1334 
1335   //  // for compoundcurve
1336   mouseMove( 17, 10 );
1337   mouseClick( 17 + offsetInMapUnits, 10, Qt::LeftButton );
1338   mouseClick( 7, 2, Qt::LeftButton );
1339   mouseClick( 7, 1, Qt::RightButton );
1340 
1341   // verifying that it's not possible to add a extra vertex to a CircularString
1342   QCOMPARE( mLayerCompoundCurve->undoStack()->index(), 2 );
1343   QCOMPARE( mLayerCompoundCurve->getFeature( mFidCompoundCurveF1 ).geometry(), QgsGeometry::fromWkt( "CompoundCurve ( CircularString (14 14, 10 10, 17 10))" ) );
1344 }
1345 
testSelectVerticesByPolygon()1346 void TestQgsVertexTool::testSelectVerticesByPolygon()
1347 {
1348   // Test selecting vertices by polygon
1349   mouseClick( 1.2, 7.7, Qt::LeftButton, Qt::AltModifier );
1350   mouseClick( 1.2, 6.5, Qt::LeftButton );
1351   mouseClick( 1.5, 6.5, Qt::LeftButton );
1352   mouseClick( 1.5, 5.2, Qt::LeftButton );
1353   mouseClick( 1.9, 5.2, Qt::LeftButton );
1354   mouseClick( 1.9, 6.5, Qt::LeftButton );
1355   mouseClick( 1.9, 6.5, Qt::RightButton );
1356 
1357   mouseMove( 1.25, 7 );
1358   mouseClick( 1.25, 7, Qt::LeftButton );
1359   mouseMove( 1.25, 7.25 );
1360   mouseClick( 1.25, 7.25, Qt::LeftButton );
1361 
1362   QCOMPARE( mLayerMultiPolygon->undoStack()->index(), 2 );
1363   QCOMPARE( mLayerMultiPolygon->getFeature( mFidMultiPolygonF1 ).geometry(), QgsGeometry::fromWkt( "MultiPolygon (((1 5, 2 5, 2 6.5, 2 8, 1 8, 1 6.5, 1 5),(1.25 5.5, 1.25 6, 1.75 6.25, 1.75 5.75, 1.25 5.5),(1.25 7.25, 1.75 7, 1.75 7.5, 1.25 7.75, 1.25 7.25)),((3 5, 3 6.5, 3 8, 4 8, 4 6.5, 4 5, 3 5),(3.25 5.5, 3.75 5.5, 3.75 6, 3.25 6, 3.25 5.5),(3.25 7, 3.75 7, 3.75 7.5, 3.25 7.5, 3.25 7)))" ) );
1364 
1365   // Undo and reset vertex selection
1366   mLayerMultiPolygon->undoStack()->undo();
1367   mouseClick( 0.5, 7, Qt::RightButton );
1368   QCOMPARE( mLayerMultiPolygon->undoStack()->index(), 1 );
1369   QCOMPARE( mLayerMultiPolygon->getFeature( mFidMultiPolygonF1 ).geometry(), QgsGeometry::fromWkt( "MultiPolygon (((1 5, 2 5, 2 6.5, 2 8, 1 8, 1 6.5, 1 5),(1.25 5.5, 1.25 6, 1.75 6, 1.75 5.5, 1.25 5.5),(1.25 7, 1.75 7, 1.75 7.5, 1.25 7.5, 1.25 7)),((3 5, 3 6.5, 3 8, 4 8, 4 6.5, 4 5, 3 5),(3.25 5.5, 3.75 5.5, 3.75 6, 3.25 6, 3.25 5.5),(3.25 7, 3.75 7, 3.75 7.5, 3.25 7.5, 3.25 7)))" ) );
1370 }
1371 
1372 
1373 QGSTEST_MAIN( TestQgsVertexTool )
1374 #include "testqgsvertextool.moc"
1375