Skip to content

Commit

Permalink
Merge pull request #3080 from nyalldawson/select_tools
Browse files Browse the repository at this point in the history
[FEATURE] Improved map select tool behaviour
  • Loading branch information
nyalldawson committed May 19, 2016
2 parents d73210a + cd9f47a commit 6bf4b92
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 74 deletions.
3 changes: 1 addition & 2 deletions src/app/qgsmaptoolselect.cpp
Expand Up @@ -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 );
}
7 changes: 4 additions & 3 deletions src/app/qgsmaptoolselectfreehand.cpp
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/qgsmaptoolselectpolygon.cpp
Expand Up @@ -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 );
Expand Down
2 changes: 1 addition & 1 deletion src/app/qgsmaptoolselectradius.cpp
Expand Up @@ -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;
Expand Down
5 changes: 2 additions & 3 deletions src/app/qgsmaptoolselectrectangle.cpp
Expand Up @@ -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;

Expand Down
122 changes: 73 additions & 49 deletions src/app/qgsmaptoolselectutils.cpp
Expand Up @@ -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
Expand All @@ -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 );
Expand All @@ -148,7 +206,6 @@ void QgsMapToolSelectUtils::setSelectFeatures( QgsMapCanvas* canvas,

QgsFeatureIterator fit = vlayer->getFeatures( request );

QgsFeatureIds newSelectedFeatures;
QgsFeature f;
QgsFeatureId closestFeatureId = 0;
bool foundSingleFeature = false;
Expand Down Expand Up @@ -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 );
}

58 changes: 43 additions & 15 deletions src/app/qgsmaptoolselectutils.h
Expand Up @@ -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>
Expand All @@ -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
Expand Down

0 comments on commit 6bf4b92

Please sign in to comment.