QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsactionmanager.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsactionmanager.cpp
3
4 A class that stores and controls the management and execution of actions
5 associated. Actions are defined to be external programs that are run
6 with user-specified inputs that can depend on the value of layer
7 attributes.
8
9 -------------------
10 begin : Oct 24 2004
11 copyright : (C) 2004 by Gavin Macaulay
12 email : gavin at macaulay dot co dot nz
13
14 ***************************************************************************/
15
16/***************************************************************************
17 * *
18 * This program is free software; you can redistribute it and/or modify *
19 * it under the terms of the GNU General Public License as published by *
20 * the Free Software Foundation; either version 2 of the License, or *
21 * (at your option) any later version. *
22 * *
23 ***************************************************************************/
24
25#include "qgsactionmanager.h"
26#include "qgsrunprocess.h"
27#include "qgsvectorlayer.h"
28#include "qgsproject.h"
29#include "qgslogger.h"
30#include "qgsexpression.h"
31#include "qgsdataprovider.h"
33#include "qgsaction.h"
34
35#include <QList>
36#include <QStringList>
37#include <QDomElement>
38#include <QSettings>
39#include <QDesktopServices>
40#include <QUrl>
41#include <QFileInfo>
42#include <QRegularExpression>
43
45 : mLayer( layer )
46{}
47
48QUuid QgsActionManager::addAction( Qgis::AttributeActionType type, const QString &name, const QString &command, bool capture )
49{
50 QgsAction action( type, name, command, capture );
52 return action.id();
53}
54
55QUuid QgsActionManager::addAction( Qgis::AttributeActionType type, const QString &name, const QString &command, const QString &icon, bool capture )
56{
57 QgsAction action( type, name, command, icon, capture );
59 return action.id();
60}
61
63{
64 QgsDebugMsgLevel( "add action " + action.name(), 3 );
65 mActions.append( action );
66 if ( mLayer && mLayer->dataProvider() && !action.notificationMessage().isEmpty() )
67 {
68 mLayer->dataProvider()->setListening( true );
69 if ( !mOnNotifyConnected )
70 {
71 QgsDebugMsgLevel( QStringLiteral( "connecting to notify" ), 3 );
72 connect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
73 mOnNotifyConnected = true;
74 }
75 }
76}
77
78void QgsActionManager::onNotifyRunActions( const QString &message )
79{
80 for ( const QgsAction &act : std::as_const( mActions ) )
81 {
82 if ( !act.notificationMessage().isEmpty() && QRegularExpression( act.notificationMessage() ).match( message ).hasMatch() )
83 {
84 if ( !act.isValid() || !act.runable() )
85 continue;
86
87 QgsExpressionContext context = createExpressionContext();
88
89 Q_ASSERT( mLayer ); // if there is no layer, then where is the notification coming from ?
90 context << QgsExpressionContextUtils::layerScope( mLayer );
92
93 QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &context );
94 if ( expandedAction.isEmpty() )
95 continue;
96 runAction( QgsAction( act.type(), act.name(), expandedAction, act.capture() ) );
97 }
98 }
99}
100
101void QgsActionManager::removeAction( QUuid actionId )
102{
103 int i = 0;
104 for ( const QgsAction &action : std::as_const( mActions ) )
105 {
106 if ( action.id() == actionId )
107 {
108 mActions.removeAt( i );
109 break;
110 }
111 ++i;
112 }
113
114 if ( mOnNotifyConnected )
115 {
116 bool hasActionOnNotify = false;
117 for ( const QgsAction &action : std::as_const( mActions ) )
118 hasActionOnNotify |= !action.notificationMessage().isEmpty();
119 if ( !hasActionOnNotify && mLayer && mLayer->dataProvider() )
120 {
121 // note that there is no way of knowing if the provider is listening only because
122 // this class has hasked it to, so we do not reset the provider listening state here
123 disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
124 mOnNotifyConnected = false;
125 }
126 }
127}
128
129void QgsActionManager::doAction( QUuid actionId, const QgsFeature &feature, int defaultValueIndex, const QgsExpressionContextScope &scope )
130{
131 QgsExpressionContext context = createExpressionContext();
132 QgsExpressionContextScope *actionScope = new QgsExpressionContextScope( scope );
133 actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_index" ), defaultValueIndex, true ) );
134 if ( defaultValueIndex >= 0 && defaultValueIndex < feature.fields().size() )
135 actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_name" ), feature.fields().at( defaultValueIndex ).name(), true ) );
136 actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_value" ), feature.attribute( defaultValueIndex ), true ) );
137 context << actionScope;
138 doAction( actionId, feature, context );
139}
140
141void QgsActionManager::doAction( QUuid actionId, const QgsFeature &feat, const QgsExpressionContext &context )
142{
143 QgsAction act = action( actionId );
144
145 if ( !act.isValid() || !act.runable() )
146 return;
147
148 QgsExpressionContext actionContext( context );
149
150 if ( mLayer )
151 actionContext << QgsExpressionContextUtils::layerScope( mLayer );
152 actionContext.setFeature( feat );
153
154 QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &actionContext );
155 if ( expandedAction.isEmpty() )
156 return;
157
158 QgsAction newAction( act.type(), act.name(), expandedAction, act.capture() );
159 runAction( newAction );
160}
161
163{
164 mActions.clear();
165 if ( mOnNotifyConnected && mLayer && mLayer->dataProvider() )
166 {
167 // note that there is no way of knowing if the provider is listening only because
168 // this class has hasked it to, so we do not reset the provider listening state here
169 disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
170 mOnNotifyConnected = false;
171 }
172}
173
174QList<QgsAction> QgsActionManager::actions( const QString &actionScope ) const
175{
176 if ( actionScope.isNull() )
177 return mActions;
178 else
179 {
180 QList<QgsAction> actions;
181
182 for ( const QgsAction &action : std::as_const( mActions ) )
183 {
184 if ( action.actionScopes().contains( actionScope ) )
185 actions.append( action );
186 }
187
188 return actions;
189 }
190}
191
192void QgsActionManager::runAction( const QgsAction &action )
193{
194 switch ( action.type() )
195 {
197 {
198 QFileInfo finfo( action.command() );
199 if ( finfo.exists() && finfo.isFile() )
200 QDesktopServices::openUrl( QUrl::fromLocalFile( action.command() ) );
201 else
202 QDesktopServices::openUrl( QUrl( action.command(), QUrl::TolerantMode ) );
203 break;
204 }
208 {
210 break;
211 }
216 {
217 // The QgsRunProcess instance created by this static function
218 // deletes itself when no longer needed.
220 break;
221 }
222 }
223}
224
225QgsExpressionContext QgsActionManager::createExpressionContext() const
226{
227 QgsExpressionContext context;
230 if ( mLayer )
231 context << QgsExpressionContextUtils::layerScope( mLayer );
232
233 return context;
234}
235
236bool QgsActionManager::writeXml( QDomNode &layer_node ) const
237{
238 QDomElement aActions = layer_node.ownerDocument().createElement( QStringLiteral( "attributeactions" ) );
239 for ( QMap<QString, QUuid>::const_iterator defaultAction = mDefaultActions.constBegin(); defaultAction != mDefaultActions.constEnd(); ++ defaultAction )
240 {
241 QDomElement defaultActionElement = layer_node.ownerDocument().createElement( QStringLiteral( "defaultAction" ) );
242 defaultActionElement.setAttribute( QStringLiteral( "key" ), defaultAction.key() );
243 defaultActionElement.setAttribute( QStringLiteral( "value" ), defaultAction.value().toString() );
244 aActions.appendChild( defaultActionElement );
245 }
246
247 for ( const QgsAction &action : std::as_const( mActions ) )
248 {
249 action.writeXml( aActions );
250 }
251 layer_node.appendChild( aActions );
252
253 return true;
254}
255
256bool QgsActionManager::readXml( const QDomNode &layer_node )
257{
258 clearActions();
259
260 QDomNode aaNode = layer_node.namedItem( QStringLiteral( "attributeactions" ) );
261
262 if ( !aaNode.isNull() )
263 {
264 QDomNodeList actionsettings = aaNode.toElement().elementsByTagName( QStringLiteral( "actionsetting" ) );
265 for ( int i = 0; i < actionsettings.size(); ++i )
266 {
268 action.readXml( actionsettings.item( i ) );
269 addAction( action );
270 }
271
272 QDomNodeList defaultActionNodes = aaNode.toElement().elementsByTagName( QStringLiteral( "defaultAction" ) );
273
274 for ( int i = 0; i < defaultActionNodes.size(); ++i )
275 {
276 QDomElement defaultValueElem = defaultActionNodes.at( i ).toElement();
277 mDefaultActions.insert( defaultValueElem.attribute( QStringLiteral( "key" ) ), QUuid( defaultValueElem.attribute( QStringLiteral( "value" ) ) ) );
278 }
279 }
280 return true;
281}
282
284{
285 for ( const QgsAction &action : std::as_const( mActions ) )
286 {
287 if ( action.id() == id )
288 return action;
289 }
290
291 return QgsAction();
292}
293
294QgsAction QgsActionManager::action( const QString &id ) const
295{
296 for ( const QgsAction &action : std::as_const( mActions ) )
297 {
298 if ( action.id().toString() == id )
299 return action;
300 }
301
302 return QgsAction();
303}
304
305void QgsActionManager::setDefaultAction( const QString &actionScope, QUuid actionId )
306{
307 mDefaultActions[ actionScope ] = actionId;
308}
309
310QgsAction QgsActionManager::defaultAction( const QString &actionScope )
311{
312 return action( mDefaultActions.value( actionScope ) );
313}
AttributeActionType
Attribute action types.
Definition: qgis.h:3876
@ Mac
MacOS specific.
@ OpenUrl
Open URL action.
@ SubmitUrlMultipart
POST data to an URL using "multipart/form-data".
@ Windows
Windows specific.
@ SubmitUrlEncoded
POST data to an URL, using "application/x-www-form-urlencoded" or "application/json" if the body is v...
void removeAction(QUuid actionId)
Remove an action by its id.
bool writeXml(QDomNode &layer_node) const
Writes the actions out in XML format.
QList< QgsAction > actions(const QString &actionScope=QString()) const
Returns a list of actions that are available in the given action scope.
void doAction(QUuid actionId, const QgsFeature &feature, int defaultValueIndex=0, const QgsExpressionContextScope &scope=QgsExpressionContextScope())
Does the given action.
void clearActions()
Removes all actions.
QUuid addAction(Qgis::AttributeActionType type, const QString &name, const QString &command, bool capture=false)
Add an action with the given name and action details.
void setDefaultAction(const QString &actionScope, QUuid actionId)
Each scope can have a default action.
QgsActionManager(QgsVectorLayer *layer)
Constructor.
QgsAction defaultAction(const QString &actionScope)
Each scope can have a default action.
bool readXml(const QDomNode &layer_node)
Reads the actions in in XML format.
QgsAction action(QUuid id) const
Gets an action by its id.
Utility class that encapsulates an action based on vector attributes.
Definition: qgsaction.h:37
QString notificationMessage() const
Returns the notification message that triggers the action.
Definition: qgsaction.h:153
QString name() const
The name of the action. This may be a longer description.
Definition: qgsaction.h:118
QSet< QString > actionScopes() const
The action scopes define where an action will be available.
Definition: qgsaction.cpp:319
Qgis::AttributeActionType type() const
The action type.
Definition: qgsaction.h:156
void readXml(const QDomNode &actionNode)
Reads an XML definition from actionNode into this object.
Definition: qgsaction.cpp:329
void run(QgsVectorLayer *layer, const QgsFeature &feature, const QgsExpressionContext &expressionContext) const
Run this action.
Definition: qgsaction.cpp:78
bool runable() const
Checks if the action is runable on the current platform.
Definition: qgsaction.cpp:41
bool isValid() const
Returns true if this action was a default constructed one.
Definition: qgsaction.h:133
QString command() const
Returns the command that is executed by this action.
Definition: qgsaction.h:147
bool capture() const
Whether to capture output for display when this action is run.
Definition: qgsaction.h:159
void writeXml(QDomNode &actionsNode) const
Appends an XML definition for this action as a new child node to actionsNode.
Definition: qgsaction.cpp:363
QUuid id() const
Returns a unique id for this action.
Definition: qgsaction.h:127
virtual void setListening(bool isListening)
Set whether the provider will listen to datasource notifications If set, the provider will issue noti...
void notify(const QString &msg)
Emitted when the datasource issues a notification.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
static QgsExpressionContextScope * notificationScope(const QString &message=QString())
Creates a new scope which contains variables and functions relating to provider notifications.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
static QString replaceExpressionText(const QString &action, const QgsExpressionContext *context, const QgsDistanceArea *distanceArea=nullptr)
This function replaces each expression between [% and %] in the string with the result of its evaluat...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsFields fields
Definition: qgsfeature.h:66
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:335
QString name
Definition: qgsfield.h:62
int size() const
Returns number of items.
Definition: qgsfields.cpp:138
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:481
static QgsRunProcess * create(const QString &action, bool capture)
Definition: qgsrunprocess.h:59
Represents a vector layer which manages a vector based data sets.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
Single variable definition for use within a QgsExpressionContextScope.