QGIS API Documentation 4.1.0-Master (4aad578bf8d)
Loading...
Searching...
No Matches
qgsmaptoolselectionhandler.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmaptoolselectionhandler.cpp
3 ---------------------
4 begin : March 2018
5 copyright : (C) 2018 by Viktor Sklencar
6 email : vsklencar at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include "qgsdoublespinbox.h"
19#include "qgsidentifymenu.h"
20#include "qgsmapcanvas.h"
21#include "qgsmapmouseevent.h"
22#include "qgsrubberband.h"
23#include "qgssnapindicator.h"
24#include "qgsuserinputwidget.h"
25
26#include <QApplication>
27#include <QBoxLayout>
28#include <QKeyEvent>
29#include <QLabel>
30#include <QString>
31
32#include "moc_qgsmaptoolselectionhandler.cpp"
33
34using namespace Qt::StringLiterals;
35
37
38QgsDistanceWidget::QgsDistanceWidget( const QString &label, QWidget *parent )
39 : QWidget( parent )
40{
41 mLayout = new QHBoxLayout( this );
42 mLayout->setContentsMargins( 0, 0, 0, 0 );
43 mLayout->setAlignment( Qt::AlignLeft );
44 setLayout( mLayout );
45
46 if ( !label.isEmpty() )
47 {
48 QLabel *lbl = new QLabel( label, this );
49 lbl->setAlignment( Qt::AlignRight | Qt::AlignCenter );
50 mLayout->addWidget( lbl );
51 }
52
53 mDistanceSpinBox = new QgsDoubleSpinBox( this );
54 mDistanceSpinBox->setSingleStep( 1 );
55 mDistanceSpinBox->setValue( 0 );
56 mDistanceSpinBox->setMinimum( 0 );
57 mDistanceSpinBox->setMaximum( 1000000000 );
58 mDistanceSpinBox->setDecimals( 6 );
59 mDistanceSpinBox->setShowClearButton( false );
60 mDistanceSpinBox->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
61 mLayout->addWidget( mDistanceSpinBox );
62
63 // connect signals
64 mDistanceSpinBox->installEventFilter( this );
65 connect( mDistanceSpinBox, static_cast<void ( QgsDoubleSpinBox::* )( double )>( &QgsDoubleSpinBox::valueChanged ), this, &QgsDistanceWidget::distanceChanged );
66
67 // config focus
68 setFocusProxy( mDistanceSpinBox );
69}
70
71void QgsDistanceWidget::setDistance( double distance )
72{
73 mDistanceSpinBox->setValue( distance );
74 mDistanceSpinBox->selectAll();
75}
76
77double QgsDistanceWidget::distance()
78{
79 return mDistanceSpinBox->value();
80}
81
82bool QgsDistanceWidget::eventFilter( QObject *obj, QEvent *ev )
83{
84 if ( obj == mDistanceSpinBox && ev->type() == QEvent::KeyPress )
85 {
86 QKeyEvent *event = static_cast<QKeyEvent *>( ev );
87 if ( event->key() == Qt::Key_Escape )
88 {
89 emit distanceEditingCanceled();
90 return true;
91 }
92 if ( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return )
93 {
94 emit distanceEditingFinished( distance(), event->modifiers() );
95 return true;
96 }
97 }
98
99 return false;
100}
101
103
104
106 : mCanvas( canvas )
107 , mSelectionMode( selectionMode )
108 , mSnapIndicator( std::make_unique<QgsSnapIndicator>( canvas ) )
109 , mIdentifyMenu( new QgsIdentifyMenu( mCanvas ) )
110{
111 mIdentifyMenu->setAllowMultipleReturn( false );
112 mIdentifyMenu->setExecWithSingleResult( true );
113}
114
119
121{
122 switch ( mSelectionMode )
123 {
126 selectFeaturesReleaseEvent( e );
127 break;
129 break;
131 selectFreehandReleaseEvent( e );
132 break;
134 selectRadiusReleaseEvent( e );
135 break;
136 }
137}
138
139
141{
142 switch ( mSelectionMode )
143 {
146 selectFeaturesMoveEvent( e );
147 break;
149 selectPolygonMoveEvent( e );
150 break;
152 selectFreehandMoveEvent( e );
153 break;
155 selectRadiusMoveEvent( e );
156 break;
157 }
158}
159
161{
162 switch ( mSelectionMode )
163 {
166 selectFeaturesPressEvent( e );
167 break;
169 selectPolygonPressEvent( e );
170 break;
172 break;
174 break;
175 }
176}
177
179{
180 if ( mSelectionActive && e->key() == Qt::Key_Escape )
181 {
182 cancel();
183 return true;
184 }
185 return false;
186}
187
189{
190 cancel();
191}
192
193void QgsMapToolSelectionHandler::selectFeaturesPressEvent( QgsMapMouseEvent *e )
194{
195 if ( !mSelectionRubberBand )
196 initRubberBand();
197
198 mInitDragPos = e->pos();
199}
200
201void QgsMapToolSelectionHandler::selectFeaturesMoveEvent( QgsMapMouseEvent *e )
202{
203 if ( mSelectionMode == QgsMapToolSelectionHandler::SelectOnMouseOver && mCanvas->underMouse() )
204 {
205 mMoveLastCursorPos = e->pos();
206 // This is a (well known, according to google) false positive,
207 // I tried all possible NOLINT placements without success, this
208 // ugly ifdef seems to do the trick with silencing the warning.
209#ifndef __clang_analyzer__
210 if ( !mOnMouseMoveDelayTimer || !mOnMouseMoveDelayTimer->isActive() )
211 {
212 setSelectedGeometry( QgsGeometry::fromPointXY( toMapCoordinates( e->pos() ) ), e->modifiers() );
213 mOnMouseMoveDelayTimer = std::make_unique<QTimer>();
214 mOnMouseMoveDelayTimer->setSingleShot( true );
215 connect( mOnMouseMoveDelayTimer.get(), &QTimer::timeout, this, [this, e] {
216 if ( !mMoveLastCursorPos.isNull() )
217 {
218 setSelectedGeometry( QgsGeometry::fromPointXY( toMapCoordinates( mMoveLastCursorPos ) ), e->modifiers() );
219 }
220 } );
221 mOnMouseMoveDelayTimer->start( 300 );
222 }
223#endif
224 return;
225 }
226
227 if ( e->buttons() != Qt::LeftButton )
228 return;
229
230 QRect rect;
231 if ( !mSelectionActive )
232 {
233 mSelectionActive = true;
234 rect = QRect( e->pos(), e->pos() );
235 }
236 else
237 {
238 rect = QRect( e->pos(), mInitDragPos );
239 }
240
241 if ( mSelectionRubberBand )
242 mSelectionRubberBand->setToCanvasRectangle( rect );
243}
244
245void QgsMapToolSelectionHandler::selectFeaturesReleaseEvent( QgsMapMouseEvent *e )
246{
247 const QPoint point = e->pos() - mInitDragPos;
248 if ( !mSelectionActive || ( point.manhattanLength() < QApplication::startDragDistance() ) )
249 {
250 mSelectionActive = false;
251 setSelectedGeometry( QgsGeometry::fromPointXY( toMapCoordinates( e->pos() ) ), e->modifiers() );
252 }
253
254 if ( mSelectionRubberBand && mSelectionActive )
255 setSelectedGeometry( mSelectionRubberBand->asGeometry(), e->modifiers() );
256 if ( mSelectionRubberBand )
257 mSelectionRubberBand.reset();
258
259 mSelectionActive = false;
260}
261
262QgsPointXY QgsMapToolSelectionHandler::toMapCoordinates( QPoint point )
263{
264 return mCanvas->getCoordinateTransform()->toMapCoordinates( point );
265}
266
267void QgsMapToolSelectionHandler::selectPolygonMoveEvent( QgsMapMouseEvent *e )
268{
269 if ( !mSelectionRubberBand )
270 return;
271
272 if ( mSelectionRubberBand->numberOfVertices() > 0 )
273 {
274 mSelectionRubberBand->movePoint( toMapCoordinates( e->pos() ) );
275 }
276}
277
278void QgsMapToolSelectionHandler::selectPolygonPressEvent( QgsMapMouseEvent *e )
279{
280 // Handle immediate right-click on feature to show context menu
281 if ( !mSelectionRubberBand && ( e->button() == Qt::RightButton ) )
282 {
283 const QList<QgsMapToolIdentify::IdentifyResult> results = QgsIdentifyMenu::findFeaturesOnCanvas( e, mCanvas, { Qgis::GeometryType::Polygon } );
284
285 const QPoint globalPos = mCanvas->mapToGlobal( QPoint( e->pos().x() + 5, e->pos().y() + 5 ) );
286 const QList<QgsMapToolIdentify::IdentifyResult> selectedFeatures = mIdentifyMenu->exec( results, globalPos );
287 if ( !selectedFeatures.empty() && selectedFeatures[0].mFeature.hasGeometry() )
288 {
289 QgsCoordinateTransform transform = mCanvas->mapSettings().layerTransform( selectedFeatures.at( 0 ).mLayer );
290 QgsGeometry geom = selectedFeatures[0].mFeature.geometry();
291 try
292 {
293 geom.transform( transform );
294 }
295 catch ( QgsCsException & )
296 {
297 QgsDebugError( u"Could not transform geometry to map CRS"_s );
298 }
299
300 setSelectedGeometry( geom, e->modifiers() );
301 }
302
303 return;
304 }
305
306 // Handle definition of polygon by clicking points on cancas
307 if ( !mSelectionRubberBand )
308 initRubberBand();
309
310 if ( e->button() == Qt::LeftButton )
311 {
312 mSelectionRubberBand->addPoint( toMapCoordinates( e->pos() ) );
313 mSelectionActive = true;
314 }
315 else
316 {
317 if ( mSelectionRubberBand->numberOfVertices() > 2 )
318 {
319 setSelectedGeometry( mSelectionRubberBand->asGeometry(), e->modifiers() );
320 }
321 mSelectionRubberBand.reset();
322 mSelectionActive = false;
323 }
324}
325
326void QgsMapToolSelectionHandler::selectFreehandMoveEvent( QgsMapMouseEvent *e )
327{
328 if ( !mSelectionActive || !mSelectionRubberBand )
329 return;
330
331 mSelectionRubberBand->addPoint( toMapCoordinates( e->pos() ) );
332}
333
334void QgsMapToolSelectionHandler::selectFreehandReleaseEvent( QgsMapMouseEvent *e )
335{
336 if ( !mSelectionActive )
337 {
338 if ( e->button() != Qt::LeftButton )
339 return;
340
341 if ( !mSelectionRubberBand )
342 initRubberBand();
343
344 mSelectionRubberBand->addPoint( toMapCoordinates( e->pos() ) );
345 mSelectionActive = true;
346 }
347 else
348 {
349 if ( e->button() == Qt::LeftButton )
350 {
351 if ( mSelectionRubberBand && mSelectionRubberBand->numberOfVertices() > 2 )
352 {
353 setSelectedGeometry( mSelectionRubberBand->asGeometry(), e->modifiers() );
354 }
355 }
356
357 mSelectionRubberBand.reset();
358 mSelectionActive = false;
359 }
360}
361
362void QgsMapToolSelectionHandler::selectRadiusMoveEvent( QgsMapMouseEvent *e )
363{
364 QgsPointXY radiusEdge = e->snapPoint();
365
366 mSnapIndicator->setMatch( e->mapPointMatch() );
367
368 if ( !mSelectionActive )
369 {
370 return;
371 }
372
373 if ( !mSelectionRubberBand )
374 {
375 initRubberBand();
376 }
377
378 updateRadiusFromEdge( radiusEdge );
379}
380
381void QgsMapToolSelectionHandler::selectRadiusReleaseEvent( QgsMapMouseEvent *e )
382{
383 if ( e->button() == Qt::RightButton )
384 {
385 cancel();
386 return;
387 }
388
389 if ( e->button() != Qt::LeftButton )
390 return;
391
392 if ( !mSelectionActive )
393 {
394 mSelectionActive = true;
395 mRadiusCenter = e->snapPoint();
396 createDistanceWidget();
397 }
398 else
399 {
400 if ( mSelectionRubberBand )
401 {
402 setSelectedGeometry( mSelectionRubberBand->asGeometry(), e->modifiers() );
403 }
404 cancel();
405 }
406}
407
408
409void QgsMapToolSelectionHandler::initRubberBand()
410{
411 mSelectionRubberBand = std::make_unique<QgsRubberBand>( mCanvas, Qgis::GeometryType::Polygon );
412 mSelectionRubberBand->setFillColor( mFillColor );
413 mSelectionRubberBand->setStrokeColor( mStrokeColor );
414}
415
416void QgsMapToolSelectionHandler::createDistanceWidget()
417{
418 if ( !mCanvas )
419 {
420 return;
421 }
422
423 deleteDistanceWidget();
424
425 mDistanceWidget = new QgsDistanceWidget( tr( "Selection radius:" ) );
426 if ( QgsUserInputWidget *userInputWidget = mCanvas->userInputWidget() )
427 {
428 userInputWidget->addUserInputWidget( mDistanceWidget );
429 }
430 mDistanceWidget->setFocus( Qt::TabFocusReason );
431
432 connect( mDistanceWidget, &QgsDistanceWidget::distanceChanged, this, &QgsMapToolSelectionHandler::updateRadiusRubberband );
433 connect( mDistanceWidget, &QgsDistanceWidget::distanceEditingFinished, this, &QgsMapToolSelectionHandler::radiusValueEntered );
434 connect( mDistanceWidget, &QgsDistanceWidget::distanceEditingCanceled, this, &QgsMapToolSelectionHandler::cancel );
435}
436
437void QgsMapToolSelectionHandler::deleteDistanceWidget()
438{
439 if ( mDistanceWidget )
440 {
441 mDistanceWidget->releaseKeyboard();
442 mDistanceWidget->deleteLater();
443 }
444 mDistanceWidget = nullptr;
445}
446
447void QgsMapToolSelectionHandler::radiusValueEntered( double radius, Qt::KeyboardModifiers modifiers )
448{
449 if ( !mSelectionRubberBand )
450 return;
451
452 updateRadiusRubberband( radius );
453 setSelectedGeometry( mSelectionRubberBand->asGeometry(), modifiers );
454 cancel();
455}
456
457void QgsMapToolSelectionHandler::cancel()
458{
459 deleteDistanceWidget();
460 mSnapIndicator->setMatch( QgsPointLocator::Match() );
461 mSelectionRubberBand.reset();
462 mSelectionActive = false;
463}
464
465void QgsMapToolSelectionHandler::updateRadiusRubberband( double radius )
466{
467 if ( !mSelectionRubberBand )
468 initRubberBand();
469
470 const int RADIUS_SEGMENTS = 80;
471
472 mSelectionRubberBand->reset( Qgis::GeometryType::Polygon );
473 for ( int i = 0; i <= RADIUS_SEGMENTS; ++i )
474 {
475 const double theta = i * ( 2.0 * M_PI / RADIUS_SEGMENTS );
476 const QgsPointXY radiusPoint( mRadiusCenter.x() + radius * std::cos( theta ), mRadiusCenter.y() + radius * std::sin( theta ) );
477 mSelectionRubberBand->addPoint( radiusPoint, false );
478 }
479 mSelectionRubberBand->closePoints( true );
480}
481
482void QgsMapToolSelectionHandler::updateRadiusFromEdge( QgsPointXY &radiusEdge )
483{
484 const double radius = std::sqrt( mRadiusCenter.sqrDist( radiusEdge ) );
485 if ( mDistanceWidget )
486 {
487 mDistanceWidget->setDistance( radius );
488 mDistanceWidget->setFocus( Qt::TabFocusReason );
489 }
490 else
491 {
492 updateRadiusRubberband( radius );
493 }
494}
495
497{
498 return mSelectionGeometry;
499}
500
501void QgsMapToolSelectionHandler::setSelectedGeometry( const QgsGeometry &geometry, Qt::KeyboardModifiers modifiers )
502{
503 mSelectionGeometry = geometry;
504 mMoveLastCursorPos = QPoint();
505 emit geometryChanged( modifiers );
506}
507
509{
510 mSelectionMode = mode;
511}
512
@ Polygon
Polygons.
Definition qgis.h:382
The QgsSpinBox is a spin box with a clear button that will set the value to the defined clear value.
A geometry is the spatial representation of a feature.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
Builds a menu to be used with identify results.
static QList< QgsMapToolIdentify::IdentifyResult > findFeaturesOnCanvas(QgsMapMouseEvent *event, QgsMapCanvas *canvas, const QList< Qgis::GeometryType > &geometryTypes)
Searches for features on the map canvas, which are located at the specified event point.
Map canvas is a class for displaying all GIS data types on a canvas.
A mouse event which is the result of a user interaction with a QgsMapCanvas.
QgsPointLocator::Match mapPointMatch() const
Returns the matching data from the most recently snapped point.
QgsPointXY snapPoint()
snapPoint will snap the points using the map canvas snapping utils configuration
SelectionMode selectionMode() const
Sets the current selection mode.
QgsGeometry selectedGeometry() const
Returns most recently selected geometry (may be a point or a polygon).
SelectionMode
Select features to identify by:
@ SelectSimple
SelectSimple - single click or drawing a rectangle, default option.
@ SelectRadius
SelectRadius - a circle selection.
@ SelectOnMouseOver
SelectOnMouseMove - selection on mouse over.
@ SelectPolygon
SelectPolygon - drawing a polygon or right-click on existing polygon feature.
@ SelectFreehand
SelectFreehand - free hand selection.
void geometryChanged(Qt::KeyboardModifiers modifiers=Qt::NoModifier)
emitted when a new geometry has been picked (selectedGeometry())
void setSelectedGeometry(const QgsGeometry &geometry, Qt::KeyboardModifiers modifiers=Qt::NoModifier)
Sets the selected geometry.
void canvasMoveEvent(QgsMapMouseEvent *e)
Handles mouse move event from map tool.
void deactivate()
Deactivates handler (when map tool gets deactivated).
bool keyReleaseEvent(QKeyEvent *e)
Handles escape press event - returns true if the even has been processed.
QgsMapToolSelectionHandler(QgsMapCanvas *canvas, QgsMapToolSelectionHandler::SelectionMode selectionMode=QgsMapToolSelectionHandler::SelectionMode::SelectSimple)
constructor
void canvasReleaseEvent(QgsMapMouseEvent *e)
Handles mouse release event from map tool.
void setSelectionMode(SelectionMode mode)
Returns the current selection mode.
void canvasPressEvent(QgsMapMouseEvent *e)
Handles mouse press event from map tool.
Represents a 2D point.
Definition qgspointxy.h:62
Shows a snapping marker on map canvas for the current snapping match.
#define QgsDebugError(str)
Definition qgslogger.h:59