From c9c1c34952ed1aa158448e24d5eddb671f9c0cee Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 10 Nov 2017 15:01:08 +0000 Subject: [PATCH 1/2] Fixes #16852 by adding click_x and click_y variables to resolve actions --- python/core/qgsaction.sip | 15 +++++++++++++++ python/core/qgsactionmanager.sip | 11 ++++++++--- python/gui/qgsactionmenu.sip | 15 +++++++++++++++ python/gui/qgsidentifymenu.sip | 15 +++++++++++++++ src/app/qgsidentifyresultsdialog.cpp | 14 +++++++++++++- src/app/qgsidentifyresultsdialog.h | 18 ++++++++++++++++++ src/app/qgsmaptoolidentifyaction.cpp | 15 +++++++++++++++ src/app/qgsmaptoolidentifyaction.h | 2 ++ src/core/qgsaction.cpp | 16 +++++++++++++++- src/core/qgsaction.h | 15 +++++++++++++++ src/core/qgsactionmanager.cpp | 4 ++-- src/core/qgsactionmanager.h | 13 +++++++++---- src/gui/qgsactionmenu.cpp | 17 ++++++++++++++++- src/gui/qgsactionmenu.h | 15 +++++++++++++++ src/gui/qgsidentifymenu.cpp | 11 +++++++++++ src/gui/qgsidentifymenu.h | 16 ++++++++++++++++ 16 files changed, 200 insertions(+), 12 deletions(-) diff --git a/python/core/qgsaction.sip b/python/core/qgsaction.sip index a52a65c7af5..3c4a2c79a94 100644 --- a/python/core/qgsaction.sip +++ b/python/core/qgsaction.sip @@ -184,6 +184,21 @@ Checks if the action is runable on the current platform .. versionadded:: 3.0 %End + void setExpressionContextScope( const QgsExpressionContextScope &scope ); +%Docstring + Sets an expression context scope to use for running the action. + +.. versionadded:: 3.0 +%End + + QgsExpressionContextScope expressionContextScope() const; +%Docstring + Returns an expression context scope used for running the action. + +.. versionadded:: 3.0 + :rtype: QgsExpressionContextScope +%End + }; diff --git a/python/core/qgsactionmanager.sip b/python/core/qgsactionmanager.sip index 834c0021e34..9a00c8aca03 100644 --- a/python/core/qgsactionmanager.sip +++ b/python/core/qgsactionmanager.sip @@ -62,10 +62,15 @@ Constructor .. versionadded:: 3.0 %End - void doAction( const QUuid &actionId, const QgsFeature &feature, int defaultValueIndex = 0 ) /PyName=doActionFeature/; + void doAction( const QUuid &actionId, const QgsFeature &feature, int defaultValueIndex = 0, const QgsExpressionContextScope &scope = QgsExpressionContextScope() ) /PyName=doActionFeature/; %Docstring - Does the given action. defaultValueIndex is the index of the - field to be used if the action has a $currfield placeholder. + Does the given action. + + \param actionId action id + \param feature feature to run action for + \param defaultValueIndex index of the field to be used if the action has a $currfield placeholder. + \param scope expression context scope to add during expression evaluation + .. note:: available in Python bindings as doActionFeature diff --git a/python/gui/qgsactionmenu.sip b/python/gui/qgsactionmenu.sip index 420aaefdeb3..ee6b3540936 100644 --- a/python/gui/qgsactionmenu.sip +++ b/python/gui/qgsactionmenu.sip @@ -73,6 +73,21 @@ class QgsActionMenu : QMenu as long as the menu is displayed and the action is running. %End + void setExpressionContextScope( const QgsExpressionContextScope &scope ); +%Docstring + Sets an expression context scope used to resolve underlying actions. + +.. versionadded:: 3.0 +%End + + QgsExpressionContextScope expressionContextScope() const; +%Docstring + Returns an expression context scope used to resolve underlying actions. + +.. versionadded:: 3.0 + :rtype: QgsExpressionContextScope +%End + signals: void reinit(); diff --git a/python/gui/qgsidentifymenu.sip b/python/gui/qgsidentifymenu.sip index c7cf3b55b32..e835f52b091 100644 --- a/python/gui/qgsidentifymenu.sip +++ b/python/gui/qgsidentifymenu.sip @@ -71,6 +71,21 @@ define if the menu will be shown with a single idetify result :rtype: bool %End + void setExpressionContextScope( const QgsExpressionContextScope &scope ); +%Docstring + Sets an expression context scope used to resolve underlying actions. + +.. versionadded:: 3.0 +%End + + QgsExpressionContextScope expressionContextScope() const; +%Docstring + Returns an expression context scope used to resolve underlying actions. + +.. versionadded:: 3.0 + :rtype: QgsExpressionContextScope +%End + void setShowFeatureActions( bool showFeatureActions ); %Docstring define if attribute actions(1) and map layer actions(2) can be listed and run from the menu diff --git a/src/app/qgsidentifyresultsdialog.cpp b/src/app/qgsidentifyresultsdialog.cpp index 950ebb53bf1..39a471cc62a 100644 --- a/src/app/qgsidentifyresultsdialog.cpp +++ b/src/app/qgsidentifyresultsdialog.cpp @@ -1164,6 +1164,8 @@ void QgsIdentifyResultsDialog::clear() delete curve; mPlotCurves.clear(); + mExpressionContextScope = QgsExpressionContextScope(); + // keep it visible but disabled, it can switch from disabled/enabled // after raster format change mActionPrint->setDisabled( true ); @@ -1245,7 +1247,7 @@ void QgsIdentifyResultsDialog::doAction( QTreeWidgetItem *item, const QString &a } int featIdx = featItem->data( 0, Qt::UserRole + 1 ).toInt(); - layer->actions()->doAction( action, mFeatures[ featIdx ], idx ); + layer->actions()->doAction( action, mFeatures[ featIdx ], idx, mExpressionContextScope ); } void QgsIdentifyResultsDialog::doMapLayerAction( QTreeWidgetItem *item, QgsMapLayerAction *action ) @@ -1978,3 +1980,13 @@ void QgsIdentifyResultsDialog::showHelp() { QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#identify" ) ); } + +void QgsIdentifyResultsDialog::setExpressionContextScope( const QgsExpressionContextScope &scope ) +{ + mExpressionContextScope = scope; +} + +QgsExpressionContextScope QgsIdentifyResultsDialog::expressionContextScope() const +{ + return mExpressionContextScope; +} diff --git a/src/app/qgsidentifyresultsdialog.h b/src/app/qgsidentifyresultsdialog.h index 661a1850746..abd06894d4e 100644 --- a/src/app/qgsidentifyresultsdialog.h +++ b/src/app/qgsidentifyresultsdialog.h @@ -25,6 +25,7 @@ #include "qgscoordinatereferencesystem.h" #include "qgsmaptoolidentify.h" #include "qgswebview.h" +#include "qgsexpressioncontext.h" #include #include @@ -150,6 +151,22 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti //! Map tool was activated void activate(); + /** + * Sets an expression context scope to consider for resolving underlying + * actions. + * + * \since QGIS 3.0 + */ + void setExpressionContextScope( const QgsExpressionContextScope &scope ); + + /** + * Returns an expression context scope used for resolving underlying + * actions. + * + * \since QGIS 3.0 + */ + QgsExpressionContextScope expressionContextScope() const; + signals: void selectedFeatureChanged( QgsVectorLayer *, QgsFeatureId featureId ); @@ -233,6 +250,7 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti QgsMapCanvas *mCanvas = nullptr; QList mFeatures; QMap< QString, QMap< QString, QVariant > > mWidgetCaches; + QgsExpressionContextScope mExpressionContextScope; QgsMapLayer *layer( QTreeWidgetItem *item ); QgsVectorLayer *vectorLayer( QTreeWidgetItem *item ); diff --git a/src/app/qgsmaptoolidentifyaction.cpp b/src/app/qgsmaptoolidentifyaction.cpp index 92ff9d43e20..a6ff334e201 100644 --- a/src/app/qgsmaptoolidentifyaction.cpp +++ b/src/app/qgsmaptoolidentifyaction.cpp @@ -37,6 +37,7 @@ #include "qgsrenderer.h" #include "qgsunittypes.h" #include "qgsstatusbar.h" +#include "qgsactionscoperegistry.h" #include "qgssettings.h" #include @@ -119,6 +120,8 @@ void QgsMapToolIdentifyAction::canvasReleaseEvent( QgsMapMouseEvent *e ) connect( this, &QgsMapToolIdentifyAction::identifyProgress, QgisApp::instance(), &QgisApp::showProgress ); connect( this, &QgsMapToolIdentifyAction::identifyMessage, QgisApp::instance(), &QgisApp::showStatusMessage ); + setClickContextScope( toMapCoordinates( e->pos() ) ); + identifyMenu()->setResultsIfExternalAction( false ); // enable the right click for extended menu so it behaves as a contextual menu @@ -201,4 +204,16 @@ void QgsMapToolIdentifyAction::handleCopyToClipboard( QgsFeatureStore &featureSt emit copyToClipboard( featureStore ); } +void QgsMapToolIdentifyAction::setClickContextScope( const QgsPointXY &point ) +{ + QgsExpressionContextScope clickScope; + clickScope.addVariable( QgsExpressionContextScope::StaticVariable( QString( "click_x" ), point.x(), true ) ); + clickScope.addVariable( QgsExpressionContextScope::StaticVariable( QString( "click_y" ), point.y(), true ) ); + resultsDialog()->setExpressionContextScope( clickScope ); + + if ( mIdentifyMenu ) + { + mIdentifyMenu->setExpressionContextScope( clickScope ); + } +} diff --git a/src/app/qgsmaptoolidentifyaction.h b/src/app/qgsmaptoolidentifyaction.h index 018553ad26d..fb801387920 100644 --- a/src/app/qgsmaptoolidentifyaction.h +++ b/src/app/qgsmaptoolidentifyaction.h @@ -78,7 +78,9 @@ class APP_EXPORT QgsMapToolIdentifyAction : public QgsMapToolIdentify virtual QgsUnitTypes::DistanceUnit displayDistanceUnits() const override; virtual QgsUnitTypes::AreaUnit displayAreaUnits() const override; + void setClickContextScope( const QgsPointXY &point ); + friend class TestQgsMapToolIdentifyAction; }; #endif diff --git a/src/core/qgsaction.cpp b/src/core/qgsaction.cpp index 3f869cc45c4..d24ff4b316c 100644 --- a/src/core/qgsaction.cpp +++ b/src/core/qgsaction.cpp @@ -59,7 +59,11 @@ void QgsAction::run( const QgsExpressionContext &expressionContext ) const return; } - QString expandedAction = QgsExpression::replaceExpressionText( mCommand, &expressionContext ); + QgsExpressionContextScope *scope = new QgsExpressionContextScope( mExpressionContextScope ); + QgsExpressionContext context( expressionContext ); + context << scope; + + QString expandedAction = QgsExpression::replaceExpressionText( mCommand, &context ); if ( mType == QgsAction::OpenUrl ) { @@ -146,3 +150,13 @@ void QgsAction::writeXml( QDomNode &actionsNode ) const actionsNode.appendChild( actionSetting ); } + +void QgsAction::setExpressionContextScope( const QgsExpressionContextScope &scope ) +{ + mExpressionContextScope = scope; +} + +QgsExpressionContextScope QgsAction::expressionContextScope() const +{ + return mExpressionContextScope; +}; diff --git a/src/core/qgsaction.h b/src/core/qgsaction.h index 27eaec689d6..b6a4e165b8c 100644 --- a/src/core/qgsaction.h +++ b/src/core/qgsaction.h @@ -191,6 +191,20 @@ class CORE_EXPORT QgsAction */ void writeXml( QDomNode &actionsNode ) const; + /** + * Sets an expression context scope to use for running the action. + * + * \since QGIS 3.0 + */ + void setExpressionContextScope( const QgsExpressionContextScope &scope ); + + /** + * Returns an expression context scope used for running the action. + * + * \since QGIS 3.0 + */ + QgsExpressionContextScope expressionContextScope() const; + private: ActionType mType = Generic; QString mDescription; @@ -202,6 +216,7 @@ class CORE_EXPORT QgsAction QString mNotificationMessage; mutable std::shared_ptr mAction; QUuid mId; + QgsExpressionContextScope mExpressionContextScope; }; Q_DECLARE_METATYPE( QgsAction ) diff --git a/src/core/qgsactionmanager.cpp b/src/core/qgsactionmanager.cpp index 325a5bcdd4f..0c7ce83971a 100644 --- a/src/core/qgsactionmanager.cpp +++ b/src/core/qgsactionmanager.cpp @@ -123,10 +123,10 @@ void QgsActionManager::removeAction( const QUuid &actionId ) } } -void QgsActionManager::doAction( const QUuid &actionId, const QgsFeature &feature, int defaultValueIndex ) +void QgsActionManager::doAction( const QUuid &actionId, const QgsFeature &feature, int defaultValueIndex, const QgsExpressionContextScope &scope ) { QgsExpressionContext context = createExpressionContext(); - QgsExpressionContextScope *actionScope = new QgsExpressionContextScope(); + QgsExpressionContextScope *actionScope = new QgsExpressionContextScope( scope ); actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_index" ), defaultValueIndex, true ) ); if ( defaultValueIndex >= 0 && defaultValueIndex < feature.fields().size() ) actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_name" ), feature.fields().at( defaultValueIndex ).name(), true ) ); diff --git a/src/core/qgsactionmanager.h b/src/core/qgsactionmanager.h index 966cf2ae617..e65a509ea61 100644 --- a/src/core/qgsactionmanager.h +++ b/src/core/qgsactionmanager.h @@ -89,11 +89,16 @@ class CORE_EXPORT QgsActionManager: public QObject void removeAction( const QUuid &actionId ); /** - * Does the given action. defaultValueIndex is the index of the - * field to be used if the action has a $currfield placeholder. - * \note available in Python bindings as doActionFeature + * Does the given action. + * + * \param actionId action id + * \param feature feature to run action for + * \param defaultValueIndex index of the field to be used if the action has a $currfield placeholder. + * \param scope expression context scope to add during expression evaluation + * + * \note available in Python bindings as doActionFeature */ - void doAction( const QUuid &actionId, const QgsFeature &feature, int defaultValueIndex = 0 ) SIP_PYNAME( doActionFeature ); + void doAction( const QUuid &actionId, const QgsFeature &feature, int defaultValueIndex = 0, const QgsExpressionContextScope &scope = QgsExpressionContextScope() ) SIP_PYNAME( doActionFeature ); /** * Does the action using the expression engine to replace any embedded expressions diff --git a/src/gui/qgsactionmenu.cpp b/src/gui/qgsactionmenu.cpp index 76836729578..c7371b7d5b4 100644 --- a/src/gui/qgsactionmenu.cpp +++ b/src/gui/qgsactionmenu.cpp @@ -107,8 +107,11 @@ void QgsActionMenu::reloadActions() Q_FOREACH ( const QgsAction &action, mActions ) { + QgsAction act( action ); + act.setExpressionContextScope( mExpressionContextScope ); + QAction *qAction = new QAction( action.icon(), action.name(), this ); - qAction->setData( QVariant::fromValue( ActionData( action, mFeatureId, mLayer ) ) ); + qAction->setData( QVariant::fromValue( ActionData( act, mFeatureId, mLayer ) ) ); qAction->setIcon( action.icon() ); // Only enable items on supported platforms @@ -160,3 +163,15 @@ QgsActionMenu::ActionData::ActionData( const QgsAction &action, QgsFeatureId fea , featureId( featureId ) , mapLayer( mapLayer ) {} + + +void QgsActionMenu::setExpressionContextScope( const QgsExpressionContextScope &scope ) +{ + mExpressionContextScope = scope; + reloadActions(); +} + +QgsExpressionContextScope QgsActionMenu::expressionContextScope() const +{ + return mExpressionContextScope; +} diff --git a/src/gui/qgsactionmenu.h b/src/gui/qgsactionmenu.h index 8adc3ee8640..5604272b7cd 100644 --- a/src/gui/qgsactionmenu.h +++ b/src/gui/qgsactionmenu.h @@ -91,6 +91,20 @@ class GUI_EXPORT QgsActionMenu : public QMenu */ void setFeature( const QgsFeature &feature ); + /** + * Sets an expression context scope used to resolve underlying actions. + * + * \since QGIS 3.0 + */ + void setExpressionContextScope( const QgsExpressionContextScope &scope ); + + /** + * Returns an expression context scope used to resolve underlying actions. + * + * \since QGIS 3.0 + */ + QgsExpressionContextScope expressionContextScope() const; + signals: void reinit(); @@ -107,6 +121,7 @@ class GUI_EXPORT QgsActionMenu : public QMenu QgsFeature mFeature; QgsFeatureId mFeatureId; QString mActionScope; + QgsExpressionContextScope mExpressionContextScope; }; diff --git a/src/gui/qgsidentifymenu.cpp b/src/gui/qgsidentifymenu.cpp index e1e7aa8d8c7..33cc70a251b 100644 --- a/src/gui/qgsidentifymenu.cpp +++ b/src/gui/qgsidentifymenu.cpp @@ -345,6 +345,7 @@ void QgsIdentifyMenu::addVectorLayer( QgsVectorLayer *layer, const QListsetExpressionContextScope( mExpressionContextScope ); } // feature title @@ -639,3 +640,13 @@ void QgsIdentifyMenu::removeCustomActions() mCustomActionRegistry.clear(); } + +void QgsIdentifyMenu::setExpressionContextScope( const QgsExpressionContextScope &scope ) +{ + mExpressionContextScope = scope; +} + +QgsExpressionContextScope QgsIdentifyMenu::expressionContextScope() const +{ + return mExpressionContextScope; +} diff --git a/src/gui/qgsidentifymenu.h b/src/gui/qgsidentifymenu.h index 1c2073b4189..3583f1c1950 100644 --- a/src/gui/qgsidentifymenu.h +++ b/src/gui/qgsidentifymenu.h @@ -103,6 +103,20 @@ class GUI_EXPORT QgsIdentifyMenu : public QMenu void setExecWithSingleResult( bool execWithSingleResult ) { mExecWithSingleResult = execWithSingleResult;} bool execWithSingleResult() { return mExecWithSingleResult;} + /** + * Sets an expression context scope used to resolve underlying actions. + * + * \since QGIS 3.0 + */ + void setExpressionContextScope( const QgsExpressionContextScope &scope ); + + /** + * Returns an expression context scope used to resolve underlying actions. + * + * \since QGIS 3.0 + */ + QgsExpressionContextScope expressionContextScope() const; + /** * \brief define if attribute actions(1) and map layer actions(2) can be listed and run from the menu * \note custom actions will be shown in any case if they exist. @@ -178,6 +192,8 @@ class GUI_EXPORT QgsIdentifyMenu : public QMenu int mMaxLayerDisplay; int mMaxFeatureDisplay; + QgsExpressionContextScope mExpressionContextScope; + // name of the action to be displayed for feature default action, if other actions are shown QString mDefaultActionName; From 96e597a8e2d71f2840b9ae3025b2736672176686 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 10 Nov 2017 15:14:14 +0000 Subject: [PATCH 2/2] Add some tests --- src/app/qgisapp.cpp | 3 + tests/src/app/CMakeLists.txt | 1 + .../src/app/testqgsmaptoolidentifyaction.cpp | 101 ++++++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 95144688108..f6c11b8c5d3 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -1273,6 +1273,9 @@ QgisApp::QgisApp() mUndoWidget = new QgsUndoWidget( nullptr, mMapCanvas ); mInfoBar = new QgsMessageBar( centralWidget() ); mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mMapCanvas, this ); + mPanelMenu = new QMenu( this ); + mProgressBar = new QProgressBar( this ); + mStatusBar = new QgsStatusBar( this ); // More tests may need more members to be initialized } diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index 9d618f6a2fc..a9645580986 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -32,6 +32,7 @@ INCLUDE_DIRECTORIES( ) INCLUDE_DIRECTORIES(SYSTEM ${QT_INCLUDE_DIR} + ${QWT_INCLUDE_DIR} ${GDAL_INCLUDE_DIR} ${PROJ_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} diff --git a/tests/src/app/testqgsmaptoolidentifyaction.cpp b/tests/src/app/testqgsmaptoolidentifyaction.cpp index 38ee3efcbf6..1eabf17e27f 100644 --- a/tests/src/app/testqgsmaptoolidentifyaction.cpp +++ b/tests/src/app/testqgsmaptoolidentifyaction.cpp @@ -26,6 +26,14 @@ #include "qgsunittypes.h" #include "qgsmaptoolidentifyaction.h" #include "qgssettings.h" +#include "qgsidentifymenu.h" +#include "qgisapp.h" +#include "qgsaction.h" +#include "qgsactionmanager.h" +#include "qgsactionmenu.h" +#include "qgsidentifyresultsdialog.h" + +#include #include "cpl_conv.h" @@ -46,9 +54,14 @@ class TestQgsMapToolIdentifyAction : public QObject void identifyRasterFloat32(); // test pixel identification and decimal precision void identifyRasterFloat64(); // test pixel identification and decimal precision void identifyInvalidPolygons(); // test selecting invalid polygons + void clickxy(); // test if clicked_x and clicked_y variables are propagated private: + void doAction(); + QgsMapCanvas *canvas = nullptr; + QgsMapToolIdentifyAction *mIdentifyAction = nullptr; + QgisApp *mQgisApp = nullptr; QString testIdentifyRaster( QgsRasterLayer *layer, double xGeoref, double yGeoref ); QList testIdentifyVector( QgsVectorLayer *layer, double xGeoref, double yGeoref ); @@ -91,6 +104,8 @@ void TestQgsMapToolIdentifyAction::initTestCase() // enforce C locale because the tests expect it // (decimal separators / thousand separators) QLocale::setDefault( QLocale::c() ); + + mQgisApp = new QgisApp(); } void TestQgsMapToolIdentifyAction::cleanupTestCase() @@ -108,6 +123,92 @@ void TestQgsMapToolIdentifyAction::cleanup() delete canvas; } +void TestQgsMapToolIdentifyAction::doAction() +{ + bool ok = false; + int clickxOk = 2484588; + int clickyOk = 2425722; + + // test QActionMenu + QList actions = mIdentifyAction->identifyMenu()->actions(); + bool testDone = false; + + for ( int i = 0; i < actions.count(); i++ ) + { + if ( actions[i]->text().compare( "MyAction" ) == 0 ) + { + QgsActionMenu::ActionData data = actions[i]->data().value(); + QgsAction act = data.actionData.value(); + + int clickx = act.expressionContextScope().variable( "click_x" ).toString().toInt( &ok, 10 ); + QCOMPARE( clickx, clickxOk ); + + int clicky = act.expressionContextScope().variable( "click_y" ).toString().toInt( &ok, 10 ); + QCOMPARE( clicky, clickyOk ); + + testDone = true; + } + } + + QCOMPARE( testDone, true ); + + // test QgsIdentifyResultsDialog expression context scope + QgsIdentifyResultsDialog *dlg = mIdentifyAction->resultsDialog(); + int clickx = dlg->expressionContextScope().variable( "click_x" ).toString().toInt( &ok, 10 ); + QCOMPARE( clickx, clickxOk ); + + int clicky = dlg->expressionContextScope().variable( "click_y" ).toString().toInt( &ok, 10 ); + QCOMPARE( clicky, clickyOk ); + + // close + mIdentifyAction->identifyMenu()->close(); +} + +void TestQgsMapToolIdentifyAction::clickxy() +{ + // create temp layer + std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:3111" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) ); + QVERIFY( tempLayer->isValid() ); + + // add feature + QgsFeature f1( tempLayer->dataProvider()->fields(), 1 ); + QgsPointXY wordPoint( 2484588, 2425722 ); + QgsGeometry geom = QgsGeometry::fromPointXY( wordPoint ) ; + f1.setGeometry( geom ); + tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 ); + + // prepare canvas + QList layers; + layers.append( tempLayer.get() ); + + QgsCoordinateReferenceSystem srs( 3111, QgsCoordinateReferenceSystem::EpsgCrsId ); + canvas->setDestinationCrs( srs ); + canvas->setLayers( layers ); + canvas->setCurrentLayer( tempLayer.get() ); + + // create/add action + QgsAction act( QgsAction::GenericPython, "MyAction", "", true ); + + QSet scopes; + scopes << "Feature"; + act.setActionScopes( scopes ); + tempLayer->actions()->addAction( act ); + + // init map tool identify action + mIdentifyAction = new QgsMapToolIdentifyAction( canvas ); + + // simulate a click on the canvas + QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( 2484588, 2425722 ); + QPoint point = QPoint( mapPoint.x(), mapPoint.y() ); + QMouseEvent releases( QEvent::MouseButtonRelease, point, + Qt::RightButton, Qt::LeftButton, Qt::NoModifier ); + QgsMapMouseEvent mapReleases( 0, &releases ); + + // simulate a click on the corresponding action + QTimer::singleShot( 2000, this, &TestQgsMapToolIdentifyAction::doAction ); + mIdentifyAction->canvasReleaseEvent( &mapReleases ); +} + void TestQgsMapToolIdentifyAction::lengthCalculation() { QgsSettings s;