Skip to content
Permalink
Browse files
[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)
  • Loading branch information
nyalldawson committed May 19, 2016
1 parent c0799d4 commit cd9f47ae45e4b664356588e6fb6c63c0b3c43aa7
@@ -50,8 +50,7 @@ void QgsMapToolSelect::canvasReleaseEvent( QgsMapMouseEvent* e )
QgsMapToolSelectUtils::expandSelectRectangle( selectRect, vlayer, e->pos() );
QgsMapToolSelectUtils::setRubberBand( mCanvas, selectRect, &rubberBand );
QgsGeometry* selectGeom = rubberBand.asGeometry();
bool doDifference = e->modifiers() & Qt::ControlModifier;
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, selectGeom, false, doDifference, true );
QgsMapToolSelectUtils::selectSingleFeature( mCanvas, selectGeom, e );
delete selectGeom;
rubberBand.reset( QGis::Polygon );
}
@@ -85,9 +85,10 @@ void QgsMapToolSelectFreehand::canvasReleaseEvent( QgsMapMouseEvent* e )
if ( mRubberBand->numberOfVertices() > 2 )
{
QgsGeometry* shapeGeom = mRubberBand->asGeometry();
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, shapeGeom,
e->modifiers() & Qt::ShiftModifier,
e->modifiers() & Qt::ControlModifier, singleSelect );
if ( singleSelect )
QgsMapToolSelectUtils::selectSingleFeature( mCanvas, shapeGeom, e );
else
QgsMapToolSelectUtils::selectMultipleFeatures( mCanvas, shapeGeom, e );
delete shapeGeom;
}

@@ -54,7 +54,7 @@ void QgsMapToolSelectPolygon::canvasPressEvent( QgsMapMouseEvent* e )
if ( mRubberBand->numberOfVertices() > 2 )
{
QgsGeometry* polygonGeom = mRubberBand->asGeometry();
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, polygonGeom, e );
QgsMapToolSelectUtils::selectMultipleFeatures( mCanvas, polygonGeom, e );
delete polygonGeom;
}
mRubberBand->reset( QGis::Polygon );
@@ -92,7 +92,7 @@ void QgsMapToolSelectRadius::canvasReleaseEvent( QgsMapMouseEvent* e )
setRadiusRubberBand( radiusEdge );
}
QgsGeometry* radiusGeometry = mRubberBand->asGeometry();
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, radiusGeometry, e );
QgsMapToolSelectUtils::selectMultipleFeatures( mCanvas, radiusGeometry, e );
delete radiusGeometry;
mRubberBand->reset( QGis::Polygon );
delete mRubberBand;
@@ -106,11 +106,10 @@ void QgsMapToolSelectFeatures::canvasReleaseEvent( QgsMapMouseEvent* e )
QgsGeometry* selectGeom = mRubberBand->asGeometry();
if ( !mDragging )
{
bool doDifference = e->modifiers() & Qt::ControlModifier;
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, selectGeom, false, doDifference, true );
QgsMapToolSelectUtils::selectSingleFeature( mCanvas, selectGeom, e );
}
else
QgsMapToolSelectUtils::setSelectFeatures( mCanvas, selectGeom, e );
QgsMapToolSelectUtils::selectMultipleFeatures( mCanvas, selectGeom, e );

delete selectGeom;

@@ -85,19 +85,80 @@ void QgsMapToolSelectUtils::expandSelectRectangle( QRect& selectRect,
selectRect.setBottom( point.y() + boxSize );
}

void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas,
QgsGeometry* selectGeometry,
bool doContains,
bool doDifference,
bool singleSelect )
void QgsMapToolSelectUtils::selectMultipleFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent* e )
{
if ( selectGeometry->type() != QGis::Polygon )
QgsVectorLayer::SelectBehaviour behaviour = QgsVectorLayer::SetSelection;
if ( e->modifiers() & Qt::ShiftModifier && e->modifiers() & Qt::ControlModifier )
behaviour = QgsVectorLayer::IntersectSelection;
else if ( e->modifiers() & Qt::ShiftModifier )
behaviour = QgsVectorLayer::AddToSelection;
else if ( e->modifiers() & Qt::ControlModifier )
behaviour = QgsVectorLayer::RemoveFromSelection;

bool doContains = e->modifiers() & Qt::AltModifier;
setSelectedFeatures( canvas, selectGeometry, behaviour, doContains );
}

void QgsMapToolSelectUtils::selectSingleFeature( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent* e )
{
QgsVectorLayer* vlayer = QgsMapToolSelectUtils::getCurrentVectorLayer( canvas );
if ( !vlayer )
return;

QApplication::setOverrideCursor( Qt::WaitCursor );

QgsFeatureIds selectedFeatures = getMatchingFeatures( canvas, selectGeometry, false, true );
if ( selectedFeatures.isEmpty() )
{
QApplication::restoreOverrideCursor();
return;
}

QgsVectorLayer::SelectBehaviour behaviour = QgsVectorLayer::SetSelection;

//either shift or control modifier switches to "toggle" selection mode
if ( e->modifiers() & Qt::ShiftModifier || e->modifiers() & Qt::ControlModifier )
{
QgsFeatureId selectId = *selectedFeatures.constBegin();
QgsFeatureIds layerSelectedFeatures = vlayer->selectedFeaturesIds();
if ( layerSelectedFeatures.contains( selectId ) )
behaviour = QgsVectorLayer::RemoveFromSelection;
else
behaviour = QgsVectorLayer::AddToSelection;
}

vlayer->selectByIds( selectedFeatures, behaviour );

QApplication::restoreOverrideCursor();
}

void QgsMapToolSelectUtils::setSelectedFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry,
QgsVectorLayer::SelectBehaviour selectBehaviour, bool doContains, bool singleSelect )
{
QgsVectorLayer* vlayer = QgsMapToolSelectUtils::getCurrentVectorLayer( canvas );
if ( !vlayer )
return;

QApplication::setOverrideCursor( Qt::WaitCursor );

QgsFeatureIds selectedFeatures = getMatchingFeatures( canvas, selectGeometry, doContains, singleSelect );
vlayer->selectByIds( selectedFeatures, selectBehaviour );

QApplication::restoreOverrideCursor();
}


QgsFeatureIds QgsMapToolSelectUtils::getMatchingFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, bool doContains, bool singleSelect )
{
QgsFeatureIds newSelectedFeatures;

if ( selectGeometry->type() != QGis::Polygon )
return newSelectedFeatures;

QgsVectorLayer* vlayer = QgsMapToolSelectUtils::getCurrentVectorLayer( canvas );
if ( !vlayer )
return newSelectedFeatures;

// toLayerCoordinates will throw an exception for any 'invalid' points in
// the rubber band.
// For example, if you project a world map onto a globe using EPSG 2163
@@ -121,16 +182,13 @@ void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas,
QObject::tr( "Selection extends beyond layer's coordinate system" ),
QgsMessageBar::WARNING,
QgisApp::instance()->messageTimeout() );
return;
return newSelectedFeatures;
}
}

QApplication::setOverrideCursor( Qt::WaitCursor );

QgsDebugMsg( "Selection layer: " + vlayer->name() );
QgsDebugMsg( "Selection polygon: " + selectGeomTrans.exportToWkt() );
QgsDebugMsg( "doContains: " + QString( doContains ? "T" : "F" ) );
QgsDebugMsg( "doDifference: " + QString( doDifference ? "T" : "F" ) );
QgsDebugMsgLevel( "Selection layer: " + vlayer->name(), 3 );
QgsDebugMsgLevel( "Selection polygon: " + selectGeomTrans.exportToWkt(), 3 );
QgsDebugMsgLevel( "doContains: " + QString( doContains ? "T" : "F" ), 3 );

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

QgsFeatureIterator fit = vlayer->getFeatures( request );

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

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

if ( doDifference )
{
QgsFeatureIds layerSelectedFeatures = vlayer->selectedFeaturesIds();

QgsFeatureIds selectedFeatures;
QgsFeatureIds deselectedFeatures;

QgsFeatureIds::const_iterator i = newSelectedFeatures.constEnd();
while ( i != newSelectedFeatures.constBegin() )
{
--i;
if ( layerSelectedFeatures.contains( *i ) )
{
deselectedFeatures.insert( *i );
}
else
{
selectedFeatures.insert( *i );
}
}

vlayer->modifySelection( selectedFeatures, deselectedFeatures );
}
else
{
vlayer->selectByIds( newSelectedFeatures );
}

QApplication::restoreOverrideCursor();
return newSelectedFeatures;
}

void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent * e )
{
bool doContains = e->modifiers() & Qt::ShiftModifier;
bool doDifference = e->modifiers() & Qt::ControlModifier;
setSelectFeatures( canvas, selectGeometry, doContains, doDifference );
}

@@ -16,6 +16,7 @@ email : jpalmer at linz dot govt dot nz
#ifndef QGSMAPTOOLSELECTUTILS_H
#define QGSMAPTOOLSELECTUTILS_H

#include "qgsvectorlayer.h"
#include <Qt>
#include <QRect>
#include <QPoint>
@@ -31,34 +32,61 @@ class QgsRubberBand;
*/
namespace QgsMapToolSelectUtils
{
/** Calculates a list of features matching a selection geometry and flags.
* @param canvas the map canvas used to get the current selected vector layer and
for any required geometry transformations
* @param selectGeometry the geometry to select the layers features. This geometry
must be in terms of the canvas coordinate system.
* @param doContains features will only be selected if fully contained within
the selection rubber band (otherwise intersection is enough).
* @param singleSelect only selects the closest feature to the selectGeometry.
* @returns list of features which match search geometry and parameters
* @note added in QGIS 2.16
*/
QgsFeatureIds getMatchingFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, bool doContains, bool singleSelect );

/**
Selects the features within currently selected layer.
@param canvas The map canvas used to get the current selected vector layer and
@param canvas the map canvas used to get the current selected vector layer and
for any required geometry transformations
@param selectGeometry The geometry to select the layers features. This geometry
@param selectGeometry the geometry to select the layers features. This geometry
must be in terms of the canvas coordinate system.
@param doContains Features will only be selected if fully contained within
@param selectBehaviour behaviour of select (ie replace selection, add to selection)
@param doContains features will only be selected if fully contained within
the selection rubber band (otherwise intersection is enough).
@param doDifference Take the symmetric difference of the current selected
features and the new features found within the provided selectGeometry.
@param singleSelect Only selects the closest feature to the selectGeometry.
@param singleSelect only selects the closest feature to the selectGeometry.
@note added in QGIS 2.16
*/
void setSelectedFeatures( QgsMapCanvas* canvas,
QgsGeometry* selectGeometry,
QgsVectorLayer::SelectBehaviour selectBehaviour = QgsVectorLayer::SetSelection,
bool doContains = true,
bool singleSelect = false );

/**
Selects multiple matching features from within currently selected layer.
@param canvas the map canvas used to get the current selected vector layer and
for any required geometry transformations
@param selectGeometry the geometry to select the layers features. This geometry
must be in terms of the canvas coordinate system.
@param e MouseEvents are used to determine the current selection
operations (add, subtract, contains)
@note added in QGIS 2.16
@see selectSingleFeature()
*/
void setSelectFeatures( QgsMapCanvas* canvas,
QgsGeometry* selectGeometry,
bool doContains = true,
bool doDifference = false,
bool singleSelect = false );
void selectMultipleFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent * e );

/**
Select the features within currently selected layer.
@param canvas The map canvas used to get the current selected vector layer and
Selects a single feature from within currently selected layer.
@param canvas the map canvas used to get the current selected vector layer and
for any required geometry transformations
@param selectGeometry The geometry to select the layers features. This geometry
@param selectGeometry the geometry to select the layers features. This geometry
must be in terms of the canvas coordinate system.
@param e MouseEvents are used to determine the current selection
operations (add, subtract, contains)
@see selectMultipleFeatures()
*/
void setSelectFeatures( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent * e );
void selectSingleFeature( QgsMapCanvas* canvas, QgsGeometry* selectGeometry, QMouseEvent * e );

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

0 comments on commit cd9f47a

Please sign in to comment.