QGIS API Documentation  3.25.0-Master (dec16ba68b)
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 "qgspythonrunner.h"
27 #include "qgsrunprocess.h"
28 #include "qgsvectorlayer.h"
29 #include "qgsproject.h"
30 #include "qgslogger.h"
31 #include "qgsexpression.h"
32 #include "qgsdataprovider.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 
44 QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, bool capture )
45 {
46  QgsAction action( type, name, command, capture );
47  addAction( action );
48  return action.id();
49 }
50 
51 QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, const QString &icon, bool capture )
52 {
53  QgsAction action( type, name, command, icon, capture );
54  addAction( action );
55  return action.id();
56 }
57 
59 {
60  QgsDebugMsg( "add action " + action.name() );
61  mActions.append( action );
62  if ( mLayer && mLayer->dataProvider() && !action.notificationMessage().isEmpty() )
63  {
64  mLayer->dataProvider()->setListening( true );
65  if ( !mOnNotifyConnected )
66  {
67  QgsDebugMsg( QStringLiteral( "connecting to notify" ) );
68  connect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
69  mOnNotifyConnected = true;
70  }
71  }
72 }
73 
74 void QgsActionManager::onNotifyRunActions( const QString &message )
75 {
76  for ( const QgsAction &act : std::as_const( mActions ) )
77  {
78  if ( !act.notificationMessage().isEmpty() && QRegularExpression( act.notificationMessage() ).match( message ).hasMatch() )
79  {
80  if ( !act.isValid() || !act.runable() )
81  continue;
82 
83  QgsExpressionContext context = createExpressionContext();
84 
85  Q_ASSERT( mLayer ); // if there is no layer, then where is the notification coming from ?
86  context << QgsExpressionContextUtils::layerScope( mLayer );
88 
89  QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &context );
90  if ( expandedAction.isEmpty() )
91  continue;
92  runAction( QgsAction( act.type(), act.name(), expandedAction, act.capture() ) );
93  }
94  }
95 }
96 
97 void QgsActionManager::removeAction( QUuid actionId )
98 {
99  int i = 0;
100  for ( const QgsAction &action : std::as_const( mActions ) )
101  {
102  if ( action.id() == actionId )
103  {
104  mActions.removeAt( i );
105  break;
106  }
107  ++i;
108  }
109 
110  if ( mOnNotifyConnected )
111  {
112  bool hasActionOnNotify = false;
113  for ( const QgsAction &action : std::as_const( mActions ) )
114  hasActionOnNotify |= !action.notificationMessage().isEmpty();
115  if ( !hasActionOnNotify && mLayer && mLayer->dataProvider() )
116  {
117  // note that there is no way of knowing if the provider is listening only because
118  // this class has hasked it to, so we do not reset the provider listening state here
119  disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
120  mOnNotifyConnected = false;
121  }
122  }
123 }
124 
125 void QgsActionManager::doAction( QUuid actionId, const QgsFeature &feature, int defaultValueIndex, const QgsExpressionContextScope &scope )
126 {
127  QgsExpressionContext context = createExpressionContext();
128  QgsExpressionContextScope *actionScope = new QgsExpressionContextScope( scope );
129  actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_index" ), defaultValueIndex, true ) );
130  if ( defaultValueIndex >= 0 && defaultValueIndex < feature.fields().size() )
131  actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_name" ), feature.fields().at( defaultValueIndex ).name(), true ) );
132  actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_value" ), feature.attribute( defaultValueIndex ), true ) );
133  context << actionScope;
134  doAction( actionId, feature, context );
135 }
136 
137 void QgsActionManager::doAction( QUuid actionId, const QgsFeature &feat, const QgsExpressionContext &context )
138 {
139  QgsAction act = action( actionId );
140 
141  if ( !act.isValid() || !act.runable() )
142  return;
143 
144  QgsExpressionContext actionContext( context );
145 
146  if ( mLayer )
147  actionContext << QgsExpressionContextUtils::layerScope( mLayer );
148  actionContext.setFeature( feat );
149 
150  QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &actionContext );
151  if ( expandedAction.isEmpty() )
152  return;
153 
154  QgsAction newAction( act.type(), act.name(), expandedAction, act.capture() );
155  runAction( newAction );
156 }
157 
159 {
160  mActions.clear();
161  if ( mOnNotifyConnected && mLayer && mLayer->dataProvider() )
162  {
163  // note that there is no way of knowing if the provider is listening only because
164  // this class has hasked it to, so we do not reset the provider listening state here
165  disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
166  mOnNotifyConnected = false;
167  }
168 }
169 
170 QList<QgsAction> QgsActionManager::actions( const QString &actionScope ) const
171 {
172  if ( actionScope.isNull() )
173  return mActions;
174  else
175  {
176  QList<QgsAction> actions;
177 
178  for ( const QgsAction &action : std::as_const( mActions ) )
179  {
180  if ( action.actionScopes().contains( actionScope ) )
181  actions.append( action );
182  }
183 
184  return actions;
185  }
186 }
187 
188 void QgsActionManager::runAction( const QgsAction &action )
189 {
190  switch ( action.type() )
191  {
192  case QgsAction::OpenUrl:
193  {
194  QFileInfo finfo( action.command() );
195  if ( finfo.exists() && finfo.isFile() )
196  QDesktopServices::openUrl( QUrl::fromLocalFile( action.command() ) );
197  else
198  QDesktopServices::openUrl( QUrl( action.command(), QUrl::TolerantMode ) );
199  break;
200  }
204  {
206  break;
207  }
208  case QgsAction::Generic:
209  case QgsAction::Mac:
210  case QgsAction::Unix:
211  case QgsAction::Windows:
212  {
213  // The QgsRunProcess instance created by this static function
214  // deletes itself when no longer needed.
216  break;
217  }
218  }
219 }
220 
221 QgsExpressionContext QgsActionManager::createExpressionContext() const
222 {
223  QgsExpressionContext context;
226  if ( mLayer )
227  context << QgsExpressionContextUtils::layerScope( mLayer );
228 
229  return context;
230 }
231 
232 bool QgsActionManager::writeXml( QDomNode &layer_node ) const
233 {
234  QDomElement aActions = layer_node.ownerDocument().createElement( QStringLiteral( "attributeactions" ) );
235  for ( QMap<QString, QUuid>::const_iterator defaultAction = mDefaultActions.constBegin(); defaultAction != mDefaultActions.constEnd(); ++ defaultAction )
236  {
237  QDomElement defaultActionElement = layer_node.ownerDocument().createElement( QStringLiteral( "defaultAction" ) );
238  defaultActionElement.setAttribute( QStringLiteral( "key" ), defaultAction.key() );
239  defaultActionElement.setAttribute( QStringLiteral( "value" ), defaultAction.value().toString() );
240  aActions.appendChild( defaultActionElement );
241  }
242 
243  for ( const QgsAction &action : std::as_const( mActions ) )
244  {
245  action.writeXml( aActions );
246  }
247  layer_node.appendChild( aActions );
248 
249  return true;
250 }
251 
252 bool QgsActionManager::readXml( const QDomNode &layer_node )
253 {
254  clearActions();
255 
256  QDomNode aaNode = layer_node.namedItem( QStringLiteral( "attributeactions" ) );
257 
258  if ( !aaNode.isNull() )
259  {
260  QDomNodeList actionsettings = aaNode.toElement().elementsByTagName( QStringLiteral( "actionsetting" ) );
261  for ( int i = 0; i < actionsettings.size(); ++i )
262  {
264  action.readXml( actionsettings.item( i ) );
265  addAction( action );
266  }
267 
268  QDomNodeList defaultActionNodes = aaNode.toElement().elementsByTagName( QStringLiteral( "defaultAction" ) );
269 
270  for ( int i = 0; i < defaultActionNodes.size(); ++i )
271  {
272  QDomElement defaultValueElem = defaultActionNodes.at( i ).toElement();
273  mDefaultActions.insert( defaultValueElem.attribute( QStringLiteral( "key" ) ), QUuid( defaultValueElem.attribute( QStringLiteral( "value" ) ) ) );
274  }
275  }
276  return true;
277 }
278 
280 {
281  for ( const QgsAction &action : std::as_const( mActions ) )
282  {
283  if ( action.id() == id )
284  return action;
285  }
286 
287  return QgsAction();
288 }
289 
290 void QgsActionManager::setDefaultAction( const QString &actionScope, QUuid actionId )
291 {
292  mDefaultActions[ actionScope ] = actionId;
293 }
294 
295 QgsAction QgsActionManager::defaultAction( const QString &actionScope )
296 {
297  return action( mDefaultActions.value( actionScope ) );
298 }
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(QgsAction::ActionType 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.
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:35
QString notificationMessage() const
Returns the notification message that triggers the action.
Definition: qgsaction.h:166
QString name() const
The name of the action. This may be a longer description.
Definition: qgsaction.h:127
QSet< QString > actionScopes() const
The action scopes define where an action will be available.
Definition: qgsaction.cpp:282
void readXml(const QDomNode &actionNode)
Reads an XML definition from actionNode into this object.
Definition: qgsaction.cpp:292
void run(QgsVectorLayer *layer, const QgsFeature &feature, const QgsExpressionContext &expressionContext) const
Run this action.
Definition: qgsaction.cpp:59
bool runable() const
Checks if the action is runable on the current platform.
Definition: qgsaction.cpp:42
bool isValid() const
Returns true if this action was a default constructed one.
Definition: qgsaction.h:144
ActionType type() const
The action type.
Definition: qgsaction.h:169
QString command() const
Returns the command that is executed by this action.
Definition: qgsaction.h:159
bool capture() const
Whether to capture output for display when this action is run.
Definition: qgsaction.h:172
void writeXml(QDomNode &actionsNode) const
Appends an XML definition for this action as a new child node to actionsNode.
Definition: qgsaction.cpp:326
QUuid id() const
Returns a unique id for this action.
Definition: qgsaction.h:137
@ GenericPython
Definition: qgsaction.h:40
@ SubmitUrlEncoded
POST data to an URL, using "application/x-www-form-urlencoded" or "application/json" if the body is v...
Definition: qgsaction.h:45
@ SubmitUrlMultipart
POST data to an URL using "multipart/form-data".
Definition: qgsaction.h:46
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:320
QString name
Definition: qgsfield.h:60
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:474
static QgsRunProcess * create(const QString &action, bool capture)
Definition: qgsrunprocess.h:59
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Single variable definition for use within a QgsExpressionContextScope.