QGIS API Documentation  3.25.0-Master (dec16ba68b)
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
6  email : [email protected]
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 "qgsactionmanager.h"
21 #include "qgshighlight.h"
22 #include "qgsmapcanvas.h"
23 #include "qgsactionmenu.h"
24 #include "qgsvectorlayer.h"
25 #include "qgslogger.h"
26 #include "qgssettings.h"
27 #include "qgsgui.h"
29 #include "qgsiconutils.h"
30 #include "qgsmapmouseevent.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 
51 QList<QgsMapToolIdentify::IdentifyResult> QgsIdentifyMenu::findFeaturesOnCanvas( QgsMapMouseEvent *event, QgsMapCanvas *canvas, const QList<QgsWkbTypes::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() == QgsMapLayerType::VectorLayer )
65  {
66  QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
67 
68  bool typeIsSelectable = false;
69  for ( QgsWkbTypes::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 );
81  transform.setBallparkTransformsAreAppropriate( true );
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 )
94  .setFlags( QgsFeatureRequest::ExactIntersect ) );
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 
107 void 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 
117 void 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 
127 QList<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  {
173  {
174  addRasterLayer( layer );
175  break;
176  }
178  {
179  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
180  if ( !vl )
181  continue;
182  addVectorLayer( vl, it.value(), singleLayer );
183  break;
184  }
185 
187  // TODO: add support
188  break;
189 
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 
229 void QgsIdentifyMenu::closeEvent( QCloseEvent *e )
230 {
231  deleteRubberBands();
232  QMenu::closeEvent( e );
233 }
234 
235 void QgsIdentifyMenu::addRasterLayer( QgsMapLayer *layer )
236 {
237  QAction *layerAction = nullptr;
238  QMenu *layerMenu = nullptr;
239 
240  QList<QgsMapLayerAction *> separators = QList<QgsMapLayerAction *>();
241  QList<QgsMapLayerAction *> layerActions = mCustomActionRegistry.mapLayerActions( layer, QgsMapLayerAction::Layer );
242  const int nCustomActions = layerActions.count();
243  if ( nCustomActions )
244  {
245  separators.append( layerActions[0] );
246  }
247  if ( mShowFeatureActions )
248  {
249  layerActions.append( QgsGui::mapLayerActionRegistry()->mapLayerActions( layer, QgsMapLayerAction::Layer ) );
250  if ( layerActions.count() > nCustomActions )
251  {
252  separators.append( layerActions[nCustomActions] );
253  }
254  }
255 
256  // use a menu only if actions will be listed
257  if ( layerActions.isEmpty() )
258  {
259  layerAction = new QAction( layer->name(), this );
260  }
261  else
262  {
263  layerMenu = new QMenu( layer->name(), this );
264  layerAction = layerMenu->menuAction();
265  }
266 
267  // add layer action to the top menu
268  layerAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconRasterLayer.svg" ) ) );
269  layerAction->setData( QVariant::fromValue<ActionData>( ActionData( layer ) ) );
270  connect( layerAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
271  addAction( layerAction );
272 
273  // no need to go further if there is no menu
274  if ( !layerMenu )
275  return;
276 
277  // add default identify action
278  QAction *identifyFeatureAction = new QAction( mDefaultActionName, layerMenu );
279  connect( identifyFeatureAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
280  identifyFeatureAction->setData( QVariant::fromValue<ActionData>( ActionData( layer ) ) );
281  layerMenu->addAction( identifyFeatureAction );
282 
283  // add custom/layer actions
284  const auto constLayerActions = layerActions;
285  for ( QgsMapLayerAction *mapLayerAction : constLayerActions )
286  {
287  QAction *action = new QAction( mapLayerAction->icon(), mapLayerAction->text(), layerMenu );
288  action->setData( QVariant::fromValue<ActionData>( ActionData( layer, true ) ) );
289  connect( action, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
290  connect( action, &QAction::triggered, this, &QgsIdentifyMenu::triggerMapLayerAction );
291  layerMenu->addAction( action );
292  if ( separators.contains( mapLayerAction ) )
293  {
294  layerMenu->insertSeparator( action );
295  }
296  }
297 }
298 
299 void QgsIdentifyMenu::addVectorLayer( QgsVectorLayer *layer, const QList<QgsMapToolIdentify::IdentifyResult> &results, bool singleLayer )
300 {
301  QAction *layerAction = nullptr;
302  QMenu *layerMenu = nullptr;
303 
304  // do not add actions with MultipleFeatures as target if only 1 feature is found for this layer
305  // targets defines which actions will be shown
306  const QgsMapLayerAction::Targets targets = results.count() > 1 ? QgsMapLayerAction::Layer | QgsMapLayerAction::MultipleFeatures : QgsMapLayerAction::Layer;
307 
308  QList<QgsMapLayerAction *> separators = QList<QgsMapLayerAction *>();
309  QList<QgsMapLayerAction *> layerActions = mCustomActionRegistry.mapLayerActions( layer, targets );
310  const int nCustomActions = layerActions.count();
311  if ( nCustomActions )
312  {
313  separators << layerActions[0];
314  }
315  if ( mShowFeatureActions )
316  {
317  layerActions << QgsGui::mapLayerActionRegistry()->mapLayerActions( layer, targets );
318 
319  if ( layerActions.count() > nCustomActions )
320  {
321  separators << layerActions[nCustomActions];
322  }
323  }
324 
325  // determines if a menu should be created or not. Following cases:
326  // 1. only one result and no feature action to be shown => just create an action
327  // 2. several features (2a) or display feature actions (2b) => create a menu
328  // 3. case 2 but only one layer (singeLayer) => do not create a menu, but give the top menu instead
329 
330  bool createMenu = results.count() > 1 || !layerActions.isEmpty();
331 
332  // case 2b: still create a menu for layer, if there is a sub-level for features
333  // i.e custom actions or map layer actions at feature level
334  if ( !createMenu )
335  {
336  createMenu = !mCustomActionRegistry.mapLayerActions( layer, QgsMapLayerAction::SingleFeature ).isEmpty();
337  if ( !createMenu && mShowFeatureActions )
338  {
339  QgsActionMenu *featureActionMenu = new QgsActionMenu( layer, results[0].mFeature, QStringLiteral( "Feature" ), this );
340  featureActionMenu->setMode( QgsAttributeEditorContext::IdentifyMode );
341  createMenu = !featureActionMenu->actions().isEmpty();
342  delete featureActionMenu;
343  }
344  }
345 
347  QgsExpression exp( layer->displayExpression() );
348  exp.prepare( &context );
349  context.setFeature( results[0].mFeature );
350  // use a menu only if actions will be listed
351  if ( !createMenu )
352  {
353  // case 1
354  QString featureTitle = exp.evaluate( &context ).toString();
355  if ( featureTitle.isEmpty() )
356  featureTitle = QString::number( results[0].mFeature.id() );
357  layerAction = new QAction( QStringLiteral( "%1 (%2)" ).arg( layer->name(), featureTitle ), this );
358  }
359  else
360  {
361  if ( singleLayer )
362  {
363  // case 3
364  layerMenu = this;
365  }
366  else
367  {
368  // case 2a
369  if ( results.count() > 1 )
370  {
371  layerMenu = new QMenu( layer->name(), this );
372  }
373  // case 2b
374  else
375  {
376  QString featureTitle = exp.evaluate( &context ).toString();
377  if ( featureTitle.isEmpty() )
378  featureTitle = QString::number( results[0].mFeature.id() );
379  layerMenu = new QMenu( QStringLiteral( "%1 (%2)" ).arg( layer->name(), featureTitle ), this );
380  }
381  layerAction = layerMenu->menuAction();
382  }
383  }
384 
385  // case 1 or 2
386  if ( layerAction )
387  {
388  layerAction->setIcon( QgsIconUtils::iconForWkbType( layer->wkbType() ) );
389 
390  // add layer action to the top menu
391  layerAction->setData( QVariant::fromValue<ActionData>( ActionData( layer ) ) );
392  connect( layerAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
393  addAction( layerAction );
394  }
395 
396  // case 1. no need to go further
397  if ( !layerMenu )
398  return;
399 
400  // add results to the menu
401  int count = 0;
402  const auto constResults = results;
403  for ( const QgsMapToolIdentify::IdentifyResult &result : constResults )
404  {
405  if ( mMaxFeatureDisplay != 0 && count > mMaxFeatureDisplay )
406  break;
407  ++count;
408 
409  QAction *featureAction = nullptr;
410  QMenu *featureMenu = nullptr;
411  QgsActionMenu *featureActionMenu = nullptr;
412 
413  const QList<QgsMapLayerAction *> customFeatureActions = mCustomActionRegistry.mapLayerActions( layer, QgsMapLayerAction::SingleFeature );
414  if ( mShowFeatureActions )
415  {
416  featureActionMenu = new QgsActionMenu( layer, result.mFeature, QStringLiteral( "Feature" ), layerMenu );
417  featureActionMenu->setMode( QgsAttributeEditorContext::IdentifyMode );
418  featureActionMenu->setExpressionContextScope( mExpressionContextScope );
419  }
420 
421  // feature title
422  context.setFeature( result.mFeature );
423  QString featureTitle = exp.evaluate( &context ).toString();
424  if ( featureTitle.isEmpty() )
425  featureTitle = QString::number( result.mFeature.id() );
426 
427  if ( customFeatureActions.isEmpty() && ( !featureActionMenu || featureActionMenu->actions().isEmpty() ) )
428  {
429  featureAction = new QAction( featureTitle, layerMenu );
430  // add the feature action (or menu) to the layer menu
431  featureAction->setData( QVariant::fromValue<ActionData>( ActionData( layer, result.mFeature.id() ) ) );
432  connect( featureAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
433  layerMenu->addAction( featureAction );
434  }
435  else if ( results.count() == 1 )
436  {
437  // if we are here with only one results, this means there is a sub-feature level (for actions)
438  // => skip the feature level since there would be only a single entry
439  // => give the layer menu as pointer instead of a new feature menu
440  featureMenu = layerMenu;
441  }
442  else
443  {
444  featureMenu = new QMenu( featureTitle, layerMenu );
445 
446  // get the action from the menu
447  featureAction = featureMenu->menuAction();
448  // add the feature action (or menu) to the layer menu
449  featureAction->setData( QVariant::fromValue<ActionData>( ActionData( layer, result.mFeature.id() ) ) );
450  connect( featureAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
451  layerMenu->addAction( featureAction );
452  }
453 
454  // if no feature menu, no need to go further
455  if ( !featureMenu )
456  continue;
457 
458  // add default identify action
459  QAction *identifyFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIdentify.svg" ) ), mDefaultActionName, featureMenu );
460  connect( identifyFeatureAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
461  identifyFeatureAction->setData( QVariant::fromValue<ActionData>( ActionData( layer, result.mFeature.id() ) ) );
462  featureMenu->addAction( identifyFeatureAction );
463  featureMenu->addSeparator();
464 
465  // custom action at feature level
466  const auto constCustomFeatureActions = customFeatureActions;
467  for ( QgsMapLayerAction *mapLayerAction : constCustomFeatureActions )
468  {
469  QAction *action = new QAction( mapLayerAction->icon(), mapLayerAction->text(), featureMenu );
470  action->setData( QVariant::fromValue<ActionData>( ActionData( layer, result.mFeature.id(), mapLayerAction ) ) );
471  connect( action, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
472  connect( action, &QAction::triggered, this, &QgsIdentifyMenu::triggerMapLayerAction );
473  featureMenu->addAction( action );
474  }
475  // use QgsActionMenu for feature actions
476  if ( featureActionMenu )
477  {
478  const auto constActions = featureActionMenu->actions();
479  for ( QAction *action : constActions )
480  {
481  connect( action, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
482  featureMenu->addAction( action );
483  }
484  }
485  }
486 
487  // back to layer level
488 
489  // identify all action
490  if ( mAllowMultipleReturn && results.count() > 1 )
491  {
492  layerMenu->addSeparator();
493  QAction *allAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIdentify.svg" ) ), tr( "%1 All (%2)" ).arg( mDefaultActionName ).arg( results.count() ), layerMenu );
494  allAction->setData( QVariant::fromValue<ActionData>( ActionData( layer ) ) );
495  connect( allAction, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
496  layerMenu->addAction( allAction );
497  }
498 
499  // add custom/layer actions
500  const auto constLayerActions = layerActions;
501  for ( QgsMapLayerAction *mapLayerAction : constLayerActions )
502  {
503  QString title = mapLayerAction->text();
504  if ( mapLayerAction->targets().testFlag( QgsMapLayerAction::MultipleFeatures ) )
505  title.append( QStringLiteral( " (%1)" ).arg( results.count() ) );
506  QAction *action = new QAction( mapLayerAction->icon(), title, layerMenu );
507  action->setData( QVariant::fromValue<ActionData>( ActionData( layer, mapLayerAction ) ) );
508  connect( action, &QAction::hovered, this, &QgsIdentifyMenu::handleMenuHover );
509  connect( action, &QAction::triggered, this, &QgsIdentifyMenu::triggerMapLayerAction );
510  layerMenu->addAction( action );
511  if ( separators.contains( mapLayerAction ) )
512  {
513  layerMenu->insertSeparator( action );
514  }
515  }
516 }
517 
518 void QgsIdentifyMenu::triggerMapLayerAction()
519 {
520  QAction *action = qobject_cast<QAction *>( sender() );
521  if ( !action )
522  return;
523  const QVariant varData = action->data();
524  if ( !varData.isValid() || !varData.canConvert<ActionData>() )
525  return;
526 
527  ActionData actData = action->data().value<ActionData>();
528 
529  if ( actData.mIsValid && actData.mMapLayerAction )
530  {
531  // layer
532  if ( actData.mMapLayerAction->targets().testFlag( QgsMapLayerAction::Layer ) )
533  {
534  actData.mMapLayerAction->triggerForLayer( actData.mLayer );
535  }
536 
537  // multiples features
538  if ( actData.mMapLayerAction->targets().testFlag( QgsMapLayerAction::MultipleFeatures ) )
539  {
540  QList<QgsFeature> featureList;
541  const auto results { mLayerIdResults[actData.mLayer] };
542  for ( const QgsMapToolIdentify::IdentifyResult &result : results )
543  {
544  featureList << result.mFeature;
545  }
546  actData.mMapLayerAction->triggerForFeatures( actData.mLayer, featureList );
547  }
548 
549  // single feature
550  if ( actData.mMapLayerAction->targets().testFlag( QgsMapLayerAction::SingleFeature ) )
551  {
552  const auto results { mLayerIdResults[actData.mLayer] };
553  for ( const QgsMapToolIdentify::IdentifyResult &result : results )
554  {
555  if ( result.mFeature.id() == actData.mFeatureId )
556  {
557  actData.mMapLayerAction->triggerForFeature( actData.mLayer, result.mFeature );
558  return;
559  }
560  }
561  QgsDebugMsg( QStringLiteral( "Identify menu: could not retrieve feature for action %1" ).arg( action->text() ) );
562  }
563  }
564 }
565 
566 
567 QList<QgsMapToolIdentify::IdentifyResult> QgsIdentifyMenu::results( QAction *action, bool &externalAction )
568 {
569  QList<QgsMapToolIdentify::IdentifyResult> idResults = QList<QgsMapToolIdentify::IdentifyResult>();
570 
571  externalAction = false;
572 
573  ActionData actData;
574  bool hasData = false;
575 
576  if ( !action )
577  return idResults;
578 
579  const QVariant varData = action->data();
580  if ( !varData.isValid() )
581  {
582  QgsDebugMsg( QStringLiteral( "Identify menu: could not retrieve results from menu entry (invalid data)" ) );
583  return idResults;
584  }
585 
586  if ( varData.canConvert<ActionData>() )
587  {
588  actData = action->data().value<ActionData>();
589  if ( actData.mIsValid )
590  {
591  externalAction = actData.mIsExternalAction;
592  hasData = true;
593  }
594  }
595 
596  if ( !hasData && varData.canConvert<QgsActionMenu::ActionData>() )
597  {
598  const QgsActionMenu::ActionData dataSrc = action->data().value<QgsActionMenu::ActionData>();
599  if ( dataSrc.actionType != QgsActionMenu::Invalid )
600  {
601  externalAction = true;
602  actData = ActionData( dataSrc.mapLayer, dataSrc.featureId );
603  hasData = true;
604  }
605  }
606 
607  if ( !hasData )
608  {
609  QgsDebugMsg( QStringLiteral( "Identify menu: could not retrieve results from menu entry (no data found)" ) );
610  return idResults;
611  }
612 
613  // return all results
614  if ( actData.mAllResults )
615  {
616  // this means "All" action was triggered
617  QMapIterator< QgsMapLayer *, QList<QgsMapToolIdentify::IdentifyResult> > it( mLayerIdResults );
618  while ( it.hasNext() )
619  {
620  it.next();
621  idResults << it.value();
622  }
623  return idResults;
624  }
625 
626  if ( !mLayerIdResults.contains( actData.mLayer ) )
627  {
628  QgsDebugMsg( QStringLiteral( "Identify menu: could not retrieve results from menu entry (layer not found)" ) );
629  return idResults;
630  }
631 
632  if ( actData.mLevel == LayerLevel )
633  {
634  return mLayerIdResults[actData.mLayer];
635  }
636 
637  if ( actData.mLevel == FeatureLevel )
638  {
639  const auto results {mLayerIdResults[actData.mLayer]};
640  for ( const QgsMapToolIdentify::IdentifyResult &res : results )
641  {
642  if ( res.mFeature.id() == actData.mFeatureId )
643  {
644  idResults << res;
645  return idResults;
646  }
647  }
648  }
649 
650  QgsDebugMsg( QStringLiteral( "Identify menu: could not retrieve results from menu entry (don't know what happened')" ) );
651  return idResults;
652 }
653 
654 void QgsIdentifyMenu::handleMenuHover()
655 {
656  if ( !mCanvas )
657  return;
658 
659  deleteRubberBands();
660 
661  QAction *senderAction = qobject_cast<QAction *>( sender() );
662  if ( !senderAction )
663  return;
664 
665  bool externalAction;
666  const QList<QgsMapToolIdentify::IdentifyResult> idResults = results( senderAction, externalAction );
667 
668  const auto constIdResults = idResults;
669  for ( const QgsMapToolIdentify::IdentifyResult &result : constIdResults )
670  {
671  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( result.mLayer );
672  if ( !vl )
673  continue;
674 
675  QgsHighlight *hl = new QgsHighlight( mCanvas, result.mFeature.geometry(), vl );
676  styleHighlight( hl );
677  mRubberBands.append( hl );
678  connect( vl, &QObject::destroyed, this, &QgsIdentifyMenu::layerDestroyed );
679  }
680 }
681 
683 {
684  const QgsSettings settings;
685  QColor color = QColor( settings.value( QStringLiteral( "Map/highlight/color" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
686  const int alpha = settings.value( QStringLiteral( "Map/highlight/colorAlpha" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
687  const double buffer = settings.value( QStringLiteral( "Map/highlight/buffer" ), Qgis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
688  const double minWidth = settings.value( QStringLiteral( "Map/highlight/minWidth" ), Qgis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
689 
690  highlight->setColor( color ); // sets also fill with default alpha
691  color.setAlpha( alpha );
692  highlight->setFillColor( color ); // sets fill with alpha
693  highlight->setBuffer( buffer );
694  highlight->setMinWidth( minWidth );
695 }
696 
697 void QgsIdentifyMenu::deleteRubberBands()
698 {
699  QList<QgsHighlight *>::const_iterator it = mRubberBands.constBegin();
700  for ( ; it != mRubberBands.constEnd(); ++it )
701  delete *it;
702  mRubberBands.clear();
703 }
704 
705 void QgsIdentifyMenu::layerDestroyed()
706 {
707  QList<QgsHighlight *>::iterator it = mRubberBands.begin();
708  while ( it != mRubberBands.end() )
709  {
710  if ( ( *it )->layer() == sender() )
711  {
712  delete *it;
713  it = mRubberBands.erase( it );
714  }
715  else
716  {
717  ++it;
718  }
719  }
720 }
721 
723 {
724  mCustomActionRegistry.clear();
725 
726 }
727 
729 {
730  mExpressionContextScope = scope;
731 }
732 
734 {
735  return mExpressionContextScope;
736 }
static const double DEFAULT_HIGHLIGHT_MIN_WIDTH_MM
Default highlight line/stroke minimum width in mm.
Definition: qgis.h:1756
static const double DEFAULT_HIGHLIGHT_BUFFER_MM
Default highlight buffer in mm.
Definition: qgis.h:1750
static const QColor DEFAULT_HIGHLIGHT_COLOR
Default highlight color.
Definition: qgis.h:1744
This class is a menu that is populated automatically with the actions defined for a given layer.
Definition: qgsactionmenu.h:38
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.
@ Invalid
Invalid.
Definition: qgsactionmenu.h:44
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:125
A class for highlight features on the map.
Definition: qgshighlight.h:62
void setBuffer(double buffer)
Set line / stroke buffer in millimeters.
Definition: qgshighlight.h:159
void setMinWidth(double width)
Set minimum line / stroke width in millimeters.
Definition: qgshighlight.h:166
void setFillColor(const QColor &fillColor)
Fill color for the highlight.
void setColor(const QColor &color)
Set line/stroke to color, polygon fill to color with alpha = 63.
static QIcon iconForWkbType(QgsWkbTypes::Type 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 void styleHighlight(QgsHighlight *highlight)
Applies style from the settings to the highlight.
static QList< QgsMapToolIdentify::IdentifyResult > findFeaturesOnCanvas(QgsMapMouseEvent *event, QgsMapCanvas *canvas, const QList< QgsWkbTypes::GeometryType > &geometryTypes)
Searches for features on the map canvas, which are located at the specified event point.
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.
QList< QgsMapLayerAction * > mapLayerActions(QgsMapLayer *layer, QgsMapLayerAction::Targets targets=QgsMapLayerAction::AllActions)
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
QgsMapLayerType 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
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QString displayExpression
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:141
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsActionMenu::ActionType actionType
Definition: qgsactionmenu.h:59