From 29e8fe15a1b9747bd46b5e5c0c8767d68274ed02 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Fri, 15 Mar 2024 11:58:28 +0200 Subject: [PATCH 01/16] Make move tool remove intersection --- src/app/qgsmaptoolmovefeature.cpp | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/app/qgsmaptoolmovefeature.cpp b/src/app/qgsmaptoolmovefeature.cpp index 315a331f8694..9bf0fc4a946f 100644 --- a/src/app/qgsmaptoolmovefeature.cpp +++ b/src/app/qgsmaptoolmovefeature.cpp @@ -15,6 +15,7 @@ #include "qgisapp.h" #include "qgsadvanceddigitizingdockwidget.h" +#include "qgsavoidintersectionsoperation.h" #include "qgsfeatureiterator.h" #include "qgsgeometry.h" #include "qgslogger.h" @@ -217,6 +218,20 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) request.setFilterFids( mMovedFeatures ).setNoAttributes(); QgsFeatureIterator fi = vlayer->getFeatures( request ); QgsFeature f; + + QgsAvoidIntersectionsOperation avoidIntersections; + connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted ); + + // when removing intersections ignore all features being moved + QSet ignoreFeatureIds; + for ( const auto &feat : mMovedFeatures ) + { + ignoreFeatureIds.insert( feat ); + } + + QHash > ignoreFeatures; + ignoreFeatures.insert( vlayer, ignoreFeatureIds ); + while ( fi.nextFeature( f ) ) { if ( !f.hasGeometry() ) @@ -227,6 +242,26 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) continue; const QgsFeatureId id = f.id(); + + if ( vlayer->geometryType() == Qgis::GeometryType::Polygon ) + { + const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( vlayer, id, geom, ignoreFeatures ); + + if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType ) + { + emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::Warning ); + vlayer->destroyEditCommand(); + return; + } + + if ( geom.isEmpty() ) + { + emit messageEmitted( tr( "Resulting geometry would be empty" ), Qgis::MessageLevel::Warning ); + vlayer->destroyEditCommand(); + return; + } + } + vlayer->changeGeometry( id, geom ); if ( QgsProject::instance()->topologicalEditing() ) From 5ac7a576254d29c33fb94ba480597a781a408dac Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Fri, 15 Mar 2024 12:16:52 +0200 Subject: [PATCH 02/16] Make rotate tool remove intersection --- src/app/qgsmaptoolmovefeature.cpp | 2 +- src/app/qgsmaptoolrotatefeature.cpp | 40 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/app/qgsmaptoolmovefeature.cpp b/src/app/qgsmaptoolmovefeature.cpp index 9bf0fc4a946f..db9abcb23779 100644 --- a/src/app/qgsmaptoolmovefeature.cpp +++ b/src/app/qgsmaptoolmovefeature.cpp @@ -222,7 +222,7 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) QgsAvoidIntersectionsOperation avoidIntersections; connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted ); - // when removing intersections ignore all features being moved + // when removing intersections don't check for intersections with selected features QSet ignoreFeatureIds; for ( const auto &feat : mMovedFeatures ) { diff --git a/src/app/qgsmaptoolrotatefeature.cpp b/src/app/qgsmaptoolrotatefeature.cpp index 488921f2d3fd..f37aa779d03e 100644 --- a/src/app/qgsmaptoolrotatefeature.cpp +++ b/src/app/qgsmaptoolrotatefeature.cpp @@ -23,6 +23,7 @@ #include #include "qgsadvanceddigitizingdockwidget.h" +#include "qgsavoidintersectionsoperation.h" #include "qgsmaptoolrotatefeature.h" #include "qgsfeatureiterator.h" #include "qgsgeometry.h" @@ -414,11 +415,50 @@ void QgsMapToolRotateFeature::applyRotation( double rotation ) request.setFilterFids( mRotatedFeatures ).setNoAttributes(); QgsFeatureIterator fi = vlayer->getFeatures( request ); QgsFeature f; + + QgsAvoidIntersectionsOperation avoidIntersections; + connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted ); + + // when removing intersections don't check for intersections with selected features + QSet ignoreFeatureIds; + for ( const auto &feat : mRotatedFeatures ) + { + ignoreFeatureIds.insert( feat ); + } + + QHash > ignoreFeatures; + ignoreFeatures.insert( vlayer, ignoreFeatureIds ); + + while ( fi.nextFeature( f ) ) { const QgsFeatureId id = f.id(); QgsGeometry geom = f.geometry(); geom.rotate( mRotation, anchorPoint ); + + if ( vlayer->geometryType() == Qgis::GeometryType::Polygon ) + { + const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( vlayer, id, geom, ignoreFeatures ); + + if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType) + { + emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::Warning ); + vlayer->destroyEditCommand(); + deleteRotationWidget(); + deleteRubberband(); + return; + } + + if ( geom.isEmpty() ) + { + emit messageEmitted( tr( "Resulting geometry would be empty" ), Qgis::MessageLevel::Warning ); + vlayer->destroyEditCommand(); + deleteRotationWidget(); + deleteRubberband(); + return; + } + } + vlayer->changeGeometry( id, geom ); } From 7ab90418861f82955e7768ce5d71357df274d6ad Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Fri, 15 Mar 2024 12:36:04 +0200 Subject: [PATCH 03/16] Make scale tool remove intersections --- src/app/qgsmaptoolrotatefeature.cpp | 3 +-- src/app/qgsmaptoolscalefeature.cpp | 39 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/app/qgsmaptoolrotatefeature.cpp b/src/app/qgsmaptoolrotatefeature.cpp index f37aa779d03e..3501e8a6c197 100644 --- a/src/app/qgsmaptoolrotatefeature.cpp +++ b/src/app/qgsmaptoolrotatefeature.cpp @@ -429,7 +429,6 @@ void QgsMapToolRotateFeature::applyRotation( double rotation ) QHash > ignoreFeatures; ignoreFeatures.insert( vlayer, ignoreFeatureIds ); - while ( fi.nextFeature( f ) ) { const QgsFeatureId id = f.id(); @@ -440,7 +439,7 @@ void QgsMapToolRotateFeature::applyRotation( double rotation ) { const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( vlayer, id, geom, ignoreFeatures ); - if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType) + if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType ) { emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::Warning ); vlayer->destroyEditCommand(); diff --git a/src/app/qgsmaptoolscalefeature.cpp b/src/app/qgsmaptoolscalefeature.cpp index 5d439036a04b..647dc58f6248 100644 --- a/src/app/qgsmaptoolscalefeature.cpp +++ b/src/app/qgsmaptoolscalefeature.cpp @@ -23,6 +23,7 @@ #include #include "qgsadvanceddigitizingdockwidget.h" +#include "qgsavoidintersectionsoperation.h" #include "qgsmaptoolscalefeature.h" #include "qgsfeatureiterator.h" #include "qgsgeometry.h" @@ -368,6 +369,20 @@ void QgsMapToolScaleFeature::applyScaling( double scale ) request.setFilterFids( mScaledFeatures ).setNoAttributes(); QgsFeatureIterator fi = vlayer->getFeatures( request ); QgsFeature feat; + + QgsAvoidIntersectionsOperation avoidIntersections; + connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted ); + + // when removing intersections don't check for intersections with selected features + QSet ignoreFeatureIds; + for ( const auto &f : mScaledFeatures ) + { + ignoreFeatureIds.insert( f ); + } + + QHash > ignoreFeatures; + ignoreFeatures.insert( vlayer, ignoreFeatureIds ); + while ( fi.nextFeature( feat ) ) { if ( !feat.hasGeometry() ) @@ -378,6 +393,30 @@ void QgsMapToolScaleFeature::applyScaling( double scale ) continue; const QgsFeatureId id = feat.id(); + + if ( vlayer->geometryType() == Qgis::GeometryType::Polygon ) + { + const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( vlayer, id, geom, ignoreFeatures ); + + if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType ) + { + emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::Warning ); + vlayer->destroyEditCommand(); + deleteScalingWidget(); + deleteRubberband(); + return; + } + + if ( geom.isEmpty() ) + { + emit messageEmitted( tr( "Resulting geometry would be empty" ), Qgis::MessageLevel::Warning ); + vlayer->destroyEditCommand(); + deleteScalingWidget(); + deleteRubberband(); + return; + } + } + vlayer->changeGeometry( id, geom ); } From 40852ce7e6334e781736e58e6dd305dc978129f9 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Fri, 15 Mar 2024 12:37:56 +0200 Subject: [PATCH 04/16] Make offset curve tool remove intersections --- src/app/qgsmaptooloffsetcurve.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/app/qgsmaptooloffsetcurve.cpp b/src/app/qgsmaptooloffsetcurve.cpp index bcbdc75d7c4b..038e5f5f49a8 100644 --- a/src/app/qgsmaptooloffsetcurve.cpp +++ b/src/app/qgsmaptooloffsetcurve.cpp @@ -17,6 +17,7 @@ #include #include +#include "qgsavoidintersectionsoperation.h" #include "qgsdoublespinbox.h" #include "qgsfeatureiterator.h" #include "qgsmaptooloffsetcurve.h" @@ -346,6 +347,33 @@ void QgsMapToolOffsetCurve::applyOffset( double offset, Qt::KeyboardModifiers mo destLayer->beginEditCommand( tr( "Offset curve" ) ); + QgsAvoidIntersectionsOperation avoidIntersections; + + connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted ); + + QHash > ignoreFeatures; + ignoreFeatures.insert( destLayer, QSet { mModifiedFeature } ); + + const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( destLayer, mModifiedFeature, mModifiedGeometry, ignoreFeatures ); + + if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType ) + { + emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::Warning ); + destLayer->destroyEditCommand(); + deleteRubberBandAndGeometry(); + deleteUserInputWidget(); + return; + } + + if ( mModifiedGeometry.isEmpty() ) + { + emit messageEmitted( tr( "Resulting geometry would be empty" ), Qgis::MessageLevel::Warning ); + destLayer->destroyEditCommand(); + deleteRubberBandAndGeometry(); + deleteUserInputWidget(); + return; + } + bool editOk = true; if ( !mCtrlHeldOnFirstClick && !( modifiers & Qt::ControlModifier ) ) { From 1fcd9e8cef877b6c3a3947b48f7ec8cb0122b71e Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Fri, 15 Mar 2024 12:53:52 +0200 Subject: [PATCH 05/16] Refactor showing error message when intersection could not be removed --- src/app/qgsmaptoolmovefeature.cpp | 13 +++++-------- src/app/qgsmaptooloffsetcurve.cpp | 15 +++++---------- src/app/qgsmaptoolrotatefeature.cpp | 15 +++++---------- src/app/qgsmaptoolscalefeature.cpp | 15 +++++---------- 4 files changed, 20 insertions(+), 38 deletions(-) diff --git a/src/app/qgsmaptoolmovefeature.cpp b/src/app/qgsmaptoolmovefeature.cpp index db9abcb23779..ecd85f6ebc98 100644 --- a/src/app/qgsmaptoolmovefeature.cpp +++ b/src/app/qgsmaptoolmovefeature.cpp @@ -247,16 +247,13 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) { const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( vlayer, id, geom, ignoreFeatures ); - if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType ) + if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType || geom.isEmpty() ) { - emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::Warning ); - vlayer->destroyEditCommand(); - return; - } + QString errorMessage = ( geom.isEmpty() ) ? + tr( "Resulting geometry would be empty" ) : + tr( "An error was reported during intersection removal" ); - if ( geom.isEmpty() ) - { - emit messageEmitted( tr( "Resulting geometry would be empty" ), Qgis::MessageLevel::Warning ); + emit messageEmitted( errorMessage, Qgis::MessageLevel::Warning ); vlayer->destroyEditCommand(); return; } diff --git a/src/app/qgsmaptooloffsetcurve.cpp b/src/app/qgsmaptooloffsetcurve.cpp index 038e5f5f49a8..5512406e55d8 100644 --- a/src/app/qgsmaptooloffsetcurve.cpp +++ b/src/app/qgsmaptooloffsetcurve.cpp @@ -356,18 +356,13 @@ void QgsMapToolOffsetCurve::applyOffset( double offset, Qt::KeyboardModifiers mo const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( destLayer, mModifiedFeature, mModifiedGeometry, ignoreFeatures ); - if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType ) + if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType || mModifiedGeometry.isEmpty() ) { - emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::Warning ); - destLayer->destroyEditCommand(); - deleteRubberBandAndGeometry(); - deleteUserInputWidget(); - return; - } + QString errorMessage = ( mModifiedGeometry.isEmpty() ) ? + tr( "Resulting geometry would be empty" ) : + tr( "An error was reported during intersection removal" ); - if ( mModifiedGeometry.isEmpty() ) - { - emit messageEmitted( tr( "Resulting geometry would be empty" ), Qgis::MessageLevel::Warning ); + emit messageEmitted( errorMessage, Qgis::MessageLevel::Warning ); destLayer->destroyEditCommand(); deleteRubberBandAndGeometry(); deleteUserInputWidget(); diff --git a/src/app/qgsmaptoolrotatefeature.cpp b/src/app/qgsmaptoolrotatefeature.cpp index 3501e8a6c197..0a77b1fcd3f6 100644 --- a/src/app/qgsmaptoolrotatefeature.cpp +++ b/src/app/qgsmaptoolrotatefeature.cpp @@ -439,18 +439,13 @@ void QgsMapToolRotateFeature::applyRotation( double rotation ) { const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( vlayer, id, geom, ignoreFeatures ); - if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType ) + if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType || geom.isEmpty() ) { - emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::Warning ); - vlayer->destroyEditCommand(); - deleteRotationWidget(); - deleteRubberband(); - return; - } + QString errorMessage = ( geom.isEmpty() ) ? + tr( "Resulting geometry would be empty" ) : + tr( "An error was reported during intersection removal" ); - if ( geom.isEmpty() ) - { - emit messageEmitted( tr( "Resulting geometry would be empty" ), Qgis::MessageLevel::Warning ); + emit messageEmitted( errorMessage, Qgis::MessageLevel::Warning ); vlayer->destroyEditCommand(); deleteRotationWidget(); deleteRubberband(); diff --git a/src/app/qgsmaptoolscalefeature.cpp b/src/app/qgsmaptoolscalefeature.cpp index 647dc58f6248..16fb05fe0409 100644 --- a/src/app/qgsmaptoolscalefeature.cpp +++ b/src/app/qgsmaptoolscalefeature.cpp @@ -398,18 +398,13 @@ void QgsMapToolScaleFeature::applyScaling( double scale ) { const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( vlayer, id, geom, ignoreFeatures ); - if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType ) + if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType || geom.isEmpty() ) { - emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::Warning ); - vlayer->destroyEditCommand(); - deleteScalingWidget(); - deleteRubberband(); - return; - } + QString errorMessage = ( geom.isEmpty() ) ? + tr( "Resulting geometry would be empty" ) : + tr( "An error was reported during intersection removal" ); - if ( geom.isEmpty() ) - { - emit messageEmitted( tr( "Resulting geometry would be empty" ), Qgis::MessageLevel::Warning ); + emit messageEmitted( errorMessage, Qgis::MessageLevel::Warning ); vlayer->destroyEditCommand(); deleteScalingWidget(); deleteRubberband(); From 429acede234ad489d785eb328e443e1f6c4f6f10 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Tue, 19 Mar 2024 12:45:03 +0200 Subject: [PATCH 06/16] Address review --- src/app/qgsmaptoolmovefeature.cpp | 15 ++++----------- src/app/qgsmaptooloffsetcurve.cpp | 9 ++++----- src/app/qgsmaptoolrotatefeature.cpp | 15 ++++----------- src/app/qgsmaptoolscalefeature.cpp | 15 ++++----------- 4 files changed, 16 insertions(+), 38 deletions(-) diff --git a/src/app/qgsmaptoolmovefeature.cpp b/src/app/qgsmaptoolmovefeature.cpp index ecd85f6ebc98..a12e22c2927c 100644 --- a/src/app/qgsmaptoolmovefeature.cpp +++ b/src/app/qgsmaptoolmovefeature.cpp @@ -223,14 +223,7 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted ); // when removing intersections don't check for intersections with selected features - QSet ignoreFeatureIds; - for ( const auto &feat : mMovedFeatures ) - { - ignoreFeatureIds.insert( feat ); - } - - QHash > ignoreFeatures; - ignoreFeatures.insert( vlayer, ignoreFeatureIds ); + const QHash > ignoreFeatures {{ vlayer, mMovedFeatures }}; while ( fi.nextFeature( f ) ) { @@ -249,9 +242,9 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType || geom.isEmpty() ) { - QString errorMessage = ( geom.isEmpty() ) ? - tr( "Resulting geometry would be empty" ) : - tr( "An error was reported during intersection removal" ); + const QString errorMessage = ( geom.isEmpty() ) ? + tr( "The feature cannot be moved because the resulting geometry would be empty" ) : + tr( "An error was reported during intersection removal" ); emit messageEmitted( errorMessage, Qgis::MessageLevel::Warning ); vlayer->destroyEditCommand(); diff --git a/src/app/qgsmaptooloffsetcurve.cpp b/src/app/qgsmaptooloffsetcurve.cpp index 5512406e55d8..36b6e78444cb 100644 --- a/src/app/qgsmaptooloffsetcurve.cpp +++ b/src/app/qgsmaptooloffsetcurve.cpp @@ -351,16 +351,15 @@ void QgsMapToolOffsetCurve::applyOffset( double offset, Qt::KeyboardModifiers mo connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted ); - QHash > ignoreFeatures; - ignoreFeatures.insert( destLayer, QSet { mModifiedFeature } ); + const QHash > ignoreFeatures = {{ destLayer, {mModifiedFeature} }}; const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( destLayer, mModifiedFeature, mModifiedGeometry, ignoreFeatures ); if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType || mModifiedGeometry.isEmpty() ) { - QString errorMessage = ( mModifiedGeometry.isEmpty() ) ? - tr( "Resulting geometry would be empty" ) : - tr( "An error was reported during intersection removal" ); + const QString errorMessage = ( mModifiedGeometry.isEmpty() ) ? + tr( "The feature cannot be modified because the resulting geometry would be empty" ) : + tr( "An error was reported during intersection removal" ); emit messageEmitted( errorMessage, Qgis::MessageLevel::Warning ); destLayer->destroyEditCommand(); diff --git a/src/app/qgsmaptoolrotatefeature.cpp b/src/app/qgsmaptoolrotatefeature.cpp index 0a77b1fcd3f6..d70e0cca9d0d 100644 --- a/src/app/qgsmaptoolrotatefeature.cpp +++ b/src/app/qgsmaptoolrotatefeature.cpp @@ -420,14 +420,7 @@ void QgsMapToolRotateFeature::applyRotation( double rotation ) connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted ); // when removing intersections don't check for intersections with selected features - QSet ignoreFeatureIds; - for ( const auto &feat : mRotatedFeatures ) - { - ignoreFeatureIds.insert( feat ); - } - - QHash > ignoreFeatures; - ignoreFeatures.insert( vlayer, ignoreFeatureIds ); + const QHash > ignoreFeatures {{vlayer, mRotatedFeatures}}; while ( fi.nextFeature( f ) ) { @@ -441,9 +434,9 @@ void QgsMapToolRotateFeature::applyRotation( double rotation ) if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType || geom.isEmpty() ) { - QString errorMessage = ( geom.isEmpty() ) ? - tr( "Resulting geometry would be empty" ) : - tr( "An error was reported during intersection removal" ); + const QString errorMessage = ( geom.isEmpty() ) ? + tr( "The feature cannot be rotated because the resulting geometry would be empty" ) : + tr( "An error was reported during intersection removal" ); emit messageEmitted( errorMessage, Qgis::MessageLevel::Warning ); vlayer->destroyEditCommand(); diff --git a/src/app/qgsmaptoolscalefeature.cpp b/src/app/qgsmaptoolscalefeature.cpp index 16fb05fe0409..5c36d2cbdc59 100644 --- a/src/app/qgsmaptoolscalefeature.cpp +++ b/src/app/qgsmaptoolscalefeature.cpp @@ -374,14 +374,7 @@ void QgsMapToolScaleFeature::applyScaling( double scale ) connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted ); // when removing intersections don't check for intersections with selected features - QSet ignoreFeatureIds; - for ( const auto &f : mScaledFeatures ) - { - ignoreFeatureIds.insert( f ); - } - - QHash > ignoreFeatures; - ignoreFeatures.insert( vlayer, ignoreFeatureIds ); + const QHash > ignoreFeatures {{vlayer, mScaledFeatures}}; while ( fi.nextFeature( feat ) ) { @@ -400,9 +393,9 @@ void QgsMapToolScaleFeature::applyScaling( double scale ) if ( res.operationResult == Qgis::GeometryOperationResult::InvalidInputGeometryType || geom.isEmpty() ) { - QString errorMessage = ( geom.isEmpty() ) ? - tr( "Resulting geometry would be empty" ) : - tr( "An error was reported during intersection removal" ); + const QString errorMessage = ( geom.isEmpty() ) ? + tr( "The feature cannot be scaled because the resulting geometry would be empty" ) : + tr( "An error was reported during intersection removal" ); emit messageEmitted( errorMessage, Qgis::MessageLevel::Warning ); vlayer->destroyEditCommand(); From 4011df1b3e804a47fe8a526b264bdf8626da167e Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Tue, 19 Mar 2024 18:04:25 +0200 Subject: [PATCH 07/16] Add test for avoiding intersections with move tool --- tests/src/app/testqgsmaptoolmovefeature.cpp | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/src/app/testqgsmaptoolmovefeature.cpp b/tests/src/app/testqgsmaptoolmovefeature.cpp index 693b2c1ed981..d582d556b265 100644 --- a/tests/src/app/testqgsmaptoolmovefeature.cpp +++ b/tests/src/app/testqgsmaptoolmovefeature.cpp @@ -46,6 +46,7 @@ class TestQgsMapToolMoveFeature: public QObject void testMoveFeature(); void testTopologicalMoveFeature(); + void testAvoidIntersectionAndTopoEdit(); private: QgisApp *mQgisApp = nullptr; @@ -163,5 +164,29 @@ void TestQgsMapToolMoveFeature::testTopologicalMoveFeature() QgsProject::instance()->setTopologicalEditing( topologicalEditing ); } +void TestQgsMapToolMoveFeature::testAvoidIntersectionAndTopoEdit() +{ + const bool topologicalEditing = QgsProject::instance()->topologicalEditing(); + const Qgis::AvoidIntersectionsMode mode( QgsProject::instance()-> avoidIntersectionsMode() ); + + QgsProject::instance()->setAvoidIntersectionsMode( Qgis::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer ); + QgsProject::instance()->setTopologicalEditing( true ); + + TestQgsMapToolAdvancedDigitizingUtils utils( mCaptureTool ); + + utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseClick( 2.5F, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + + const QString wkt1 = "Polygon ((1.5 1, 2 1, 2 0, 1.5 0, 1.5 1))"; + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt(), wkt1 ); + const QString wkt2 = "Polygon ((2 0, 2 1, 2 5, 3 5, 3 0, 2 0))"; + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt(), wkt2 ); + + mLayerBase->undoStack()->undo(); + + QgsProject::instance()->setTopologicalEditing( topologicalEditing ); + QgsProject::instance()->setAvoidIntersectionsMode( mode ); +} + QGSTEST_MAIN( TestQgsMapToolMoveFeature ) #include "testqgsmaptoolmovefeature.moc" From 362640d0a777c8f8fb7a305896716f94ff177d04 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Tue, 19 Mar 2024 18:46:35 +0200 Subject: [PATCH 08/16] Add test for avoid overlap + topo editing with rotate tool --- tests/src/app/testqgsmaptoolmovefeature.cpp | 2 +- tests/src/app/testqgsmaptoolrotatefeature.cpp | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/src/app/testqgsmaptoolmovefeature.cpp b/tests/src/app/testqgsmaptoolmovefeature.cpp index d582d556b265..85baa46da294 100644 --- a/tests/src/app/testqgsmaptoolmovefeature.cpp +++ b/tests/src/app/testqgsmaptoolmovefeature.cpp @@ -167,7 +167,7 @@ void TestQgsMapToolMoveFeature::testTopologicalMoveFeature() void TestQgsMapToolMoveFeature::testAvoidIntersectionAndTopoEdit() { const bool topologicalEditing = QgsProject::instance()->topologicalEditing(); - const Qgis::AvoidIntersectionsMode mode( QgsProject::instance()-> avoidIntersectionsMode() ); + const Qgis::AvoidIntersectionsMode mode( QgsProject::instance()->avoidIntersectionsMode() ); QgsProject::instance()->setAvoidIntersectionsMode( Qgis::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer ); QgsProject::instance()->setTopologicalEditing( true ); diff --git a/tests/src/app/testqgsmaptoolrotatefeature.cpp b/tests/src/app/testqgsmaptoolrotatefeature.cpp index f7fa392b76d0..a81ad50a8ab7 100644 --- a/tests/src/app/testqgsmaptoolrotatefeature.cpp +++ b/tests/src/app/testqgsmaptoolrotatefeature.cpp @@ -48,6 +48,7 @@ class TestQgsMapToolRotateFeature: public QObject void testCancelManualAnchor(); void testRotateFeatureManualAnchorAfterStartRotate(); void testRotateFeatureManualAnchorSnapping(); + void testAvoidIntersectionsAndTopoEdit(); private: QgisApp *mQgisApp = nullptr; @@ -239,6 +240,36 @@ void TestQgsMapToolRotateFeature::testRotateFeatureManualAnchorSnapping() cfg.setTolerance( tolerance ); cfg.setUnits( units ); mCanvas->snappingUtils()->setConfig( cfg ); + + +} + +void TestQgsMapToolRotateFeature::testAvoidIntersectionsAndTopoEdit() +{ + const bool topologicalEditing = QgsProject::instance()->topologicalEditing(); + const Qgis::AvoidIntersectionsMode mode( QgsProject::instance()->avoidIntersectionsMode() ); + + QgsProject::instance()->setAvoidIntersectionsMode( Qgis::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer ); + QgsProject::instance()->setTopologicalEditing( true ); + + TestQgsMapToolUtils utils( mRotateTool ); + + // remove anchor point if it exists + utils.mouseClick( 1, 1, Qt::RightButton, Qt::KeyboardModifiers(), true ); + + utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 1.6F, 0.5F ); + utils.mouseClick( 1.6F, 0.5F, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + + const QString wkt1 = "Polygon ((0.5 1.21, 1.1 0.61, 1.1 0.39, 0.5 -0.21, -0.21 0.5, 0.5 1.21))"; + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1 ); + const QString wkt2 = "Polygon ((1.1 0, 1.1 0.39, 1.1 0.61, 1.1 5, 2.1 5, 2.1 0, 1.1 0))"; + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt2 ); + + mLayerBase->undoStack()->undo(); + + QgsProject::instance()->setTopologicalEditing( topologicalEditing ); + QgsProject::instance()->setAvoidIntersectionsMode( mode ); } From 69a4be323a6fe68d1a21512a5f2d92cd7fe9c09e Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Tue, 19 Mar 2024 23:29:48 +0200 Subject: [PATCH 09/16] Add test for avoid overlap + topo editing with scale tool --- tests/src/app/testqgsmaptoolscalefeature.cpp | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/src/app/testqgsmaptoolscalefeature.cpp b/tests/src/app/testqgsmaptoolscalefeature.cpp index d32eaec8d293..f03e316a17d2 100644 --- a/tests/src/app/testqgsmaptoolscalefeature.cpp +++ b/tests/src/app/testqgsmaptoolscalefeature.cpp @@ -49,6 +49,7 @@ class TestQgsMapToolScaleFeature: public QObject void testScaleFeatureWithAnchorSetAfterStart(); void testScaleSelectedFeatures(); void testScaleFeatureManualAnchorSnapping(); + void testAvoidIntersectionsAndTopoEdit(); void testScaleFeatureDifferentCrs(); private: @@ -267,6 +268,33 @@ void TestQgsMapToolScaleFeature::testScaleFeatureManualAnchorSnapping() utils.mouseClick( 10, 25, Qt::RightButton, Qt::KeyboardModifiers(), true ); } +void TestQgsMapToolScaleFeature::testAvoidIntersectionsAndTopoEdit() +{ + const bool topologicalEditing = QgsProject::instance()->topologicalEditing(); + const Qgis::AvoidIntersectionsMode mode( QgsProject::instance()->avoidIntersectionsMode() ); + + QgsProject::instance()->setAvoidIntersectionsMode( Qgis::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer ); + QgsProject::instance()->setTopologicalEditing( true ); + + TestQgsMapToolUtils utils( mScaleTool ); + + utils.mouseMove( -1, -1 ); + utils.mouseClick( -1, -1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 2.1, 0.8 ); + utils.mouseClick( 2.1, 0.8, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + + const QString wkt1 = "Polygon ((-4.52 1.52, 1.1 1.52, 1.1 0.8, 1.52 0.8, 1.52 -4.52, -4.52 -4.52, -4.52 1.52))"; + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1 ); + const QString wkt2 = "Polygon ((1.1 0.8, 1.1 1.52, 1.1 5, 2.1 5, 2.1 0.8, 1.52 0.8, 1.1 0.8))"; + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt2 ); + + mLayerBase->undoStack()->undo(); + + // restore settings + QgsProject::instance()->setTopologicalEditing( topologicalEditing ); + QgsProject::instance()->setAvoidIntersectionsMode( mode ); +} + void TestQgsMapToolScaleFeature::testScaleFeatureDifferentCrs() { mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) ); From 562acf3d4733db25e7f1bf3c81a088a447bb8b18 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Wed, 20 Mar 2024 22:58:58 +0200 Subject: [PATCH 10/16] Add test for offset curve tool --- tests/src/app/CMakeLists.txt | 1 + tests/src/app/testqgsmaptoolmovefeature.cpp | 2 +- tests/src/app/testqgsmaptooloffsetcurve.cpp | 245 ++++++++++++++++++ tests/src/app/testqgsmaptoolrotatefeature.cpp | 4 +- 4 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 tests/src/app/testqgsmaptooloffsetcurve.cpp diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index 3cb5849ab817..333cd25ff0d9 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -41,6 +41,7 @@ set(TESTS testqgsmaptoolregularpolygon.cpp testqgsmaptoolsplitparts.cpp testqgsmaptoolsplitfeatures.cpp + testqgsmaptooloffsetcurve.cpp testqgsmeasuretool.cpp testqgsmeasurebearingtool.cpp testqgsvertexeditor.cpp diff --git a/tests/src/app/testqgsmaptoolmovefeature.cpp b/tests/src/app/testqgsmaptoolmovefeature.cpp index 85baa46da294..a15afd0c5a58 100644 --- a/tests/src/app/testqgsmaptoolmovefeature.cpp +++ b/tests/src/app/testqgsmaptoolmovefeature.cpp @@ -175,7 +175,7 @@ void TestQgsMapToolMoveFeature::testAvoidIntersectionAndTopoEdit() TestQgsMapToolAdvancedDigitizingUtils utils( mCaptureTool ); utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); - utils.mouseClick( 2.5F, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseClick( 2.5, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); const QString wkt1 = "Polygon ((1.5 1, 2 1, 2 0, 1.5 0, 1.5 1))"; QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt(), wkt1 ); diff --git a/tests/src/app/testqgsmaptooloffsetcurve.cpp b/tests/src/app/testqgsmaptooloffsetcurve.cpp new file mode 100644 index 000000000000..78c3bdbfa5c5 --- /dev/null +++ b/tests/src/app/testqgsmaptooloffsetcurve.cpp @@ -0,0 +1,245 @@ +/*************************************************************************** + testqgsmaptooloffsetcurve.cpp + -------------------------------- + Date : March 2024 + Copyright : (C) 2024 by Juho Ervasti + Email : juho dot ervasti at gispo dot fi + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgstest.h" + +#include "qgisapp.h" +#include "qgsgeometry.h" +#include "qgsmapcanvas.h" +#include "qgssettingsregistrycore.h" +#include "qgssettingsentryenumflag.h" +#include "qgsmaptooloffsetcurve.h" +#include "qgsvectorlayer.h" +#include "testqgsmaptoolutils.h" + + +/** + * \ingroup UnitTests + * This is a unit test for the offset curve tool + */ +class TestQgsMapToolOffsetCurve: public QObject +{ + Q_OBJECT + public: + TestQgsMapToolOffsetCurve(); + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + + void testOffsetCurveDefault(); + void testOffsetCurveJoinStyle(); + void testAvoidIntersectionAndTopoEdit(); + + private: + QgisApp *mQgisApp = nullptr; + QgsMapCanvas *mCanvas = nullptr; + QgsMapToolOffsetCurve *mOffsetCurveTool = nullptr; + QgsVectorLayer *mLayerBase = nullptr; +}; + +TestQgsMapToolOffsetCurve::TestQgsMapToolOffsetCurve() = default; + + +//runs before all tests +void TestQgsMapToolOffsetCurve::initTestCase() +{ + qDebug() << "TestQgsMapToolOffsetCurve::initTestCase()"; + // init QGIS's paths - true means that all path will be inited from prefix + QgsApplication::init(); + QgsApplication::initQgis(); + + // Set up the QSettings environment + QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) ); + QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) ); + QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) ); + + mQgisApp = new QgisApp(); + + mCanvas = new QgsMapCanvas(); + + mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3946" ) ) ); + + mCanvas->setFrameStyle( QFrame::NoFrame ); + mCanvas->resize( 512, 512 ); + mCanvas->setExtent( QgsRectangle( 0, 0, 8, 8 ) ); + mCanvas->show(); // to make the canvas resize + mCanvas->hide(); + + // make testing layers + mLayerBase = new QgsVectorLayer( QStringLiteral( "Polygon?crs=EPSG:3946" ), QStringLiteral( "baselayer" ), QStringLiteral( "memory" ) ); + QVERIFY( mLayerBase->isValid() ); + QgsProject::instance()->addMapLayers( QList() << mLayerBase ); + + mLayerBase->startEditing(); + const QString wkt1 = QStringLiteral( "Polygon ((0 0, 0 1, 1 1, 1 0, 0 0))" ); + QgsFeature f1; + f1.setGeometry( QgsGeometry::fromWkt( wkt1 ) ); + const QString wkt2 = QStringLiteral( "Polygon ((2 0, 2 5, 3 5, 3 0, 2 0))" ); + QgsFeature f2; + f2.setGeometry( QgsGeometry::fromWkt( wkt2 ) ); + + QgsFeatureList flist; + flist << f1 << f2; + mLayerBase->dataProvider()->addFeatures( flist ); + QCOMPARE( mLayerBase->featureCount(), 2L ); + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt(), wkt1 ); + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt(), wkt2 ); + + mCanvas->setLayers( QList() << mLayerBase ); + mCanvas->setCurrentLayer( mLayerBase ); + + // create the tool + mOffsetCurveTool = new QgsMapToolOffsetCurve( mCanvas ); + mCanvas->setMapTool( mOffsetCurveTool ); + + QCOMPARE( mCanvas->mapSettings().outputSize(), QSize( 512, 512 ) ); + QCOMPARE( mCanvas->mapSettings().visibleExtent(), QgsRectangle( 0, 0, 8, 8 ) ); + + // set default offset settings to ensure consistency + + const Qgis::JoinStyle joinStyle = Qgis::JoinStyle::Round; + const int quadSegments = 8; + const double miterLimit = 5.0; + const Qgis::EndCapStyle capStyle = Qgis::EndCapStyle::Round; + + QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->setValue( joinStyle ); + QgsSettingsRegistryCore::settingsDigitizingOffsetQuadSeg->setValue( quadSegments ); + QgsSettingsRegistryCore::settingsDigitizingOffsetMiterLimit->setValue( miterLimit ); + QgsSettingsRegistryCore::settingsDigitizingOffsetCapStyle->setValue( capStyle ); + + QCOMPARE( QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->value(), joinStyle ); + QCOMPARE( QgsSettingsRegistryCore::settingsDigitizingOffsetQuadSeg->value(), quadSegments ); + QCOMPARE( QgsSettingsRegistryCore::settingsDigitizingOffsetMiterLimit->value(), miterLimit ); + QCOMPARE( QgsSettingsRegistryCore::settingsDigitizingOffsetCapStyle->value(), capStyle ); +} + +//runs after all tests +void TestQgsMapToolOffsetCurve::cleanupTestCase() +{ + delete mOffsetCurveTool; + delete mCanvas; + QgsApplication::exitQgis(); +} + +void TestQgsMapToolOffsetCurve::testOffsetCurveDefault() +{ + TestQgsMapToolUtils utils( mOffsetCurveTool ); + + // positive offset + utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 1.25, 1.25 ); + utils.mouseClick( 1.25, 1.25, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + + const QString wkt1 = "Polygon ((0 -0.35, -0.07 -0.35, -0.14 -0.33, -0.2 -0.29, -0.25 -0.25, -0.29 -0.2, -0.33 -0.14, -0.35 -0.07, -0.35 0, -0.35 1, -0.35 1.07, -0.33 1.14, -0.29 1.2, -0.25 1.25, -0.2 1.29, -0.14 1.33, -0.07 1.35, 0 1.35, 1 1.35, 1.07 1.35, 1.14 1.33, 1.2 1.29, 1.25 1.25, 1.29 1.2, 1.33 1.14, 1.35 1.07, 1.35 1, 1.35 0, 1.35 -0.07, 1.33 -0.14, 1.29 -0.2, 1.25 -0.25, 1.2 -0.29, 1.14 -0.33, 1.07 -0.35, 1 -0.35, 0 -0.35))"; + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1); + + mLayerBase->undoStack()->undo(); + + // negative offset + utils.mouseClick( 2, 0, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 2.1, 0.1 ); + utils.mouseClick( 2.1, 0.1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + + const QString wkt2 = "Polygon ((2.09 0.09, 2.09 4.91, 2.91 4.91, 2.91 0.09, 2.09 0.09))"; + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt2); + + mLayerBase->undoStack()->undo(); +} + +void TestQgsMapToolOffsetCurve::testOffsetCurveJoinStyle() +{ + TestQgsMapToolUtils utils( mOffsetCurveTool ); + + const Qgis::JoinStyle joinStyle = QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->value(); + QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->setValue( Qgis::JoinStyle::Miter ); + + // positive offset miter + utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 1.5, 1.5 ); + utils.mouseClick( 1.5, 1.5, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + + const QString wkt1 = "Polygon ((-0.71 -0.71, -0.71 1.71, 1.71 1.71, 1.71 -0.71, -0.71 -0.71))"; + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1); + + mLayerBase->undoStack()->undo(); + + // negative offset miter + utils.mouseClick( 2, 0, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 2.25, 0.25 ); + utils.mouseClick( 2.25, 0.25, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + + const QString wkt2 = "Polygon ((2.25 0.25, 2.25 4.75, 2.75 4.75, 2.75 0.25, 2.25 0.25))"; + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt2); + + mLayerBase->undoStack()->undo(); + + QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->setValue( Qgis::JoinStyle::Bevel ); + + // negative offset bevel + utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 0.75, 0.75 ); + utils.mouseClick( 0.75, 0.75, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + + const QString wkt3 = "Polygon ((0.25 0.25, 0.25 0.75, 0.75 0.75, 0.75 0.25, 0.25 0.25))"; + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt3); + + mLayerBase->undoStack()->undo(); + + // positive offset bevel + utils.mouseClick( 2, 0, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 1.75, -0.25 ); + utils.mouseClick( 1.75, -0.25, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + + const QString wkt4 = "Polygon ((2 -0.35, 1.65 0, 1.65 5, 2 5.35, 3 5.35, 3.35 5, 3.35 0, 3 -0.35, 2 -0.35))"; + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt4); + + mLayerBase->undoStack()->undo(); + + // reset settings + QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->setValue( joinStyle ); +} + +void TestQgsMapToolOffsetCurve::testAvoidIntersectionAndTopoEdit() +{ + TestQgsMapToolUtils utils( mOffsetCurveTool ); + + const Qgis::JoinStyle joinStyle = QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->value(); + const bool topologicalEditing = QgsProject::instance()->topologicalEditing(); + const Qgis::AvoidIntersectionsMode mode( QgsProject::instance()->avoidIntersectionsMode() ); + + QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->setValue( Qgis::JoinStyle::Bevel ); + QgsProject::instance()->setAvoidIntersectionsMode( Qgis::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer ); + QgsProject::instance()->setTopologicalEditing( true ); + + utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 2, 1.75 ); + utils.mouseClick( 2, 1.75, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + + const QString wkt1 = "Polygon ((-1.25 0, -1.25 1, 0 2.25, 1 2.25, 2 1.25, 2 0, 2.25 0, 1 -1.25, 0 -1.25, -1.25 0))"; + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1); + const QString wkt2 = "Polygon ((2 0, 2 1.25, 2 5, 3 5, 3 0, 2.25 0, 2 0))"; + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt2); + + mLayerBase->undoStack()->undo(); + + // reset settings + QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->setValue( joinStyle ); + QgsProject::instance()->setTopologicalEditing( topologicalEditing ); + QgsProject::instance()->setAvoidIntersectionsMode( mode ); +} + +QGSTEST_MAIN( TestQgsMapToolOffsetCurve ) +#include "testqgsmaptooloffsetcurve.moc" diff --git a/tests/src/app/testqgsmaptoolrotatefeature.cpp b/tests/src/app/testqgsmaptoolrotatefeature.cpp index a81ad50a8ab7..4c56a83bce90 100644 --- a/tests/src/app/testqgsmaptoolrotatefeature.cpp +++ b/tests/src/app/testqgsmaptoolrotatefeature.cpp @@ -258,8 +258,8 @@ void TestQgsMapToolRotateFeature::testAvoidIntersectionsAndTopoEdit() utils.mouseClick( 1, 1, Qt::RightButton, Qt::KeyboardModifiers(), true ); utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); - utils.mouseMove( 1.6F, 0.5F ); - utils.mouseClick( 1.6F, 0.5F, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 1.6, 0.5 ); + utils.mouseClick( 1.6, 0.5, Qt::LeftButton, Qt::KeyboardModifiers(), true ); const QString wkt1 = "Polygon ((0.5 1.21, 1.1 0.61, 1.1 0.39, 0.5 -0.21, -0.21 0.5, 0.5 1.21))"; QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1 ); From 3fa16da893e1aafdfc85a0a7ea747469560be368 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Wed, 20 Mar 2024 23:06:53 +0200 Subject: [PATCH 11/16] Use cancel() if geom is empty --- src/app/qgsmaptooloffsetcurve.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/qgsmaptooloffsetcurve.cpp b/src/app/qgsmaptooloffsetcurve.cpp index 36b6e78444cb..65b80b5d573e 100644 --- a/src/app/qgsmaptooloffsetcurve.cpp +++ b/src/app/qgsmaptooloffsetcurve.cpp @@ -363,8 +363,7 @@ void QgsMapToolOffsetCurve::applyOffset( double offset, Qt::KeyboardModifiers mo emit messageEmitted( errorMessage, Qgis::MessageLevel::Warning ); destLayer->destroyEditCommand(); - deleteRubberBandAndGeometry(); - deleteUserInputWidget(); + cancel(); return; } From a4f63672194758293241b7ba9db0d8f00a8d5ac5 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Wed, 20 Mar 2024 23:07:08 +0200 Subject: [PATCH 12/16] Fix style --- tests/src/app/testqgsmaptooloffsetcurve.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/src/app/testqgsmaptooloffsetcurve.cpp b/tests/src/app/testqgsmaptooloffsetcurve.cpp index 78c3bdbfa5c5..4ab2de6d224a 100644 --- a/tests/src/app/testqgsmaptooloffsetcurve.cpp +++ b/tests/src/app/testqgsmaptooloffsetcurve.cpp @@ -144,7 +144,7 @@ void TestQgsMapToolOffsetCurve::testOffsetCurveDefault() utils.mouseClick( 1.25, 1.25, Qt::LeftButton, Qt::KeyboardModifiers(), true ); const QString wkt1 = "Polygon ((0 -0.35, -0.07 -0.35, -0.14 -0.33, -0.2 -0.29, -0.25 -0.25, -0.29 -0.2, -0.33 -0.14, -0.35 -0.07, -0.35 0, -0.35 1, -0.35 1.07, -0.33 1.14, -0.29 1.2, -0.25 1.25, -0.2 1.29, -0.14 1.33, -0.07 1.35, 0 1.35, 1 1.35, 1.07 1.35, 1.14 1.33, 1.2 1.29, 1.25 1.25, 1.29 1.2, 1.33 1.14, 1.35 1.07, 1.35 1, 1.35 0, 1.35 -0.07, 1.33 -0.14, 1.29 -0.2, 1.25 -0.25, 1.2 -0.29, 1.14 -0.33, 1.07 -0.35, 1 -0.35, 0 -0.35))"; - QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1); + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1 ); mLayerBase->undoStack()->undo(); @@ -154,7 +154,7 @@ void TestQgsMapToolOffsetCurve::testOffsetCurveDefault() utils.mouseClick( 2.1, 0.1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); const QString wkt2 = "Polygon ((2.09 0.09, 2.09 4.91, 2.91 4.91, 2.91 0.09, 2.09 0.09))"; - QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt2); + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt2 ); mLayerBase->undoStack()->undo(); } @@ -172,7 +172,7 @@ void TestQgsMapToolOffsetCurve::testOffsetCurveJoinStyle() utils.mouseClick( 1.5, 1.5, Qt::LeftButton, Qt::KeyboardModifiers(), true ); const QString wkt1 = "Polygon ((-0.71 -0.71, -0.71 1.71, 1.71 1.71, 1.71 -0.71, -0.71 -0.71))"; - QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1); + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1 ); mLayerBase->undoStack()->undo(); @@ -182,7 +182,7 @@ void TestQgsMapToolOffsetCurve::testOffsetCurveJoinStyle() utils.mouseClick( 2.25, 0.25, Qt::LeftButton, Qt::KeyboardModifiers(), true ); const QString wkt2 = "Polygon ((2.25 0.25, 2.25 4.75, 2.75 4.75, 2.75 0.25, 2.25 0.25))"; - QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt2); + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt2 ); mLayerBase->undoStack()->undo(); @@ -194,7 +194,7 @@ void TestQgsMapToolOffsetCurve::testOffsetCurveJoinStyle() utils.mouseClick( 0.75, 0.75, Qt::LeftButton, Qt::KeyboardModifiers(), true ); const QString wkt3 = "Polygon ((0.25 0.25, 0.25 0.75, 0.75 0.75, 0.75 0.25, 0.25 0.25))"; - QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt3); + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt3 ); mLayerBase->undoStack()->undo(); @@ -204,7 +204,7 @@ void TestQgsMapToolOffsetCurve::testOffsetCurveJoinStyle() utils.mouseClick( 1.75, -0.25, Qt::LeftButton, Qt::KeyboardModifiers(), true ); const QString wkt4 = "Polygon ((2 -0.35, 1.65 0, 1.65 5, 2 5.35, 3 5.35, 3.35 5, 3.35 0, 3 -0.35, 2 -0.35))"; - QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt4); + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt4 ); mLayerBase->undoStack()->undo(); @@ -229,9 +229,9 @@ void TestQgsMapToolOffsetCurve::testAvoidIntersectionAndTopoEdit() utils.mouseClick( 2, 1.75, Qt::LeftButton, Qt::KeyboardModifiers(), true ); const QString wkt1 = "Polygon ((-1.25 0, -1.25 1, 0 2.25, 1 2.25, 2 1.25, 2 0, 2.25 0, 1 -1.25, 0 -1.25, -1.25 0))"; - QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1); + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt1 ); const QString wkt2 = "Polygon ((2 0, 2 1.25, 2 5, 3 5, 3 0, 2.25 0, 2 0))"; - QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt2); + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt2 ); mLayerBase->undoStack()->undo(); From a784b0d188a8772560d6dadf99fa82378fa9a463 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Wed, 20 Mar 2024 23:57:33 +0200 Subject: [PATCH 13/16] Add test for control modifier --- tests/src/app/testqgsmaptooloffsetcurve.cpp | 52 +++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/src/app/testqgsmaptooloffsetcurve.cpp b/tests/src/app/testqgsmaptooloffsetcurve.cpp index 4ab2de6d224a..5fe67e27e320 100644 --- a/tests/src/app/testqgsmaptooloffsetcurve.cpp +++ b/tests/src/app/testqgsmaptooloffsetcurve.cpp @@ -41,6 +41,7 @@ class TestQgsMapToolOffsetCurve: public QObject void testOffsetCurveDefault(); void testOffsetCurveJoinStyle(); + void testOffsetCurveControlModifier(); void testAvoidIntersectionAndTopoEdit(); private: @@ -212,6 +213,57 @@ void TestQgsMapToolOffsetCurve::testOffsetCurveJoinStyle() QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->setValue( joinStyle ); } +void TestQgsMapToolOffsetCurve::testOffsetCurveControlModifier() +{ + TestQgsMapToolUtils utils( mOffsetCurveTool ); + + const Qgis::JoinStyle joinStyle = QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->value(); + QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->setValue( Qgis::JoinStyle::Miter ); + + // positive offset + utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 1.5, 1.5 ); + utils.mouseClick( 1.5, 1.5, Qt::LeftButton, Qt::ControlModifier, true ); + + const QString wkt1 = "Polygon ((-0.71 -0.71, -0.71 1.71, 1.71 1.71, 1.71 -0.71, -0.71 -0.71))"; + QgsFeatureIterator fi1 = mLayerBase->getFeatures(); + QgsFeature f1; + + while ( fi1.nextFeature( f1 ) ) + { + QCOMPARE( f1.geometry().asWkt( 2 ), wkt1 ); + break; + } + + const QString wkt2 = "Polygon ((0 0, 0 1, 1 1, 1 0, 0 0))"; + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt2 ); + + + mLayerBase->undoStack()->undo(); + + // negative offset + utils.mouseClick( 2, 0, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 2.25, 0.25 ); + utils.mouseClick( 2.25, 0.25, Qt::LeftButton, Qt::ControlModifier, true ); + + const QString wkt3 = "Polygon ((2.25 0.25, 2.25 4.75, 2.75 4.75, 2.75 0.25, 2.25 0.25))"; + QgsFeatureIterator fi2 = mLayerBase->getFeatures(); + QgsFeature f2; + + while ( fi2.nextFeature( f2 ) ) + { + QCOMPARE( f2.geometry().asWkt( 2 ), wkt3 ); + break; + } + + const QString wkt4 = "Polygon ((2 0, 2 5, 3 5, 3 0, 2 0))"; + QCOMPARE( mLayerBase->getFeature( 2 ).geometry().asWkt( 2 ), wkt4 ); + mLayerBase->undoStack()->undo(); + + // reset settings + QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->setValue( joinStyle ); +} + void TestQgsMapToolOffsetCurve::testAvoidIntersectionAndTopoEdit() { TestQgsMapToolUtils utils( mOffsetCurveTool ); From 9e88dad7983ddac0ee0358bdfd09c302270a6d02 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Thu, 21 Mar 2024 09:19:41 +0200 Subject: [PATCH 14/16] Offset curve: take control modifier into account when removing intersections --- src/app/qgsmaptooloffsetcurve.cpp | 6 +++++- tests/src/app/testqgsmaptooloffsetcurve.cpp | 21 ++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/app/qgsmaptooloffsetcurve.cpp b/src/app/qgsmaptooloffsetcurve.cpp index 65b80b5d573e..58bddc779883 100644 --- a/src/app/qgsmaptooloffsetcurve.cpp +++ b/src/app/qgsmaptooloffsetcurve.cpp @@ -351,7 +351,11 @@ void QgsMapToolOffsetCurve::applyOffset( double offset, Qt::KeyboardModifiers mo connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted ); - const QHash > ignoreFeatures = {{ destLayer, {mModifiedFeature} }}; + const QSet ignoredFeatures = ( QgsProject::instance()->avoidIntersectionsMode() != Qgis::AvoidIntersectionsMode::AllowIntersections && ( modifiers & Qt::ControlModifier ) ) ? + QSet() : + QSet( {mModifiedFeature} ); + + const QHash > ignoreFeatures = {{ destLayer, {ignoredFeatures} }}; const QgsAvoidIntersectionsOperation::Result res = avoidIntersections.apply( destLayer, mModifiedFeature, mModifiedGeometry, ignoreFeatures ); diff --git a/tests/src/app/testqgsmaptooloffsetcurve.cpp b/tests/src/app/testqgsmaptooloffsetcurve.cpp index 5fe67e27e320..f571ac546ffd 100644 --- a/tests/src/app/testqgsmaptooloffsetcurve.cpp +++ b/tests/src/app/testqgsmaptooloffsetcurve.cpp @@ -238,7 +238,6 @@ void TestQgsMapToolOffsetCurve::testOffsetCurveControlModifier() const QString wkt2 = "Polygon ((0 0, 0 1, 1 1, 1 0, 0 0))"; QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt2 ); - mLayerBase->undoStack()->undo(); // negative offset @@ -287,6 +286,26 @@ void TestQgsMapToolOffsetCurve::testAvoidIntersectionAndTopoEdit() mLayerBase->undoStack()->undo(); + // with control modifier + utils.mouseClick( 1, 1, Qt::LeftButton, Qt::KeyboardModifiers(), true ); + utils.mouseMove( 2, 1.75 ); + utils.mouseClick( 2, 1.75, Qt::LeftButton, Qt::ControlModifier, true ); + + const QString wkt3 = "Polygon ((-1.25 0, -1.25 1, 0 2.25, 1 2.25, 2 1.25, 2 0, 2.25 0, 1 -1.25, 0 -1.25, -1.25 0),(0 0, 1 0, 1 1, 0 1, 0 0))"; + QgsFeatureIterator fi = mLayerBase->getFeatures(); + QgsFeature f; + + while ( fi.nextFeature( f ) ) + { + QCOMPARE( f.geometry().asWkt( 2 ), wkt3 ); + break; + } + + const QString wkt4 = "Polygon ((0 0, 0 1, 1 1, 1 0, 0 0))"; + QCOMPARE( mLayerBase->getFeature( 1 ).geometry().asWkt( 2 ), wkt4 ); + + mLayerBase->undoStack()->undo(); + // reset settings QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->setValue( joinStyle ); QgsProject::instance()->setTopologicalEditing( topologicalEditing ); From 2883c8e53fb0536307edde87113605e3d9788848 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Thu, 21 Mar 2024 10:11:17 +0200 Subject: [PATCH 15/16] Remove unnecessary check for avoid overlap mode --- src/app/qgsmaptooloffsetcurve.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/qgsmaptooloffsetcurve.cpp b/src/app/qgsmaptooloffsetcurve.cpp index 58bddc779883..fa822d97952b 100644 --- a/src/app/qgsmaptooloffsetcurve.cpp +++ b/src/app/qgsmaptooloffsetcurve.cpp @@ -351,7 +351,7 @@ void QgsMapToolOffsetCurve::applyOffset( double offset, Qt::KeyboardModifiers mo connect( &avoidIntersections, &QgsAvoidIntersectionsOperation::messageEmitted, this, &QgsMapTool::messageEmitted ); - const QSet ignoredFeatures = ( QgsProject::instance()->avoidIntersectionsMode() != Qgis::AvoidIntersectionsMode::AllowIntersections && ( modifiers & Qt::ControlModifier ) ) ? + const QSet ignoredFeatures = ( modifiers & Qt::ControlModifier ) ? QSet() : QSet( {mModifiedFeature} ); From 18e78a188c121369b1cf4bfdafe05e34662d7922 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Mon, 25 Mar 2024 09:40:54 +0200 Subject: [PATCH 16/16] Change ingroup comment back --- tests/src/app/testqgsmaptooloffsetcurve.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/src/app/testqgsmaptooloffsetcurve.cpp b/tests/src/app/testqgsmaptooloffsetcurve.cpp index f571ac546ffd..f14cf642f4bd 100644 --- a/tests/src/app/testqgsmaptooloffsetcurve.cpp +++ b/tests/src/app/testqgsmaptooloffsetcurve.cpp @@ -27,7 +27,7 @@ /** * \ingroup UnitTests - * This is a unit test for the offset curve tool + * This is a unit test for the vertex tool */ class TestQgsMapToolOffsetCurve: public QObject { @@ -271,6 +271,7 @@ void TestQgsMapToolOffsetCurve::testAvoidIntersectionAndTopoEdit() const bool topologicalEditing = QgsProject::instance()->topologicalEditing(); const Qgis::AvoidIntersectionsMode mode( QgsProject::instance()->avoidIntersectionsMode() ); + // test with bevel QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle->setValue( Qgis::JoinStyle::Bevel ); QgsProject::instance()->setAvoidIntersectionsMode( Qgis::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer ); QgsProject::instance()->setTopologicalEditing( true );