34#include "moc_qgsidentifymenu.cpp"
36using namespace Qt::StringLiterals;
42 , mDefaultActionName( tr(
"Identify" ) )
53 QList<QgsMapToolIdentify::IdentifyResult> results;
54 const QMap<QString, QString> derivedAttributes;
57 const double x = mapPoint.
x();
58 const double y = mapPoint.
y();
61 const QList<QgsMapLayer *> layers = canvas->
layers(
true );
66 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
68 bool typeIsSelectable =
false;
73 typeIsSelectable =
true;
77 if ( typeIsSelectable )
89 QgsDebugError( u
"Could not transform geometry to layer CRS"_s );
93 .setFilterRect( rect )
111 QgsDebugError( u
"invalid value for number of layers displayed."_s );
121 QgsDebugError( u
"invalid value for number of layers displayed."_s );
127QList<QgsMapToolIdentify::IdentifyResult>
QgsIdentifyMenu::exec(
const QList<QgsMapToolIdentify::IdentifyResult> &idResults, QPoint pos )
130 mLayerIdResults.clear();
132 QList<QgsMapToolIdentify::IdentifyResult> returnResults = QList<QgsMapToolIdentify::IdentifyResult>();
134 if ( idResults.isEmpty() )
136 return returnResults;
138 if ( idResults.count() == 1 && !mExecWithSingleResult )
140 returnResults << idResults[0];
141 return returnResults;
145 const auto constIdResults = idResults;
149 if ( mLayerIdResults.contains( layer ) )
151 mLayerIdResults[layer].append( result );
155 mLayerIdResults.insert( layer, QList<QgsMapToolIdentify::IdentifyResult>() << result );
160 const bool singleLayer = mLayerIdResults.count() == 1;
162 QMapIterator<QgsMapLayer *, QList<QgsMapToolIdentify::IdentifyResult>> it( mLayerIdResults );
163 while ( it.hasNext() )
165 if ( mMaxLayerDisplay != 0 && count > mMaxLayerDisplay )
170 switch ( layer->
type() )
174 addRasterLayer( layer );
182 addVectorLayer( vl, it.value(), singleLayer );
201 if ( !singleLayer && mAllowMultipleReturn && idResults.count() > 1 )
204 QAction *allAction =
new QAction(
QgsApplication::getThemeIcon( u
"/mActionIdentify.svg"_s ), tr(
"%1 All (%2)" ).arg( mDefaultActionName ).arg( idResults.count() ),
this );
205 allAction->setData( QVariant::fromValue<ActionData>(
ActionData(
nullptr ) ) );
206 connect( allAction, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
207 addAction( allAction );
211 QAction *selectedAction = QMenu::exec( pos );
213 returnResults = results( selectedAction, externalAction );
218 qDeleteAll( findChildren<QgsActionMenu *>() );
220 if ( externalAction && !mResultsIfExternalAction )
222 return QList<QgsMapToolIdentify::IdentifyResult>();
226 return returnResults;
233 QMenu::closeEvent( e );
236void QgsIdentifyMenu::addRasterLayer(
QgsMapLayer *layer )
238 QAction *layerAction =
nullptr;
239 QMenu *layerMenu =
nullptr;
241 QList<QgsMapLayerAction *> separators = QList<QgsMapLayerAction *>();
244 const int nCustomActions = layerActions.count();
245 if ( nCustomActions )
247 separators.append( layerActions[0] );
249 if ( mShowFeatureActions )
252 if ( layerActions.count() > nCustomActions )
254 separators.append( layerActions[nCustomActions] );
259 if ( layerActions.isEmpty() )
261 layerAction =
new QAction( layer->
name(),
this );
265 layerMenu =
new QMenu( layer->
name(),
this );
266 layerAction = layerMenu->menuAction();
271 layerAction->setData( QVariant::fromValue<ActionData>(
ActionData( layer ) ) );
272 connect( layerAction, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
273 addAction( layerAction );
280 QAction *identifyFeatureAction =
new QAction( mDefaultActionName, layerMenu );
281 connect( identifyFeatureAction, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
282 identifyFeatureAction->setData( QVariant::fromValue<ActionData>(
ActionData( layer ) ) );
283 layerMenu->addAction( identifyFeatureAction );
286 const auto constLayerActions = layerActions;
287 for ( QgsMapLayerAction *mapLayerAction : constLayerActions )
289 QAction *action =
new QAction( mapLayerAction->icon(), mapLayerAction->text(), layerMenu );
290 action->setData( QVariant::fromValue<ActionData>(
ActionData( layer,
true ) ) );
291 connect( action, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
292 connect( action, &QAction::triggered,
this, &QgsIdentifyMenu::triggerMapLayerAction );
293 layerMenu->addAction( action );
294 if ( separators.contains( mapLayerAction ) )
296 layerMenu->insertSeparator( action );
301void QgsIdentifyMenu::addVectorLayer(
QgsVectorLayer *layer,
const QList<QgsMapToolIdentify::IdentifyResult> &results,
bool singleLayer )
303 QAction *layerAction =
nullptr;
304 QMenu *layerMenu =
nullptr;
310 QList<QgsMapLayerAction *> separators = QList<QgsMapLayerAction *>();
311 QgsMapLayerActionContext actionContext;
312 QList<QgsMapLayerAction *> layerActions = mCustomActionRegistry.mapLayerActions( layer, targets, actionContext );
313 const int nCustomActions = layerActions.count();
314 if ( nCustomActions )
316 separators << layerActions[0];
318 if ( mShowFeatureActions )
322 if ( layerActions.count() > nCustomActions )
324 separators << layerActions[nCustomActions];
333 bool createMenu = results.count() > 1 || !layerActions.isEmpty();
340 if ( !createMenu && mShowFeatureActions )
342 QgsActionMenu *featureActionMenu =
new QgsActionMenu( layer, results[0].mFeature, u
"Feature"_s,
this );
346 createMenu = !featureActionMenu->actions().
isEmpty();
347 delete featureActionMenu;
353 exp.prepare( &context );
354 context.setFeature( results[0].mFeature );
359 QString featureTitle = exp.evaluate( &context ).toString();
360 if ( featureTitle.isEmpty() )
361 featureTitle = QString::number( results[0].mFeature.id() );
362 layerAction =
new QAction( u
"%1 (%2)"_s.arg( layer->
name(), featureTitle ),
this );
374 if ( results.count() > 1 )
376 layerMenu =
new QMenu( layer->
name(),
this );
381 QString featureTitle = exp.evaluate( &context ).toString();
382 if ( featureTitle.isEmpty() )
383 featureTitle = QString::number( results[0].mFeature.id() );
384 layerMenu =
new QMenu( u
"%1 (%2)"_s.arg( layer->
name(), featureTitle ),
this );
386 layerAction = layerMenu->menuAction();
396 layerAction->setData( QVariant::fromValue<ActionData>(
ActionData( layer ) ) );
397 connect( layerAction, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
398 addAction( layerAction );
407 const auto constResults = results;
408 for (
const QgsMapToolIdentify::IdentifyResult &result : constResults )
410 if ( mMaxFeatureDisplay != 0 && count > mMaxFeatureDisplay )
414 QAction *featureAction =
nullptr;
415 QMenu *featureMenu =
nullptr;
416 QgsActionMenu *featureActionMenu =
nullptr;
419 if ( mShowFeatureActions )
421 featureActionMenu =
new QgsActionMenu( layer, result.mFeature, u
"Feature"_s, layerMenu );
429 context.setFeature( result.mFeature );
430 QString featureTitle = exp.evaluate( &context ).toString();
431 if ( featureTitle.isEmpty() )
432 featureTitle = QString::number( result.mFeature.id() );
436 if ( layerMenu ==
this )
437 featureTitle = u
"%1 (%2)"_s.arg( layer->
name(), featureTitle );
439 if ( customFeatureActions.isEmpty() && ( !featureActionMenu || featureActionMenu->actions().
isEmpty() ) )
441 featureAction =
new QAction( featureTitle, layerMenu );
443 featureAction->setData( QVariant::fromValue<ActionData>(
ActionData( layer, result.mFeature.id() ) ) );
444 connect( featureAction, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
445 layerMenu->addAction( featureAction );
447 else if ( results.count() == 1 )
452 featureMenu = layerMenu;
456 featureMenu =
new QMenu( featureTitle, layerMenu );
459 featureAction = featureMenu->menuAction();
461 featureAction->setData( QVariant::fromValue<ActionData>(
ActionData( layer, result.mFeature.id() ) ) );
462 connect( featureAction, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
463 layerMenu->addAction( featureAction );
471 QAction *identifyFeatureAction =
new QAction(
QgsApplication::getThemeIcon( u
"/mActionIdentify.svg"_s ), mDefaultActionName, featureMenu );
472 connect( identifyFeatureAction, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
473 identifyFeatureAction->setData( QVariant::fromValue<ActionData>(
ActionData( layer, result.mFeature.id() ) ) );
474 featureMenu->addAction( identifyFeatureAction );
475 featureMenu->addSeparator();
478 const auto constCustomFeatureActions = customFeatureActions;
479 for ( QgsMapLayerAction *mapLayerAction : constCustomFeatureActions )
481 QAction *action =
new QAction( mapLayerAction->icon(), mapLayerAction->text(), featureMenu );
482 action->setData( QVariant::fromValue<ActionData>(
ActionData( layer, result.mFeature.id(), mapLayerAction ) ) );
483 connect( action, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
484 connect( action, &QAction::triggered,
this, &QgsIdentifyMenu::triggerMapLayerAction );
485 featureMenu->addAction( action );
488 if ( featureActionMenu )
490 const auto constActions = featureActionMenu->actions();
491 for ( QAction *action : constActions )
493 connect( action, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
494 featureMenu->addAction( action );
502 if ( mAllowMultipleReturn && results.count() > 1 )
504 layerMenu->addSeparator();
505 QAction *allAction =
new QAction(
QgsApplication::getThemeIcon( u
"/mActionIdentify.svg"_s ), tr(
"%1 All (%2)" ).arg( mDefaultActionName ).arg( results.count() ), layerMenu );
506 allAction->setData( QVariant::fromValue<ActionData>(
ActionData( layer ) ) );
507 connect( allAction, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
508 layerMenu->addAction( allAction );
512 const auto constLayerActions = layerActions;
513 for ( QgsMapLayerAction *mapLayerAction : constLayerActions )
515 QString title = mapLayerAction->text();
517 title.append( u
" (%1)"_s.arg( results.count() ) );
518 QAction *action =
new QAction( mapLayerAction->icon(), title, layerMenu );
519 action->setData( QVariant::fromValue<ActionData>(
ActionData( layer, mapLayerAction ) ) );
520 connect( action, &QAction::hovered,
this, &QgsIdentifyMenu::handleMenuHover );
521 connect( action, &QAction::triggered,
this, &QgsIdentifyMenu::triggerMapLayerAction );
522 layerMenu->addAction( action );
523 if ( separators.contains( mapLayerAction ) )
525 layerMenu->insertSeparator( action );
530void QgsIdentifyMenu::triggerMapLayerAction()
532 QAction *action = qobject_cast<QAction *>( sender() );
535 const QVariant varData = action->data();
536 if ( !varData.isValid() || !varData.canConvert<
ActionData>() )
541 if ( actData.mIsValid && actData.mMapLayerAction )
543 QgsMapLayerActionContext context;
549 actData.mMapLayerAction->triggerForLayer( actData.mLayer );
551 actData.mMapLayerAction->triggerForLayer( actData.mLayer, context );
557 QList<QgsFeature> featureList;
558 const auto results { mLayerIdResults[actData.mLayer] };
559 for (
const QgsMapToolIdentify::IdentifyResult &result : results )
561 featureList << result.mFeature;
564 actData.mMapLayerAction->triggerForFeatures( actData.mLayer, featureList );
566 actData.mMapLayerAction->triggerForFeatures( actData.mLayer, featureList, context );
572 const auto results { mLayerIdResults[actData.mLayer] };
573 for (
const QgsMapToolIdentify::IdentifyResult &result : results )
575 if ( result.mFeature.id() == actData.mFeatureId )
578 actData.mMapLayerAction->triggerForFeature( actData.mLayer, result.mFeature );
580 actData.mMapLayerAction->triggerForFeature( actData.mLayer, result.mFeature, context );
584 QgsDebugError( u
"Identify menu: could not retrieve feature for action %1"_s.arg( action->text() ) );
590QList<QgsMapToolIdentify::IdentifyResult> QgsIdentifyMenu::results( QAction *action,
bool &externalAction )
592 QList<QgsMapToolIdentify::IdentifyResult> idResults = QList<QgsMapToolIdentify::IdentifyResult>();
594 externalAction =
false;
597 bool hasData =
false;
602 const QVariant varData = action->data();
603 if ( !varData.isValid() )
605 QgsDebugError( u
"Identify menu: could not retrieve results from menu entry (invalid data)"_s );
612 if ( actData.mIsValid )
614 externalAction = actData.mIsExternalAction;
619 if ( !hasData && varData.canConvert<QgsActionMenu::ActionData>() )
621 const QgsActionMenu::ActionData dataSrc = action->data().value<QgsActionMenu::ActionData>();
624 externalAction =
true;
632 QgsDebugError( u
"Identify menu: could not retrieve results from menu entry (no data found)"_s );
637 if ( actData.mAllResults )
640 QMapIterator<QgsMapLayer *, QList<QgsMapToolIdentify::IdentifyResult>> it( mLayerIdResults );
641 while ( it.hasNext() )
644 idResults << it.value();
649 if ( !mLayerIdResults.contains( actData.mLayer ) )
651 QgsDebugError( u
"Identify menu: could not retrieve results from menu entry (layer not found)"_s );
657 return mLayerIdResults[actData.mLayer];
662 const auto results { mLayerIdResults[actData.mLayer] };
663 for (
const QgsMapToolIdentify::IdentifyResult &res : results )
665 if ( res.mFeature.id() == actData.mFeatureId )
673 QgsDebugError( u
"Identify menu: could not retrieve results from menu entry (don't know what happened')"_s );
677void QgsIdentifyMenu::handleMenuHover()
684 QAction *senderAction = qobject_cast<QAction *>( sender() );
689 const QList<QgsMapToolIdentify::IdentifyResult> idResults = results( senderAction, externalAction );
691 const auto constIdResults = idResults;
692 for (
const QgsMapToolIdentify::IdentifyResult &result : constIdResults )
694 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( result.mLayer );
698 QgsHighlight *hl =
new QgsHighlight( mCanvas, result.mFeature.geometry(), vl );
700 mRubberBands.append( hl );
701 connect( vl, &QObject::destroyed,
this, &QgsIdentifyMenu::layerDestroyed );
710void QgsIdentifyMenu::deleteRubberBands()
712 QList<QgsHighlight *>::const_iterator it = mRubberBands.constBegin();
713 for ( ; it != mRubberBands.constEnd(); ++it )
715 mRubberBands.clear();
718void QgsIdentifyMenu::layerDestroyed()
720 QList<QgsHighlight *>::iterator it = mRubberBands.begin();
721 while ( it != mRubberBands.end() )
723 if ( ( *it )->layer() == sender() )
726 it = mRubberBands.erase( it );
737 mCustomActionRegistry.clear();
742 mExpressionContextScope = scope;
747 return mExpressionContextScope;
QFlags< MapLayerActionTarget > MapLayerActionTargets
Map layer action targets.
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
@ MultipleFeatures
Action targets multiple features from a layer.
@ Layer
Action targets a complete layer.
@ SingleFeature
Action targets a single feature from a layer.
@ Group
Composite group layer. Added in QGIS 3.24.
@ Plugin
Plugin based layer.
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
@ Mesh
Mesh layer. Added in QGIS 3.2.
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
@ Reverse
Reverse/inverse transform (from destination to source).
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
@ IdentifyMode
Identify the feature.
Custom exception class for Coordinate Reference System related exceptions.
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.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
static QgsMapLayerActionRegistry * mapLayerActionRegistry()
Returns the global map layer action registry, used for registering map layer actions.
Highlights features on the map.
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.
Map canvas is a class for displaying all GIS data types on a canvas.
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.
Base class for all map layer types.
A mouse event which is the result of a user interaction with 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.
A rectangle specified with double values.
Represents a vector layer which manages a vector based dataset.
Q_INVOKABLE Qgis::WkbType wkbType() const final
Returns the WKBType or WKBUnknown in case of error.
QString displayExpression
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
#define Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_PUSH
#define QgsDebugError(str)