QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
qgsidentifymenu.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsidentifymenu.cpp - menu to be used in identify map tool
3 ---------------------
4 begin : August 2014
5 copyright : (C) 2014 by Denis Rouzaud
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
16#include <QMouseEvent>
17
18#include "qgsidentifymenu.h"
19#include "qgsapplication.h"
20#include "qgshighlight.h"
21#include "qgsmapcanvas.h"
22#include "qgsactionmenu.h"
23#include "qgsvectorlayer.h"
24#include "qgslogger.h"
25#include "qgsgui.h"
27#include "qgsiconutils.h"
28#include "qgsmapmouseevent.h"
29#include "qgsmaplayeraction.h"
31
32//TODO 4.0 add explicitly qobject parent to constructor
34 : QMenu( canvas )
35 , mCanvas( canvas )
36 , mAllowMultipleReturn( true )
37 , mExecWithSingleResult( false )
38 , mShowFeatureActions( false )
39 , mResultsIfExternalAction( false )
40 , mMaxLayerDisplay( 10 )
41 , mMaxFeatureDisplay( 10 )
42 , mDefaultActionName( tr( "Identify" ) )
43{
44}
45
47{
48 deleteRubberBands();
49}
50
51QList<QgsMapToolIdentify::IdentifyResult> QgsIdentifyMenu::findFeaturesOnCanvas( QgsMapMouseEvent *event, QgsMapCanvas *canvas, const QList<Qgis::GeometryType> &geometryTypes )
52{
53 QList<QgsMapToolIdentify::IdentifyResult> results;
54 const QMap< QString, QString > derivedAttributes;
55
56 const QgsPointXY mapPoint = canvas->getCoordinateTransform()->toMapCoordinates( event->pos() ) ;
57 const double x = mapPoint.x();
58 const double y = mapPoint.y();
59 const double sr = QgsMapTool::searchRadiusMU( canvas );
60
61 const QList<QgsMapLayer *> layers = canvas->layers( true );
62 for ( QgsMapLayer *layer : layers )
63 {
64 if ( layer->type() == Qgis::LayerType::Vector )
65 {
66 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
67
68 bool typeIsSelectable = false;
69 for ( Qgis::GeometryType type : geometryTypes )
70 {
71 if ( vectorLayer->geometryType() == type )
72 {
73 typeIsSelectable = true;
74 break;
75 }
76 }
77 if ( typeIsSelectable )
78 {
79 QgsRectangle rect( x - sr, y - sr, x + sr, y + sr );
80 QgsCoordinateTransform transform = canvas->mapSettings().layerTransform( vectorLayer );
82
83 try
84 {
85 rect = transform.transformBoundingBox( rect, Qgis::TransformDirection::Reverse );
86 }
87 catch ( QgsCsException & )
88 {
89 QgsDebugMsg( QStringLiteral( "Could not transform geometry to layer CRS" ) );
90 }
91
93 .setFilterRect( rect )
95 QgsFeature f;
96 while ( fit.nextFeature( f ) )
97 {
98 results << QgsMapToolIdentify::IdentifyResult( vectorLayer, f, derivedAttributes );
99 }
100 }
101 }
102 }
103
104 return results;
105}
106
107void QgsIdentifyMenu::setMaxLayerDisplay( int maxLayerDisplay )
108{
109 if ( maxLayerDisplay < 0 )
110 {
111 QgsDebugMsg( QStringLiteral( "invalid value for number of layers displayed." ) );
112 }
113 mMaxLayerDisplay = maxLayerDisplay;
114}
115
116
117void QgsIdentifyMenu::setMaxFeatureDisplay( int maxFeatureDisplay )
118{
119 if ( maxFeatureDisplay < 0 )
120 {
121 QgsDebugMsg( QStringLiteral( "invalid value for number of layers displayed." ) );
122 }
123 mMaxFeatureDisplay = maxFeatureDisplay;
124}
125
126
127QList<QgsMapToolIdentify::IdentifyResult> QgsIdentifyMenu::exec( const QList<QgsMapToolIdentify::IdentifyResult> &idResults, QPoint pos )
128{
129 clear();
130 mLayerIdResults.clear();
131
132 QList<QgsMapToolIdentify::IdentifyResult> returnResults = QList<QgsMapToolIdentify::IdentifyResult>();
133
134 if ( idResults.isEmpty() )
135 {
136 return returnResults;
137 }
138 if ( idResults.count() == 1 && !mExecWithSingleResult )
139 {
140 returnResults << idResults[0];
141 return returnResults;
142 }
143
144 // sort results by layer
145 const auto constIdResults = idResults;
146 for ( const QgsMapToolIdentify::IdentifyResult &result : constIdResults )
147 {
148 QgsMapLayer *layer = result.mLayer;
149 if ( mLayerIdResults.contains( layer ) )
150 {
151 mLayerIdResults[layer].append( result );
152 }
153 else
154 {
155 mLayerIdResults.insert( layer, QList<QgsMapToolIdentify::IdentifyResult>() << result );
156 }
157 }
158
159 // add results to the menu
160 const bool singleLayer = mLayerIdResults.count() == 1;
161 int count = 0;
162 QMapIterator< QgsMapLayer *, QList<QgsMapToolIdentify::IdentifyResult> > it( mLayerIdResults );
163 while ( it.hasNext() )
164 {
165 if ( mMaxLayerDisplay != 0 && count > mMaxLayerDisplay )
166 break;
167 ++count;
168 it.next();
169 QgsMapLayer *layer = it.key();
170 switch ( layer->type() )
171 {
172 case Qgis::LayerType::Raster:
173 {
174 addRasterLayer( layer );
175 break;
176 }
177 case Qgis::LayerType::Vector:
178 {
179 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
180 if ( !vl )
181 continue;
182 addVectorLayer( vl, it.value(), singleLayer );
183 break;
184 }
185
186 case Qgis::LayerType::VectorTile:
187 // TODO: add support
188 break;
189
190 case Qgis::LayerType::Plugin:
191 case Qgis::LayerType::Annotation:
192 case Qgis::LayerType::Mesh:
193 case Qgis::LayerType::PointCloud:
194 case Qgis::LayerType::Group:
195 break;
196 }
197 }
198
199 // add an "identify all" action on the top level
200 if ( !singleLayer && mAllowMultipleReturn && idResults.count() > 1 )
201 {
202 addSeparator();
203 QAction *allAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIdentify.svg" ) ), tr( "%1 All (%2)" ).arg( mDefaultActionName ).arg( idResults.count() ), this );
204 allAction->setData( QVariant::fromValue<ActionData>( ActionData( nullptr ) ) );
205 connect( allAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
206 addAction( allAction );
207 }
208
209 // exec
210 QAction *selectedAction = QMenu::exec( pos );
211 bool externalAction;
212 returnResults = results( selectedAction, externalAction );
213
214 // delete actions
215 clear();
216 // also remove the QgsActionMenu
217 qDeleteAll( findChildren<QgsActionMenu *>() );
218
219 if ( externalAction && !mResultsIfExternalAction )
220 {
221 return QList<QgsMapToolIdentify::IdentifyResult>();
222 }
223 else
224 {
225 return returnResults;
226 }
227}
228
229void QgsIdentifyMenu::closeEvent( QCloseEvent *e )
230{
231 deleteRubberBands();
232 QMenu::closeEvent( e );
233}
234
235void QgsIdentifyMenu::addRasterLayer( QgsMapLayer *layer )
236{
237 QAction *layerAction = nullptr;
238 QMenu *layerMenu = nullptr;
239
240 QList<QgsMapLayerAction *> separators = QList<QgsMapLayerAction *>();
242 QList<QgsMapLayerAction *> layerActions = mCustomActionRegistry.mapLayerActions( layer, Qgis::MapLayerActionTarget::Layer, context );
243 const int nCustomActions = layerActions.count();
244 if ( nCustomActions )
245 {
246 separators.append( layerActions[0] );
247 }
248 if ( mShowFeatureActions )
249 {
250 layerActions.append( QgsGui::mapLayerActionRegistry()->mapLayerActions( layer, Qgis::MapLayerActionTarget::Layer, context ) );
251 if ( layerActions.count() > nCustomActions )
252 {
253 separators.append( layerActions[nCustomActions] );
254 }
255 }
256
257 // use a menu only if actions will be listed
258 if ( layerActions.isEmpty() )
259 {
260 layerAction = new QAction( layer->name(), this );
261 }
262 else
263 {
264 layerMenu = new QMenu( layer->name(), this );
265 layerAction = layerMenu->menuAction();
266 }
267
268 // add layer action to the top menu
269 layerAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconRasterLayer.svg" ) ) );
270 layerAction->setData( QVariant::fromValue<ActionData>( ActionData( layer ) ) );
271 connect( layerAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
272 addAction( layerAction );
273
274 // no need to go further if there is no menu
275 if ( !layerMenu )
276 return;
277
278 // add default identify action
279 QAction *identifyFeatureAction = new QAction( mDefaultActionName, layerMenu );
280 connect( identifyFeatureAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
281 identifyFeatureAction->setData( QVariant::fromValue<ActionData>( ActionData( layer ) ) );
282 layerMenu->addAction( identifyFeatureAction );
283
284 // add custom/layer actions
285 const auto constLayerActions = layerActions;
286 for ( QgsMapLayerAction *mapLayerAction : constLayerActions )
287 {
288 QAction *action = new QAction( mapLayerAction->icon(), mapLayerAction->text(), layerMenu );
289 action->setData( QVariant::fromValue<ActionData>( ActionData( layer, true ) ) );
290 connect( action, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
291 connect( action, &QAction::triggered, this, &QgsIdentifyMenu::triggerMapLayerAction );
292 layerMenu->addAction( action );
293 if ( separators.contains( mapLayerAction ) )
294 {
295 layerMenu->insertSeparator( action );
296 }
297 }
298}
299
300void QgsIdentifyMenu::addVectorLayer( QgsVectorLayer *layer, const QList<QgsMapToolIdentify::IdentifyResult> &results, bool singleLayer )
301{
302 QAction *layerAction = nullptr;
303 QMenu *layerMenu = nullptr;
304
305 // do not add actions with MultipleFeatures as target if only 1 feature is found for this layer
306 // targets defines which actions will be shown
307 const Qgis::MapLayerActionTargets targets = results.count() > 1 ? ( Qgis::MapLayerActionTarget::Layer | Qgis::MapLayerActionTarget::MultipleFeatures ) : Qgis::MapLayerActionTarget::Layer;
308
309 QList<QgsMapLayerAction *> separators = QList<QgsMapLayerAction *>();
310 QgsMapLayerActionContext actionContext;
311 QList<QgsMapLayerAction *> layerActions = mCustomActionRegistry.mapLayerActions( layer, targets, actionContext );
312 const int nCustomActions = layerActions.count();
313 if ( nCustomActions )
314 {
315 separators << layerActions[0];
316 }
317 if ( mShowFeatureActions )
318 {
319 layerActions << QgsGui::mapLayerActionRegistry()->mapLayerActions( layer, targets, actionContext );
320
321 if ( layerActions.count() > nCustomActions )
322 {
323 separators << layerActions[nCustomActions];
324 }
325 }
326
327 // determines if a menu should be created or not. Following cases:
328 // 1. only one result and no feature action to be shown => just create an action
329 // 2. several features (2a) or display feature actions (2b) => create a menu
330 // 3. case 2 but only one layer (singeLayer) => do not create a menu, but give the top menu instead
331
332 bool createMenu = results.count() > 1 || !layerActions.isEmpty();
333
334 // case 2b: still create a menu for layer, if there is a sub-level for features
335 // i.e custom actions or map layer actions at feature level
336 if ( !createMenu )
337 {
338 createMenu = !mCustomActionRegistry.mapLayerActions( layer, Qgis::MapLayerActionTarget::SingleFeature, actionContext ).isEmpty();
339 if ( !createMenu && mShowFeatureActions )
340 {
341 QgsActionMenu *featureActionMenu = new QgsActionMenu( layer, results[0].mFeature, QStringLiteral( "Feature" ), this );
343 createMenu = !featureActionMenu->actions().isEmpty();
344 delete featureActionMenu;
345 }
346 }
347
349 QgsExpression exp( layer->displayExpression() );
350 exp.prepare( &context );
351 context.setFeature( results[0].mFeature );
352 // use a menu only if actions will be listed
353 if ( !createMenu )
354 {
355 // case 1
356 QString featureTitle = exp.evaluate( &context ).toString();
357 if ( featureTitle.isEmpty() )
358 featureTitle = QString::number( results[0].mFeature.id() );
359 layerAction = new QAction( QStringLiteral( "%1 (%2)" ).arg( layer->name(), featureTitle ), this );
360 }
361 else
362 {
363 if ( singleLayer )
364 {
365 // case 3
366 layerMenu = this;
367 }
368 else
369 {
370 // case 2a
371 if ( results.count() > 1 )
372 {
373 layerMenu = new QMenu( layer->name(), this );
374 }
375 // case 2b
376 else
377 {
378 QString featureTitle = exp.evaluate( &context ).toString();
379 if ( featureTitle.isEmpty() )
380 featureTitle = QString::number( results[0].mFeature.id() );
381 layerMenu = new QMenu( QStringLiteral( "%1 (%2)" ).arg( layer->name(), featureTitle ), this );
382 }
383 layerAction = layerMenu->menuAction();
384 }
385 }
386
387 // case 1 or 2
388 if ( layerAction )
389 {
390 layerAction->setIcon( QgsIconUtils::iconForWkbType( layer->wkbType() ) );
391
392 // add layer action to the top menu
393 layerAction->setData( QVariant::fromValue<ActionData>( ActionData( layer ) ) );
394 connect( layerAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
395 addAction( layerAction );
396 }
397
398 // case 1. no need to go further
399 if ( !layerMenu )
400 return;
401
402 // add results to the menu
403 int count = 0;
404 const auto constResults = results;
405 for ( const QgsMapToolIdentify::IdentifyResult &result : constResults )
406 {
407 if ( mMaxFeatureDisplay != 0 && count > mMaxFeatureDisplay )
408 break;
409 ++count;
410
411 QAction *featureAction = nullptr;
412 QMenu *featureMenu = nullptr;
413 QgsActionMenu *featureActionMenu = nullptr;
414
415 const QList<QgsMapLayerAction *> customFeatureActions = mCustomActionRegistry.mapLayerActions( layer, Qgis::MapLayerActionTarget::SingleFeature, actionContext );
416 if ( mShowFeatureActions )
417 {
418 featureActionMenu = new QgsActionMenu( layer, result.mFeature, QStringLiteral( "Feature" ), layerMenu );
420 featureActionMenu->setExpressionContextScope( mExpressionContextScope );
421 }
422
423 // feature title
424 context.setFeature( result.mFeature );
425 QString featureTitle = exp.evaluate( &context ).toString();
426 if ( featureTitle.isEmpty() )
427 featureTitle = QString::number( result.mFeature.id() );
428
429 if ( customFeatureActions.isEmpty() && ( !featureActionMenu || featureActionMenu->actions().isEmpty() ) )
430 {
431 featureAction = new QAction( featureTitle, layerMenu );
432 // add the feature action (or menu) to the layer menu
433 featureAction->setData( QVariant::fromValue<ActionData>( ActionData( layer, result.mFeature.id() ) ) );
434 connect( featureAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
435 layerMenu->addAction( featureAction );
436 }
437 else if ( results.count() == 1 )
438 {
439 // if we are here with only one results, this means there is a sub-feature level (for actions)
440 // => skip the feature level since there would be only a single entry
441 // => give the layer menu as pointer instead of a new feature menu
442 featureMenu = layerMenu;
443 }
444 else
445 {
446 featureMenu = new QMenu( featureTitle, layerMenu );
447
448 // get the action from the menu
449 featureAction = featureMenu->menuAction();
450 // add the feature action (or menu) to the layer menu
451 featureAction->setData( QVariant::fromValue<ActionData>( ActionData( layer, result.mFeature.id() ) ) );
452 connect( featureAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
453 layerMenu->addAction( featureAction );
454 }
455
456 // if no feature menu, no need to go further
457 if ( !featureMenu )
458 continue;
459
460 // add default identify action
461 QAction *identifyFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIdentify.svg" ) ), mDefaultActionName, featureMenu );
462 connect( identifyFeatureAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
463 identifyFeatureAction->setData( QVariant::fromValue<ActionData>( ActionData( layer, result.mFeature.id() ) ) );
464 featureMenu->addAction( identifyFeatureAction );
465 featureMenu->addSeparator();
466
467 // custom action at feature level
468 const auto constCustomFeatureActions = customFeatureActions;
469 for ( QgsMapLayerAction *mapLayerAction : constCustomFeatureActions )
470 {
471 QAction *action = new QAction( mapLayerAction->icon(), mapLayerAction->text(), featureMenu );
472 action->setData( QVariant::fromValue<ActionData>( ActionData( layer, result.mFeature.id(), mapLayerAction ) ) );
473 connect( action, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
474 connect( action, &QAction::triggered, this, &QgsIdentifyMenu::triggerMapLayerAction );
475 featureMenu->addAction( action );
476 }
477 // use QgsActionMenu for feature actions
478 if ( featureActionMenu )
479 {
480 const auto constActions = featureActionMenu->actions();
481 for ( QAction *action : constActions )
482 {
483 connect( action, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
484 featureMenu->addAction( action );
485 }
486 }
487 }
488
489 // back to layer level
490
491 // identify all action
492 if ( mAllowMultipleReturn && results.count() > 1 )
493 {
494 layerMenu->addSeparator();
495 QAction *allAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIdentify.svg" ) ), tr( "%1 All (%2)" ).arg( mDefaultActionName ).arg( results.count() ), layerMenu );
496 allAction->setData( QVariant::fromValue<ActionData>( ActionData( layer ) ) );
497 connect( allAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
498 layerMenu->addAction( allAction );
499 }
500
501 // add custom/layer actions
502 const auto constLayerActions = layerActions;
503 for ( QgsMapLayerAction *mapLayerAction : constLayerActions )
504 {
505 QString title = mapLayerAction->text();
506 if ( mapLayerAction->targets().testFlag( Qgis::MapLayerActionTarget::MultipleFeatures ) )
507 title.append( QStringLiteral( " (%1)" ).arg( results.count() ) );
508 QAction *action = new QAction( mapLayerAction->icon(), title, layerMenu );
509 action->setData( QVariant::fromValue<ActionData>( ActionData( layer, mapLayerAction ) ) );
510 connect( action, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
511 connect( action, &QAction::triggered, this, &QgsIdentifyMenu::triggerMapLayerAction );
512 layerMenu->addAction( action );
513 if ( separators.contains( mapLayerAction ) )
514 {
515 layerMenu->insertSeparator( action );
516 }
517 }
518}
519
520void QgsIdentifyMenu::triggerMapLayerAction()
521{
522 QAction *action = qobject_cast<QAction *>( sender() );
523 if ( !action )
524 return;
525 const QVariant varData = action->data();
526 if ( !varData.isValid() || !varData.canConvert<ActionData>() )
527 return;
528
529 ActionData actData = action->data().value<ActionData>();
530
531 if ( actData.mIsValid && actData.mMapLayerAction )
532 {
534
535 // layer
536 if ( actData.mMapLayerAction->targets().testFlag( Qgis::MapLayerActionTarget::Layer ) )
537 {
539 actData.mMapLayerAction->triggerForLayer( actData.mLayer );
541 actData.mMapLayerAction->triggerForLayer( actData.mLayer, context );
542 }
543
544 // multiples features
545 if ( actData.mMapLayerAction->targets().testFlag( Qgis::MapLayerActionTarget::MultipleFeatures ) )
546 {
547 QList<QgsFeature> featureList;
548 const auto results { mLayerIdResults[actData.mLayer] };
549 for ( const QgsMapToolIdentify::IdentifyResult &result : results )
550 {
551 featureList << result.mFeature;
552 }
554 actData.mMapLayerAction->triggerForFeatures( actData.mLayer, featureList );
556 actData.mMapLayerAction->triggerForFeatures( actData.mLayer, featureList, context );
557 }
558
559 // single feature
560 if ( actData.mMapLayerAction->targets().testFlag( Qgis::MapLayerActionTarget::SingleFeature ) )
561 {
562 const auto results { mLayerIdResults[actData.mLayer] };
563 for ( const QgsMapToolIdentify::IdentifyResult &result : results )
564 {
565 if ( result.mFeature.id() == actData.mFeatureId )
566 {
568 actData.mMapLayerAction->triggerForFeature( actData.mLayer, result.mFeature );
570 actData.mMapLayerAction->triggerForFeature( actData.mLayer, result.mFeature, context );
571 return;
572 }
573 }
574 QgsDebugMsg( QStringLiteral( "Identify menu: could not retrieve feature for action %1" ).arg( action->text() ) );
575 }
576 }
577}
578
579
580QList<QgsMapToolIdentify::IdentifyResult> QgsIdentifyMenu::results( QAction *action, bool &externalAction )
581{
582 QList<QgsMapToolIdentify::IdentifyResult> idResults = QList<QgsMapToolIdentify::IdentifyResult>();
583
584 externalAction = false;
585
586 ActionData actData;
587 bool hasData = false;
588
589 if ( !action )
590 return idResults;
591
592 const QVariant varData = action->data();
593 if ( !varData.isValid() )
594 {
595 QgsDebugMsg( QStringLiteral( "Identify menu: could not retrieve results from menu entry (invalid data)" ) );
596 return idResults;
597 }
598
599 if ( varData.canConvert<ActionData>() )
600 {
601 actData = action->data().value<ActionData>();
602 if ( actData.mIsValid )
603 {
604 externalAction = actData.mIsExternalAction;
605 hasData = true;
606 }
607 }
608
609 if ( !hasData && varData.canConvert<QgsActionMenu::ActionData>() )
610 {
611 const QgsActionMenu::ActionData dataSrc = action->data().value<QgsActionMenu::ActionData>();
612 if ( dataSrc.actionType != Qgis::ActionType::Invalid )
613 {
614 externalAction = true;
615 actData = ActionData( dataSrc.mapLayer, dataSrc.featureId );
616 hasData = true;
617 }
618 }
619
620 if ( !hasData )
621 {
622 QgsDebugMsg( QStringLiteral( "Identify menu: could not retrieve results from menu entry (no data found)" ) );
623 return idResults;
624 }
625
626 // return all results
627 if ( actData.mAllResults )
628 {
629 // this means "All" action was triggered
630 QMapIterator< QgsMapLayer *, QList<QgsMapToolIdentify::IdentifyResult> > it( mLayerIdResults );
631 while ( it.hasNext() )
632 {
633 it.next();
634 idResults << it.value();
635 }
636 return idResults;
637 }
638
639 if ( !mLayerIdResults.contains( actData.mLayer ) )
640 {
641 QgsDebugMsg( QStringLiteral( "Identify menu: could not retrieve results from menu entry (layer not found)" ) );
642 return idResults;
643 }
644
645 if ( actData.mLevel == LayerLevel )
646 {
647 return mLayerIdResults[actData.mLayer];
648 }
649
650 if ( actData.mLevel == FeatureLevel )
651 {
652 const auto results {mLayerIdResults[actData.mLayer]};
653 for ( const QgsMapToolIdentify::IdentifyResult &res : results )
654 {
655 if ( res.mFeature.id() == actData.mFeatureId )
656 {
657 idResults << res;
658 return idResults;
659 }
660 }
661 }
662
663 QgsDebugMsg( QStringLiteral( "Identify menu: could not retrieve results from menu entry (don't know what happened')" ) );
664 return idResults;
665}
666
667void QgsIdentifyMenu::handleMenuHover()
668{
669 if ( !mCanvas )
670 return;
671
672 deleteRubberBands();
673
674 QAction *senderAction = qobject_cast<QAction *>( sender() );
675 if ( !senderAction )
676 return;
677
678 bool externalAction;
679 const QList<QgsMapToolIdentify::IdentifyResult> idResults = results( senderAction, externalAction );
680
681 const auto constIdResults = idResults;
682 for ( const QgsMapToolIdentify::IdentifyResult &result : constIdResults )
683 {
684 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( result.mLayer );
685 if ( !vl )
686 continue;
687
688 QgsHighlight *hl = new QgsHighlight( mCanvas, result.mFeature.geometry(), vl );
689 hl->applyDefaultStyle();
690 mRubberBands.append( hl );
691 connect( vl, &QObject::destroyed, this, &QgsIdentifyMenu::layerDestroyed );
692 }
693}
694
696{
697 highlight->applyDefaultStyle();
698}
699
700void QgsIdentifyMenu::deleteRubberBands()
701{
702 QList<QgsHighlight *>::const_iterator it = mRubberBands.constBegin();
703 for ( ; it != mRubberBands.constEnd(); ++it )
704 delete *it;
705 mRubberBands.clear();
706}
707
708void QgsIdentifyMenu::layerDestroyed()
709{
710 QList<QgsHighlight *>::iterator it = mRubberBands.begin();
711 while ( it != mRubberBands.end() )
712 {
713 if ( ( *it )->layer() == sender() )
714 {
715 delete *it;
716 it = mRubberBands.erase( it );
717 }
718 else
719 {
720 ++it;
721 }
722 }
723}
724
726{
727 mCustomActionRegistry.clear();
728
729}
730
732{
733 mExpressionContextScope = scope;
734}
735
737{
738 return mExpressionContextScope;
739}
@ Invalid
Invalid.
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition: qgis.h:228
@ MultipleFeatures
Action targets multiple features from a layer.
@ Layer
Action targets a complete layer.
@ SingleFeature
Action targets a single feature from a layer.
This class is a menu that is populated automatically with the actions defined for a given layer.
Definition: qgsactionmenu.h:39
void setExpressionContextScope(const QgsExpressionContextScope &scope)
Sets an expression context scope used to resolve underlying actions.
void setMode(QgsAttributeEditorContext::Mode mode)
Change the mode of the actions.
bool isEmpty() const
Returns true if the menu has no valid actions.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
@ IdentifyMode
Identify the feature.
Class for doing transforms between two map coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Class for parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
static QgsMapLayerActionRegistry * mapLayerActionRegistry()
Returns the global map layer action registry, used for registering map layer actions.
Definition: qgsgui.cpp:123
A class for highlight features on the map.
Definition: qgshighlight.h:62
void applyDefaultStyle()
Applies the default style from the user settings to the highlight.
static QIcon iconForWkbType(Qgis::WkbType type)
Returns the icon for a vector layer whose geometry type is provided.
QList< QgsMapToolIdentify::IdentifyResult > exec(const QList< QgsMapToolIdentify::IdentifyResult > &idResults, QPoint pos)
exec
void setMaxLayerDisplay(int maxLayerDisplay)
Defines the maximum number of layers displayed in the menu (default is 10).
~QgsIdentifyMenu() override
QgsExpressionContextScope expressionContextScope() const
Returns an expression context scope used to resolve underlying actions.
void setMaxFeatureDisplay(int maxFeatureDisplay)
Defines the maximum number of features displayed in the menu for vector layers (default is 10).
QgsIdentifyMenu(QgsMapCanvas *canvas)
QgsIdentifyMenu is a menu to be used to choose within a list of QgsMapTool::IdentifyReults.
void closeEvent(QCloseEvent *e) override
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.
static Q_DECL_DEPRECATED void styleHighlight(QgsHighlight *highlight)
Applies style from the settings to the highlight.
void removeCustomActions()
remove all custom actions from the menu to be built
void setExpressionContextScope(const QgsExpressionContextScope &scope)
Sets an expression context scope used to resolve underlying actions.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:90
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers shown within the map canvas.
const QgsMapToPixel * getCoordinateTransform()
Gets the current coordinate transform.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
Encapsulates the context in which a QgsMapLayerAction action is executed.
QList< QgsMapLayerAction * > mapLayerActions(QgsMapLayer *layer, Qgis::MapLayerActionTargets targets=Qgis::MapLayerActionTarget::AllActions, const QgsMapLayerActionContext &context=QgsMapLayerActionContext())
Returns the map layer actions which can run on the specified layer.
An action which can run on map layers The class can be used in two manners:
Base class for all map layer types.
Definition: qgsmaplayer.h:73
QString name
Definition: qgsmaplayer.h:76
Qgis::LayerType type
Definition: qgsmaplayer.h:80
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Returns the coordinate transform from layer's CRS to destination CRS.
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
Definition: qgsmaptool.cpp:232
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
A rectangle specified with double values.
Definition: qgsrectangle.h:42
Represents a vector layer which manages a vector based data sets.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
Q_INVOKABLE Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
QString displayExpression
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:4093
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:4092
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Qgis::ActionType actionType
Definition: qgsactionmenu.h:54