Skip to content

Commit 3b36cdd

Browse files
authoredMay 1, 2018
Merge pull request #6750 from lbartoletti/visibleSnapPerf
[BUGFIX][FEATURE][NEEDS-DOCS] Disable snapping on invisible features. Second version
2 parents cec59c0 + 9f3d571 commit 3b36cdd

14 files changed

+364
-122
lines changed
 

‎python/core/qgspointlocator.sip.in

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ Get extent of the area point locator covers - if null then it caches the whole l
7171
Configure extent - if not null, it will index only that area
7272

7373
.. versionadded:: 2.14
74+
%End
75+
76+
void setRenderContext( const QgsRenderContext *context );
77+
%Docstring
78+
Configure render context - if not null, it will use to index only visible feature
79+
80+
.. versionadded:: 3.2
7481
%End
7582

7683
enum Type
@@ -199,7 +206,6 @@ Override of edgesInRect that construct rectangle from a center point and toleran
199206
find out if the point is in any polygons
200207
%End
201208

202-
203209
int cachedGeometryCount() const;
204210
%Docstring
205211
Return how many geometries are cached in the index

‎python/core/qgssnappingutils.sip.in

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ which keeps the configuration in sync with map canvas (e.g. current view, active
3535
%End
3636
public:
3737

38-
QgsSnappingUtils( QObject *parent /TransferThis/ = 0 );
38+
QgsSnappingUtils( QObject *parent /TransferThis/ = 0, bool enableSnappingForInvisibleFeature = true );
3939
%Docstring
4040
Constructor for QgsSnappingUtils
4141
%End
@@ -139,6 +139,15 @@ Get extra information about the instance
139139
QgsSnappingConfig config() const;
140140
%Docstring
141141
The snapping configuration controls the behavior of this object
142+
%End
143+
144+
void setEnableSnappingForInvisibleFeature( bool enable );
145+
%Docstring
146+
Set if invisible features must be snapped or not.
147+
148+
:param enable: Enable or not this feature
149+
150+
.. versionadded:: 3.2
142151
%End
143152

144153
public slots:

‎python/gui/qgsmapcanvassnappingutils.sip.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010

1111

12+
1213
class QgsMapCanvasSnappingUtils : QgsSnappingUtils
1314
{
1415
%Docstring

‎resources/qgis_global_settings.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ digitizing\default_snap_type=1
2020
# 2 = Project units
2121
digitizing\default_snapping_tolerance_unit=1
2222

23+
# Snap on invisble feature
24+
digitizing\snap_invisible_feature=false
25+
2326
# Default XYZ tile servers to include
2427
connections-xyz\OpenStreetMap\authcfg=
2528
connections-xyz\OpenStreetMap\password=

‎src/app/qgsoptions.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,7 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QList<QgsOpti
974974

975975
mSnappingMarkerColorButton->setColor( mSettings->value( QStringLiteral( "/qgis/digitizing/snap_color" ), QColor( Qt::magenta ) ).value<QColor>() );
976976
mSnappingTooltipsCheckbox->setChecked( mSettings->value( QStringLiteral( "/qgis/digitizing/snap_tooltip" ), false ).toBool() );
977+
mEnableSnappingOnInvisibleFeatureCheckbox->setChecked( mSettings->value( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), false ).toBool() );
977978

978979
//vertex marker
979980
mMarkersOnlyForSelectedCheckBox->setChecked( mSettings->value( QStringLiteral( "/qgis/digitizing/marker_only_for_selected" ), true ).toBool() );
@@ -1488,6 +1489,7 @@ void QgsOptions::saveOptions()
14881489

14891490
mSettings->setValue( QStringLiteral( "/qgis/digitizing/snap_color" ), mSnappingMarkerColorButton->color() );
14901491
mSettings->setValue( QStringLiteral( "/qgis/digitizing/snap_tooltip" ), mSnappingTooltipsCheckbox->isChecked() );
1492+
mSettings->setValue( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), mEnableSnappingOnInvisibleFeatureCheckbox->isChecked() );
14911493

14921494
mSettings->setValue( QStringLiteral( "/qgis/digitizing/marker_only_for_selected" ), mMarkersOnlyForSelectedCheckBox->isChecked() );
14931495

‎src/core/layertree/qgslayertreemodellegendnode.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ bool QgsSymbolLegendNode::setData( const QVariant &value, int role )
351351
vlayer->renderer()->checkLegendSymbolItem( mItem.ruleKey(), value == Qt::Checked );
352352

353353
emit dataChanged();
354+
vlayer->emitStyleChanged();
354355

355356
vlayer->triggerRepaint();
356357

‎src/core/qgspointlocator.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "qgswkbptr.h"
2222
#include "qgis.h"
2323
#include "qgslogger.h"
24+
#include "qgsrenderer.h"
2425

2526
#include <SpatialIndex.h>
2627

@@ -635,6 +636,7 @@ QgsPointLocator::QgsPointLocator( QgsVectorLayer *layer, const QgsCoordinateRefe
635636
connect( mLayer, &QgsVectorLayer::featureAdded, this, &QgsPointLocator::onFeatureAdded );
636637
connect( mLayer, &QgsVectorLayer::featureDeleted, this, &QgsPointLocator::onFeatureDeleted );
637638
connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsPointLocator::onGeometryChanged );
639+
connect( mLayer, &QgsVectorLayer::attributeValueChanged, this, &QgsPointLocator::onAttributeValueChanged );
638640
connect( mLayer, &QgsVectorLayer::dataChanged, this, &QgsPointLocator::destroyIndex );
639641
}
640642

@@ -661,6 +663,20 @@ void QgsPointLocator::setExtent( const QgsRectangle *extent )
661663
destroyIndex();
662664
}
663665

666+
void QgsPointLocator::setRenderContext( const QgsRenderContext *context )
667+
{
668+
disconnect( mLayer, &QgsVectorLayer::styleChanged, this, &QgsPointLocator::destroyIndex );
669+
670+
destroyIndex();
671+
mContext.reset( nullptr );
672+
673+
if ( context )
674+
{
675+
mContext = std::unique_ptr<QgsRenderContext>( new QgsRenderContext( *context ) );
676+
connect( mLayer, &QgsVectorLayer::styleChanged, this, &QgsPointLocator::destroyIndex );
677+
}
678+
679+
}
664680

665681
bool QgsPointLocator::init( int maxFeaturesToIndex )
666682
{
@@ -686,6 +702,7 @@ bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
686702

687703
QgsFeatureRequest request;
688704
request.setSubsetOfAttributes( QgsAttributeList() );
705+
689706
if ( mExtent )
690707
{
691708
QgsRectangle rect = *mExtent;
@@ -704,13 +721,40 @@ bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
704721
}
705722
request.setFilterRect( rect );
706723
}
724+
725+
bool filter = false;
726+
std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
727+
QgsRenderContext *ctx = nullptr;
728+
if ( mContext )
729+
{
730+
mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
731+
ctx = mContext.get();
732+
if ( renderer )
733+
{
734+
// setup scale for scale dependent visibility (rule based)
735+
renderer->startRender( *ctx, mLayer->fields() );
736+
filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
737+
request.setSubsetOfAttributes( renderer->usedAttributes( *ctx ), mLayer->fields() );
738+
}
739+
}
740+
707741
QgsFeatureIterator fi = mLayer->getFeatures( request );
708742
int indexedCount = 0;
743+
709744
while ( fi.nextFeature( f ) )
710745
{
711746
if ( !f.hasGeometry() )
712747
continue;
713748

749+
if ( filter && ctx && renderer )
750+
{
751+
ctx->expressionContext().setFeature( f );
752+
if ( !renderer->willRenderFeature( f, *ctx ) )
753+
{
754+
continue;
755+
}
756+
}
757+
714758
if ( mTransform.isValid() )
715759
{
716760
try
@@ -761,6 +805,12 @@ bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
761805
QgsPointLocator_Stream stream( dataList );
762806
mRTree = RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, stream, *mStorage, fillFactor, indexCapacity,
763807
leafCapacity, dimension, variant, indexId );
808+
809+
if ( ctx && renderer )
810+
{
811+
renderer->stopRender( *ctx );
812+
renderer.release();
813+
}
764814
return true;
765815
}
766816

@@ -792,6 +842,31 @@ void QgsPointLocator::onFeatureAdded( QgsFeatureId fid )
792842
if ( !f.hasGeometry() )
793843
return;
794844

845+
if ( mContext )
846+
{
847+
std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
848+
QgsRenderContext *ctx = nullptr;
849+
850+
mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
851+
ctx = mContext.get();
852+
if ( renderer && ctx )
853+
{
854+
bool pass = false;
855+
renderer->startRender( *ctx, mLayer->fields() );
856+
857+
ctx->expressionContext().setFeature( f );
858+
if ( !renderer->willRenderFeature( f, *ctx ) )
859+
{
860+
pass = true;
861+
}
862+
863+
renderer->stopRender( *ctx );
864+
renderer.release();
865+
if ( pass )
866+
return;
867+
}
868+
}
869+
795870
if ( mTransform.isValid() )
796871
{
797872
try
@@ -832,6 +907,7 @@ void QgsPointLocator::onFeatureDeleted( QgsFeatureId fid )
832907
mRTree->deleteData( rect2region( mGeoms[fid]->boundingBox() ), fid );
833908
delete mGeoms.take( fid );
834909
}
910+
835911
}
836912

837913
void QgsPointLocator::onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom )
@@ -841,6 +917,17 @@ void QgsPointLocator::onGeometryChanged( QgsFeatureId fid, const QgsGeometry &ge
841917
onFeatureAdded( fid );
842918
}
843919

920+
void QgsPointLocator::onAttributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
921+
{
922+
Q_UNUSED( idx );
923+
Q_UNUSED( value );
924+
if ( mContext )
925+
{
926+
onFeatureDeleted( fid );
927+
onFeatureAdded( fid );
928+
}
929+
}
930+
844931

845932
QgsPointLocator::Match QgsPointLocator::nearestVertex( const QgsPointXY &point, double tolerance, MatchFilter *filter )
846933
{

‎src/core/qgspointlocator.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818

1919
class QgsPointXY;
2020
class QgsVectorLayer;
21+
class QgsFeatureRenderer;
22+
class QgsRenderContext;
2123

2224
#include "qgis_core.h"
2325
#include "qgsfeature.h"
2426
#include "qgspointxy.h"
2527
#include "qgscoordinatereferencesystem.h"
2628
#include "qgscoordinatetransform.h"
29+
#include <memory>
2730

2831
class QgsPointLocator_VisitorNearestVertex;
2932
class QgsPointLocator_VisitorNearestEdge;
@@ -92,6 +95,12 @@ class CORE_EXPORT QgsPointLocator : public QObject
9295
*/
9396
void setExtent( const QgsRectangle *extent );
9497

98+
/**
99+
* Configure render context - if not null, it will use to index only visible feature
100+
* \since QGIS 3.2
101+
*/
102+
void setRenderContext( const QgsRenderContext *context );
103+
95104
/**
96105
* The type of a snap result or the filter type for a snap request.
97106
*/
@@ -251,8 +260,6 @@ class CORE_EXPORT QgsPointLocator : public QObject
251260
//! find out if the point is in any polygons
252261
MatchList pointInPolygon( const QgsPointXY &point );
253262

254-
//
255-
256263
/**
257264
* Return how many geometries are cached in the index
258265
* \since QGIS 2.14
@@ -267,6 +274,7 @@ class CORE_EXPORT QgsPointLocator : public QObject
267274
void onFeatureAdded( QgsFeatureId fid );
268275
void onFeatureDeleted( QgsFeatureId fid );
269276
void onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom );
277+
void onAttributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value );
270278

271279
private:
272280
//! Storage manager
@@ -278,11 +286,14 @@ class CORE_EXPORT QgsPointLocator : public QObject
278286
//! flag whether the layer is currently empty (i.e. mRTree is null but it is not necessary to rebuild it)
279287
bool mIsEmptyLayer;
280288

289+
281290
//! R-tree containing spatial index
282291
QgsCoordinateTransform mTransform;
283292
QgsVectorLayer *mLayer = nullptr;
284293
QgsRectangle *mExtent = nullptr;
285294

295+
std::unique_ptr<QgsRenderContext> mContext;
296+
286297
friend class QgsPointLocator_VisitorNearestVertex;
287298
friend class QgsPointLocator_VisitorNearestEdge;
288299
friend class QgsPointLocator_VisitorArea;

‎src/core/qgssnappingutils.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
#include "qgsproject.h"
2020
#include "qgsvectorlayer.h"
2121
#include "qgslogger.h"
22+
#include "qgsrenderer.h"
2223

23-
QgsSnappingUtils::QgsSnappingUtils( QObject *parent )
24+
QgsSnappingUtils::QgsSnappingUtils( QObject *parent, bool enableSnappingForInvisibleFeature )
2425
: QObject( parent )
2526
, mSnappingConfig( QgsProject::instance() )
27+
, mEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
2628
{
2729
}
2830

@@ -92,7 +94,6 @@ bool QgsSnappingUtils::isIndexPrepared( QgsVectorLayer *vl, const QgsRectangle &
9294
return ( mStrategy == IndexHybrid || mStrategy == IndexExtent ) && loc->hasIndex() && ( !loc->extent() || loc->extent()->contains( aoi ) ); // the index - even if it exists - is not suitable
9395
}
9496

95-
9697
static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPointXY &pt, const QgsPointLocator::MatchList &segments )
9798
{
9899
if ( segments.isEmpty() )
@@ -156,9 +157,9 @@ static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPointXY
156157
return QgsPointLocator::Match( QgsPointLocator::Vertex, nullptr, 0, std::sqrt( minSqrDist ), minP );
157158
}
158159

159-
160160
static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointLocator::Match &candidateMatch, double maxDistance )
161161
{
162+
162163
// is candidate match relevant?
163164
if ( !candidateMatch.isValid() || candidateMatch.distance() > maxDistance )
164165
return;
@@ -174,7 +175,6 @@ static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointL
174175
bestMatch = candidateMatch; // the other match is better!
175176
}
176177

177-
178178
static void _updateBestMatch( QgsPointLocator::Match &bestMatch, const QgsPointXY &pointMap, QgsPointLocator *loc, QgsPointLocator::Types type, double tolerance, QgsPointLocator::MatchFilter *filter )
179179
{
180180
if ( type & QgsPointLocator::Vertex )
@@ -329,7 +329,6 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
329329
return QgsPointLocator::Match();
330330
}
331331

332-
333332
void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers )
334333
{
335334
if ( mIsIndexing )
@@ -341,6 +340,7 @@ void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers
341340
Q_FOREACH ( const LayerAndAreaOfInterest &entry, layers )
342341
{
343342
QgsVectorLayer *vl = entry.first;
343+
344344
if ( vl->geometryType() == QgsWkbTypes::NullGeometry || mStrategy == IndexNeverFull )
345345
continue;
346346

@@ -359,7 +359,15 @@ void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers
359359
QgsVectorLayer *vl = entry.first;
360360
QTime tt;
361361
tt.start();
362+
362363
QgsPointLocator *loc = locatorForLayer( vl );
364+
365+
if ( !mEnableSnappingForInvisibleFeature )
366+
{
367+
QgsRenderContext ctx = QgsRenderContext::fromMapSettings( mMapSettings );
368+
loc->setRenderContext( &ctx );
369+
}
370+
363371
if ( mStrategy == IndexExtent )
364372
{
365373
QgsRectangle rect( mMapSettings.extent() );
@@ -428,6 +436,11 @@ QgsSnappingConfig QgsSnappingUtils::config() const
428436
return mSnappingConfig;
429437
}
430438

439+
void QgsSnappingUtils::setEnableSnappingForInvisibleFeature( bool enable )
440+
{
441+
mEnableSnappingForInvisibleFeature = enable;
442+
}
443+
431444
void QgsSnappingUtils::setConfig( const QgsSnappingConfig &config )
432445
{
433446
if ( mSnappingConfig == config )

‎src/core/qgssnappingutils.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
5353
public:
5454

5555
//! Constructor for QgsSnappingUtils
56-
QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr );
56+
QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr, bool enableSnappingForInvisibleFeature = true );
5757
~QgsSnappingUtils() override;
5858

5959
// main actions
@@ -159,6 +159,15 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
159159
*/
160160
QgsSnappingConfig config() const;
161161

162+
/**
163+
* Set if invisible features must be snapped or not.
164+
*
165+
* \param enable Enable or not this feature
166+
*
167+
* \since QGIS 3.2
168+
*/
169+
void setEnableSnappingForInvisibleFeature( bool enable );
170+
162171
public slots:
163172

164173
/**
@@ -242,6 +251,10 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
242251

243252
//! internal flag that an indexing process is going on. Prevents starting two processes in parallel.
244253
bool mIsIndexing = false;
254+
255+
//! Disable or not the snapping on all features. By default is always true except for non visible features on map canvas.
256+
bool mEnableSnappingForInvisibleFeature = true;
257+
245258
};
246259

247260

‎src/gui/qgsmapcanvassnappingutils.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616

1717
#include "qgsmapcanvas.h"
1818
#include "qgsvectorlayer.h"
19+
#include "qgssettings.h"
1920

2021
#include <QApplication>
2122
#include <QProgressDialog>
2223

2324
QgsMapCanvasSnappingUtils::QgsMapCanvasSnappingUtils( QgsMapCanvas *canvas, QObject *parent )
24-
: QgsSnappingUtils( parent )
25+
: QgsSnappingUtils( parent, QgsSettings().value( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), false ).toBool() )
2526
, mCanvas( canvas )
2627

2728
{
@@ -30,13 +31,15 @@ QgsMapCanvasSnappingUtils::QgsMapCanvasSnappingUtils( QgsMapCanvas *canvas, QObj
3031
connect( canvas, &QgsMapCanvas::layersChanged, this, &QgsMapCanvasSnappingUtils::canvasMapSettingsChanged );
3132
connect( canvas, &QgsMapCanvas::currentLayerChanged, this, &QgsMapCanvasSnappingUtils::canvasCurrentLayerChanged );
3233
connect( canvas, &QgsMapCanvas::transformContextChanged, this, &QgsMapCanvasSnappingUtils::canvasTransformContextChanged );
34+
connect( canvas, &QgsMapCanvas::mapToolSet, this, &QgsMapCanvasSnappingUtils::canvasMapToolChanged );
3335
canvasMapSettingsChanged();
3436
canvasCurrentLayerChanged();
3537
}
3638

3739
void QgsMapCanvasSnappingUtils::canvasMapSettingsChanged()
3840
{
3941
setMapSettings( mCanvas->mapSettings() );
42+
setEnableSnappingForInvisibleFeature( QgsSettings().value( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), false ).toBool() );
4043
}
4144

4245
void QgsMapCanvasSnappingUtils::canvasTransformContextChanged()
@@ -51,6 +54,11 @@ void QgsMapCanvasSnappingUtils::canvasCurrentLayerChanged()
5154
setCurrentLayer( qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() ) );
5255
}
5356

57+
void QgsMapCanvasSnappingUtils::canvasMapToolChanged()
58+
{
59+
setEnableSnappingForInvisibleFeature( QgsSettings().value( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), false ).toBool() );
60+
}
61+
5462
void QgsMapCanvasSnappingUtils::prepareIndexStarting( int count )
5563
{
5664
QApplication::setOverrideCursor( Qt::WaitCursor );

‎src/gui/qgsmapcanvassnappingutils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "qgssnappingutils.h"
1919
#include "qgis_gui.h"
2020

21+
#include "qgsmaptool.h"
22+
2123
class QgsMapCanvas;
2224

2325
class QProgressDialog;
@@ -42,6 +44,7 @@ class GUI_EXPORT QgsMapCanvasSnappingUtils : public QgsSnappingUtils
4244
void canvasMapSettingsChanged();
4345
void canvasTransformContextChanged();
4446
void canvasCurrentLayerChanged();
47+
void canvasMapToolChanged();
4548

4649
private:
4750
QgsMapCanvas *mCanvas = nullptr;

‎src/ui/qgsoptionsbase.ui

Lines changed: 111 additions & 106 deletions
Large diffs are not rendered by default.

‎tests/src/core/testqgssnappingutils.cpp

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
#include "qgsproject.h"
2525
#include "qgssnappingutils.h"
2626
#include "qgssnappingconfig.h"
27-
27+
#include "qgscategorizedsymbolrenderer.h"
28+
#include "qgssettings.h"
29+
#include "qgslayertree.h"
30+
#include "qgslayertreemodel.h"
2831

2932
struct FilterExcludePoint : public QgsPointLocator::MatchFilter
3033
{
@@ -44,6 +47,7 @@ class TestQgsSnappingUtils : public QObject
4447

4548
private:
4649
QgsVectorLayer *mVL = nullptr;
50+
QgsFeature f1, f2;
4751
private slots:
4852

4953
void initTestCase()
@@ -60,19 +64,26 @@ class TestQgsSnappingUtils : public QObject
6064
// \ |
6165
// \|
6266
// + (1,0)
63-
mVL = new QgsVectorLayer( QStringLiteral( "Polygon" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
64-
QgsFeature ff( 0 );
67+
mVL = new QgsVectorLayer( QStringLiteral( "Polygon?field=fld:int" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
68+
int idx = mVL->fields().indexFromName( QStringLiteral( "fld" ) );
69+
QVERIFY( idx != -1 );
70+
f1.initAttributes( 1 );
71+
f2.initAttributes( 1 );
72+
6573
QgsPolygonXY polygon;
6674
QgsPolylineXY polyline;
6775
polyline << QgsPointXY( 0, 1 ) << QgsPointXY( 1, 0 ) << QgsPointXY( 1, 1 ) << QgsPointXY( 0, 1 );
6876
polygon << polyline;
6977
QgsGeometry polygonGeom = QgsGeometry::fromPolygonXY( polygon );
70-
ff.setGeometry( polygonGeom );
78+
f1.setGeometry( polygonGeom );
79+
f1.setAttribute( idx, QVariant( 2 ) );
7180
QgsFeatureList flist;
72-
flist << ff;
81+
flist << f1;
82+
7383
mVL->dataProvider()->addFeatures( flist );
7484

7585
QgsProject::instance()->addMapLayer( mVL );
86+
7687
}
7788

7889
void cleanupTestCase()
@@ -129,6 +140,75 @@ class TestQgsSnappingUtils : public QObject
129140
QVERIFY( !m3.isValid() );
130141
}
131142

143+
void testSnapInvisible()
144+
{
145+
QgsCategorizedSymbolRenderer *renderer = new QgsCategorizedSymbolRenderer();
146+
renderer->setClassAttribute( QStringLiteral( "fld" ) );
147+
renderer->setSourceSymbol( QgsSymbol::defaultSymbol( QgsWkbTypes::PolygonGeometry ) );
148+
renderer->addCategory( QgsRendererCategory( "2", QgsSymbol::defaultSymbol( QgsWkbTypes::PolygonGeometry ), QStringLiteral( "2" ) ) );
149+
mVL->setRenderer( renderer );
150+
151+
//create legend with symbology nodes for categorized renderer
152+
QgsLayerTree *root = new QgsLayerTree();
153+
QgsLayerTreeLayer *n = new QgsLayerTreeLayer( mVL );
154+
root->addChildNode( n );
155+
QgsLayerTreeModel *m = new QgsLayerTreeModel( root, nullptr );
156+
m->refreshLayerLegend( n );
157+
158+
//test that all nodes are initially checked
159+
QList<QgsLayerTreeModelLegendNode *> nodes = m->layerLegendNodes( n );
160+
QCOMPARE( nodes.length(), 1 );
161+
Q_FOREACH ( QgsLayerTreeModelLegendNode *ln, nodes )
162+
{
163+
QVERIFY( ln->data( Qt::CheckStateRole ) == Qt::Checked );
164+
}
165+
166+
167+
QgsMapSettings mapSettings;
168+
mapSettings.setOutputSize( QSize( 100, 100 ) );
169+
mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) );
170+
QVERIFY( mapSettings.hasValidSettings() );
171+
172+
QgsSnappingUtils u;
173+
u.setMapSettings( mapSettings );
174+
u.setEnableSnappingForInvisibleFeature( false );
175+
u.setCurrentLayer( mVL );
176+
177+
// first try with no snapping enabled
178+
QgsSnappingConfig snappingConfig = u.config();
179+
snappingConfig.setEnabled( false );
180+
snappingConfig.setTolerance( 10 );
181+
snappingConfig.setUnits( QgsTolerance::Pixels );
182+
snappingConfig.setMode( QgsSnappingConfig::ActiveLayer );
183+
u.setConfig( snappingConfig );
184+
185+
QgsPointLocator::Match m0 = u.snapToMap( QPoint( 2, 2 ) );
186+
QVERIFY( !m0.isValid() );
187+
QVERIFY( !m0.hasVertex() );
188+
189+
// now enable snapping
190+
snappingConfig.setEnabled( true );
191+
snappingConfig.setType( QgsSnappingConfig::Vertex );
192+
u.setConfig( snappingConfig );
193+
194+
QgsPointLocator::Match m5 = u.snapToMap( QPoint( 2, 2 ) );
195+
QVERIFY( m5.isValid() );
196+
QVERIFY( m5.hasVertex() );
197+
QCOMPARE( m5.point(), QgsPointXY( 0, 1 ) );
198+
199+
//uncheck all and test that all nodes are unchecked
200+
static_cast< QgsSymbolLegendNode * >( nodes.at( 0 ) )->uncheckAllItems();
201+
Q_FOREACH ( QgsLayerTreeModelLegendNode *ln, nodes )
202+
{
203+
QVERIFY( ln->data( Qt::CheckStateRole ) == Qt::Unchecked );
204+
}
205+
mVL->dataChanged(); /* refresh index */
206+
207+
m5 = u.snapToMap( QPoint( 2, 2 ) );
208+
QVERIFY( !m5.isValid() );
209+
QVERIFY( !m5.hasVertex() );
210+
}
211+
132212
void testSnapModeAll()
133213
{
134214
QgsMapSettings mapSettings;

0 commit comments

Comments
 (0)
Please sign in to comment.