Skip to content

Commit cd9f47a

Browse files
committedMay 19, 2016
[FEATURE] Improved map select tool behaviour
Implements the improved mouse/key modifier behaviour discussed in: http://osgeo-org.1560.x6.nabble.com/Key-modifiers-with-selection-tc5239653.html Specifically, For click-and-drag selections: - holding shift = add to selection - holding ctrl = substract from selection - holding ctrl+shift = intersect with current selection - holding alt (can be used with shift/ctrl too) = change from "intersects" to "fully contains" selection mode For single-click selections: - holding shift or ctrl = toggle whether feature is selected (ie either add to current selection or remove from current selection) This brings the canvas behaviour into line with other design apps and also with the composer behaviour. (fix #2666)
1 parent c0799d4 commit cd9f47a

7 files changed

+125
-74
lines changed
 

‎src/app/qgsmaptoolselect.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ void QgsMapToolSelect::canvasReleaseEvent( QgsMapMouseEvent* e )
5050
QgsMapToolSelectUtils::expandSelectRectangle( selectRect, vlayer, e->pos() );
5151
QgsMapToolSelectUtils::setRubberBand( mCanvas, selectRect, &rubberBand );
5252
QgsGeometry* selectGeom = rubberBand.asGeometry();
53-
bool doDifference = e->modifiers() & Qt::ControlModifier;
54-
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, selectGeom, false, doDifference, true );
53+
QgsMapToolSelectUtils::selectSingleFeature( mCanvas, selectGeom, e );
5554
delete selectGeom;
5655
rubberBand.reset( QGis::Polygon );
5756
}

‎src/app/qgsmaptoolselectfreehand.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ void QgsMapToolSelectFreehand::canvasReleaseEvent( QgsMapMouseEvent* e )
8585
if ( mRubberBand->numberOfVertices() > 2 )
8686
{
8787
QgsGeometry* shapeGeom = mRubberBand->asGeometry();
88-
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, shapeGeom,
89-
e->modifiers() & Qt::ShiftModifier,
90-
e->modifiers() & Qt::ControlModifier, singleSelect );
88+
if ( singleSelect )
89+
QgsMapToolSelectUtils::selectSingleFeature( mCanvas, shapeGeom, e );
90+
else
91+
QgsMapToolSelectUtils::selectMultipleFeatures( mCanvas, shapeGeom, e );
9192
delete shapeGeom;
9293
}
9394

‎src/app/qgsmaptoolselectpolygon.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ void QgsMapToolSelectPolygon::canvasPressEvent( QgsMapMouseEvent* e )
5454
if ( mRubberBand->numberOfVertices() > 2 )
5555
{
5656
QgsGeometry* polygonGeom = mRubberBand->asGeometry();
57-
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, polygonGeom, e );
57+
QgsMapToolSelectUtils::selectMultipleFeatures( mCanvas, polygonGeom, e );
5858
delete polygonGeom;
5959
}
6060
mRubberBand->reset( QGis::Polygon );

‎src/app/qgsmaptoolselectradius.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ void QgsMapToolSelectRadius::canvasReleaseEvent( QgsMapMouseEvent* e )
9292
setRadiusRubberBand( radiusEdge );
9393
}
9494
QgsGeometry* radiusGeometry = mRubberBand->asGeometry();
95-
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, radiusGeometry, e );
95+
QgsMapToolSelectUtils::selectMultipleFeatures( mCanvas, radiusGeometry, e );
9696
delete radiusGeometry;
9797
mRubberBand->reset( QGis::Polygon );
9898
delete mRubberBand;

‎src/app/qgsmaptoolselectrectangle.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,10 @@ void QgsMapToolSelectFeatures::canvasReleaseEvent( QgsMapMouseEvent* e )
106106
QgsGeometry* selectGeom = mRubberBand->asGeometry();
107107
if ( !mDragging )
108108
{
109-
bool doDifference = e->modifiers() & Qt::ControlModifier;
110-
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, selectGeom, false, doDifference, true );
109+
QgsMapToolSelectUtils::selectSingleFeature( mCanvas, selectGeom, e );
111110
}
112111
else
113-
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, selectGeom, e );
112+
QgsMapToolSelectUtils::selectMultipleFeatures( mCanvas, selectGeom, e );
114113

115114
delete selectGeom;
116115

‎src/app/qgsmaptoolselectutils.cpp

Lines changed: 73 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,80 @@ void QgsMapToolSelectUtils::expandSelectRectangle( QRect& selectRect,
8585
selectRect.setBottom( point.y() + boxSize );
8686
}
8787

88-
void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas,
89-
QgsGeometry* selectGeometry,
90-
bool doContains,
91-
bool doDifference,
92-
bool singleSelect )
88+
void QgsMapToolSelectUtils::selectMultipleFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent* e )
9389
{
94-
if ( selectGeometry->type() != QGis::Polygon )
90+
QgsVectorLayer::SelectBehaviour behaviour = QgsVectorLayer::SetSelection;
91+
if ( e->modifiers() & Qt::ShiftModifier && e->modifiers() & Qt::ControlModifier )
92+
behaviour = QgsVectorLayer::IntersectSelection;
93+
else if ( e->modifiers() & Qt::ShiftModifier )
94+
behaviour = QgsVectorLayer::AddToSelection;
95+
else if ( e->modifiers() & Qt::ControlModifier )
96+
behaviour = QgsVectorLayer::RemoveFromSelection;
97+
98+
bool doContains = e->modifiers() & Qt::AltModifier;
99+
setSelectedFeatures( canvas, selectGeometry, behaviour, doContains );
100+
}
101+
102+
void QgsMapToolSelectUtils::selectSingleFeature( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent* e )
103+
{
104+
QgsVectorLayer* vlayer = QgsMapToolSelectUtils::getCurrentVectorLayer( canvas );
105+
if ( !vlayer )
106+
return;
107+
108+
QApplication::setOverrideCursor( Qt::WaitCursor );
109+
110+
QgsFeatureIds selectedFeatures = getMatchingFeatures( canvas, selectGeometry, false, true );
111+
if ( selectedFeatures.isEmpty() )
112+
{
113+
QApplication::restoreOverrideCursor();
95114
return;
115+
}
116+
117+
QgsVectorLayer::SelectBehaviour behaviour = QgsVectorLayer::SetSelection;
96118

119+
//either shift or control modifier switches to "toggle" selection mode
120+
if ( e->modifiers() & Qt::ShiftModifier || e->modifiers() & Qt::ControlModifier )
121+
{
122+
QgsFeatureId selectId = *selectedFeatures.constBegin();
123+
QgsFeatureIds layerSelectedFeatures = vlayer->selectedFeaturesIds();
124+
if ( layerSelectedFeatures.contains( selectId ) )
125+
behaviour = QgsVectorLayer::RemoveFromSelection;
126+
else
127+
behaviour = QgsVectorLayer::AddToSelection;
128+
}
129+
130+
vlayer->selectByIds( selectedFeatures, behaviour );
131+
132+
QApplication::restoreOverrideCursor();
133+
}
134+
135+
void QgsMapToolSelectUtils::setSelectedFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry,
136+
QgsVectorLayer::SelectBehaviour selectBehaviour, bool doContains, bool singleSelect )
137+
{
97138
QgsVectorLayer* vlayer = QgsMapToolSelectUtils::getCurrentVectorLayer( canvas );
98139
if ( !vlayer )
99140
return;
100141

142+
QApplication::setOverrideCursor( Qt::WaitCursor );
143+
144+
QgsFeatureIds selectedFeatures = getMatchingFeatures( canvas, selectGeometry, doContains, singleSelect );
145+
vlayer->selectByIds( selectedFeatures, selectBehaviour );
146+
147+
QApplication::restoreOverrideCursor();
148+
}
149+
150+
151+
QgsFeatureIds QgsMapToolSelectUtils::getMatchingFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, bool doContains, bool singleSelect )
152+
{
153+
QgsFeatureIds newSelectedFeatures;
154+
155+
if ( selectGeometry->type() != QGis::Polygon )
156+
return newSelectedFeatures;
157+
158+
QgsVectorLayer* vlayer = QgsMapToolSelectUtils::getCurrentVectorLayer( canvas );
159+
if ( !vlayer )
160+
return newSelectedFeatures;
161+
101162
// toLayerCoordinates will throw an exception for any 'invalid' points in
102163
// the rubber band.
103164
// For example, if you project a world map onto a globe using EPSG 2163
@@ -121,16 +182,13 @@ void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas,
121182
QObject::tr( "Selection extends beyond layer's coordinate system" ),
122183
QgsMessageBar::WARNING,
123184
QgisApp::instance()->messageTimeout() );
124-
return;
185+
return newSelectedFeatures;
125186
}
126187
}
127188

128-
QApplication::setOverrideCursor( Qt::WaitCursor );
129-
130-
QgsDebugMsg( "Selection layer: " + vlayer->name() );
131-
QgsDebugMsg( "Selection polygon: " + selectGeomTrans.exportToWkt() );
132-
QgsDebugMsg( "doContains: " + QString( doContains ? "T" : "F" ) );
133-
QgsDebugMsg( "doDifference: " + QString( doDifference ? "T" : "F" ) );
189+
QgsDebugMsgLevel( "Selection layer: " + vlayer->name(), 3 );
190+
QgsDebugMsgLevel( "Selection polygon: " + selectGeomTrans.exportToWkt(), 3 );
191+
QgsDebugMsgLevel( "doContains: " + QString( doContains ? "T" : "F" ), 3 );
134192

135193
QgsRenderContext context = QgsRenderContext::fromMapSettings( canvas->mapSettings() );
136194
context.expressionContext() << QgsExpressionContextUtils::layerScope( vlayer );
@@ -148,7 +206,6 @@ void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas,
148206

149207
QgsFeatureIterator fit = vlayer->getFeatures( request );
150208

151-
QgsFeatureIds newSelectedFeatures;
152209
QgsFeature f;
153210
QgsFeatureId closestFeatureId = 0;
154211
bool foundSingleFeature = false;
@@ -196,40 +253,7 @@ void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas,
196253

197254
QgsDebugMsg( "Number of new selected features: " + QString::number( newSelectedFeatures.size() ) );
198255

199-
if ( doDifference )
200-
{
201-
QgsFeatureIds layerSelectedFeatures = vlayer->selectedFeaturesIds();
202-
203-
QgsFeatureIds selectedFeatures;
204-
QgsFeatureIds deselectedFeatures;
205-
206-
QgsFeatureIds::const_iterator i = newSelectedFeatures.constEnd();
207-
while ( i != newSelectedFeatures.constBegin() )
208-
{
209-
--i;
210-
if ( layerSelectedFeatures.contains( *i ) )
211-
{
212-
deselectedFeatures.insert( *i );
213-
}
214-
else
215-
{
216-
selectedFeatures.insert( *i );
217-
}
218-
}
219-
220-
vlayer->modifySelection( selectedFeatures, deselectedFeatures );
221-
}
222-
else
223-
{
224-
vlayer->selectByIds( newSelectedFeatures );
225-
}
226-
227-
QApplication::restoreOverrideCursor();
256+
return newSelectedFeatures;
228257
}
229258

230-
void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent * e )
231-
{
232-
bool doContains = e->modifiers() & Qt::ShiftModifier;
233-
bool doDifference = e->modifiers() & Qt::ControlModifier;
234-
setSelectFeatures( canvas, selectGeometry, doContains, doDifference );
235-
}
259+

‎src/app/qgsmaptoolselectutils.h

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ email : jpalmer at linz dot govt dot nz
1616
#ifndef QGSMAPTOOLSELECTUTILS_H
1717
#define QGSMAPTOOLSELECTUTILS_H
1818

19+
#include "qgsvectorlayer.h"
1920
#include <Qt>
2021
#include <QRect>
2122
#include <QPoint>
@@ -31,34 +32,61 @@ class QgsRubberBand;
3132
*/
3233
namespace QgsMapToolSelectUtils
3334
{
35+
/** Calculates a list of features matching a selection geometry and flags.
36+
* @param canvas the map canvas used to get the current selected vector layer and
37+
for any required geometry transformations
38+
* @param selectGeometry the geometry to select the layers features. This geometry
39+
must be in terms of the canvas coordinate system.
40+
* @param doContains features will only be selected if fully contained within
41+
the selection rubber band (otherwise intersection is enough).
42+
* @param singleSelect only selects the closest feature to the selectGeometry.
43+
* @returns list of features which match search geometry and parameters
44+
* @note added in QGIS 2.16
45+
*/
46+
QgsFeatureIds getMatchingFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, bool doContains, bool singleSelect );
47+
3448
/**
3549
Selects the features within currently selected layer.
36-
@param canvas The map canvas used to get the current selected vector layer and
50+
@param canvas the map canvas used to get the current selected vector layer and
3751
for any required geometry transformations
38-
@param selectGeometry The geometry to select the layers features. This geometry
52+
@param selectGeometry the geometry to select the layers features. This geometry
3953
must be in terms of the canvas coordinate system.
40-
@param doContains Features will only be selected if fully contained within
54+
@param selectBehaviour behaviour of select (ie replace selection, add to selection)
55+
@param doContains features will only be selected if fully contained within
4156
the selection rubber band (otherwise intersection is enough).
42-
@param doDifference Take the symmetric difference of the current selected
43-
features and the new features found within the provided selectGeometry.
44-
@param singleSelect Only selects the closest feature to the selectGeometry.
57+
@param singleSelect only selects the closest feature to the selectGeometry.
58+
@note added in QGIS 2.16
59+
*/
60+
void setSelectedFeatures( QgsMapCanvas* canvas,
61+
QgsGeometry* selectGeometry,
62+
QgsVectorLayer::SelectBehaviour selectBehaviour = QgsVectorLayer::SetSelection,
63+
bool doContains = true,
64+
bool singleSelect = false );
65+
66+
/**
67+
Selects multiple matching features from within currently selected layer.
68+
@param canvas the map canvas used to get the current selected vector layer and
69+
for any required geometry transformations
70+
@param selectGeometry the geometry to select the layers features. This geometry
71+
must be in terms of the canvas coordinate system.
72+
@param e MouseEvents are used to determine the current selection
73+
operations (add, subtract, contains)
74+
@note added in QGIS 2.16
75+
@see selectSingleFeature()
4576
*/
46-
void setSelectFeatures( QgsMapCanvas* canvas,
47-
QgsGeometry* selectGeometry,
48-
bool doContains = true,
49-
bool doDifference = false,
50-
bool singleSelect = false );
77+
void selectMultipleFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent * e );
5178

5279
/**
53-
Select the features within currently selected layer.
54-
@param canvas The map canvas used to get the current selected vector layer and
80+
Selects a single feature from within currently selected layer.
81+
@param canvas the map canvas used to get the current selected vector layer and
5582
for any required geometry transformations
56-
@param selectGeometry The geometry to select the layers features. This geometry
83+
@param selectGeometry the geometry to select the layers features. This geometry
5784
must be in terms of the canvas coordinate system.
5885
@param e MouseEvents are used to determine the current selection
5986
operations (add, subtract, contains)
87+
@see selectMultipleFeatures()
6088
*/
61-
void setSelectFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent * e );
89+
void selectSingleFeature( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent * e );
6290

6391
/**
6492
Get the current selected canvas map layer. Returns nullptr if it is not a vector layer

0 commit comments

Comments
 (0)
Please sign in to comment.