QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 <QDir>
42 #include <QFileInfo>
43 #include <QRegularExpression>
44 
45 
46 QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, bool capture )
47 {
48  QgsAction action( type, name, command, capture );
49  addAction( action );
50  return action.id();
51 }
52 
53 QUuid QgsActionManager::addAction( QgsAction::ActionType type, const QString &name, const QString &command, const QString &icon, bool capture )
54 {
55  QgsAction action( type, name, command, icon, capture );
56  addAction( action );
57  return action.id();
58 }
59 
61 {
62  QgsDebugMsg( "add action " + action.name() );
63  mActions.append( action );
64  if ( mLayer && mLayer->dataProvider() && !action.notificationMessage().isEmpty() )
65  {
66  mLayer->dataProvider()->setListening( true );
67  if ( !mOnNotifyConnected )
68  {
69  QgsDebugMsg( QStringLiteral( "connecting to notify" ) );
70  connect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
71  mOnNotifyConnected = true;
72  }
73  }
74 }
75 
76 void QgsActionManager::onNotifyRunActions( const QString &message )
77 {
78  for ( const QgsAction &act : qgis::as_const( mActions ) )
79  {
80  if ( !act.notificationMessage().isEmpty() && QRegularExpression( act.notificationMessage() ).match( message ).hasMatch() )
81  {
82  if ( !act.isValid() || !act.runable() )
83  continue;
84 
85  QgsExpressionContext context = createExpressionContext();
86 
87  Q_ASSERT( mLayer ); // if there is no layer, then where is the notification coming from ?
88  context << QgsExpressionContextUtils::layerScope( mLayer );
90 
91  QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &context );
92  if ( expandedAction.isEmpty() )
93  continue;
94  runAction( QgsAction( act.type(), act.name(), expandedAction, act.capture() ) );
95  }
96  }
97 }
98 
99 void QgsActionManager::removeAction( QUuid actionId )
100 {
101  int i = 0;
102  for ( const QgsAction &action : qgis::as_const( mActions ) )
103  {
104  if ( action.id() == actionId )
105  {
106  mActions.removeAt( i );
107  break;
108  }
109  ++i;
110  }
111 
112  if ( mOnNotifyConnected )
113  {
114  bool hasActionOnNotify = false;
115  for ( const QgsAction &action : qgis::as_const( mActions ) )
116  hasActionOnNotify |= !action.notificationMessage().isEmpty();
117  if ( !hasActionOnNotify && mLayer && mLayer->dataProvider() )
118  {
119  // note that there is no way of knowing if the provider is listening only because
120  // this class has hasked it to, so we do not reset the provider listening state here
121  disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
122  mOnNotifyConnected = false;
123  }
124  }
125 }
126 
127 void QgsActionManager::doAction( QUuid actionId, const QgsFeature &feature, int defaultValueIndex, const QgsExpressionContextScope &scope )
128 {
129  QgsExpressionContext context = createExpressionContext();
130  QgsExpressionContextScope *actionScope = new QgsExpressionContextScope( scope );
131  actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_index" ), defaultValueIndex, true ) );
132  if ( defaultValueIndex >= 0 && defaultValueIndex < feature.fields().size() )
133  actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_name" ), feature.fields().at( defaultValueIndex ).name(), true ) );
134  actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "field_value" ), feature.attribute( defaultValueIndex ), true ) );
135  context << actionScope;
136  doAction( actionId, feature, context );
137 }
138 
139 void QgsActionManager::doAction( QUuid actionId, const QgsFeature &feat, const QgsExpressionContext &context )
140 {
141  QgsAction act = action( actionId );
142 
143  if ( !act.isValid() || !act.runable() )
144  return;
145 
146  QgsExpressionContext actionContext( context );
147 
148  if ( mLayer )
149  actionContext << QgsExpressionContextUtils::layerScope( mLayer );
150  actionContext.setFeature( feat );
151 
152  QString expandedAction = QgsExpression::replaceExpressionText( act.command(), &actionContext );
153  if ( expandedAction.isEmpty() )
154  return;
155 
156  QgsAction newAction( act.type(), act.name(), expandedAction, act.capture() );
157  runAction( newAction );
158 }
159 
161 {
162  mActions.clear();
163  if ( mOnNotifyConnected && mLayer && mLayer->dataProvider() )
164  {
165  // note that there is no way of knowing if the provider is listening only because
166  // this class has hasked it to, so we do not reset the provider listening state here
167  disconnect( mLayer->dataProvider(), &QgsDataProvider::notify, this, &QgsActionManager::onNotifyRunActions );
168  mOnNotifyConnected = false;
169  }
170 }
171 
172 QList<QgsAction> QgsActionManager::actions( const QString &actionScope ) const
173 {
174  if ( actionScope.isNull() )
175  return mActions;
176  else
177  {
178  QList<QgsAction> actions;
179 
180  for ( const QgsAction &action : qgis::as_const( mActions ) )
181  {
182  if ( action.actionScopes().contains( actionScope ) )
183  actions.append( action );
184  }
185 
186  return actions;
187  }
188 }
189 
190 void QgsActionManager::runAction( const QgsAction &action )
191 {
192  if ( action.type() == 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  }
200  else if ( action.type() == QgsAction::GenericPython )
201  {
202  // TODO: capture output from QgsPythonRunner (like QgsRunProcess does)
204  }
205  else
206  {
207  // The QgsRunProcess instance created by this static function
208  // deletes itself when no longer needed.
210  }
211 }
212 
213 QgsExpressionContext QgsActionManager::createExpressionContext() const
214 {
215  QgsExpressionContext context;
218  if ( mLayer )
219  context << QgsExpressionContextUtils::layerScope( mLayer );
220 
221  return context;
222 }
223 
224 bool QgsActionManager::writeXml( QDomNode &layer_node ) const
225 {
226  QDomElement aActions = layer_node.ownerDocument().createElement( QStringLiteral( "attributeactions" ) );
227  for ( QMap<QString, QUuid>::const_iterator defaultAction = mDefaultActions.constBegin(); defaultAction != mDefaultActions.constEnd(); ++ defaultAction )
228  {
229  QDomElement defaultActionElement = layer_node.ownerDocument().createElement( QStringLiteral( "defaultAction" ) );
230  defaultActionElement.setAttribute( QStringLiteral( "key" ), defaultAction.key() );
231  defaultActionElement.setAttribute( QStringLiteral( "value" ), defaultAction.value().toString() );
232  aActions.appendChild( defaultActionElement );
233  }
234 
235  for ( const QgsAction &action : qgis::as_const( mActions ) )
236  {
237  action.writeXml( aActions );
238  }
239  layer_node.appendChild( aActions );
240 
241  return true;
242 }
243 
244 bool QgsActionManager::readXml( const QDomNode &layer_node )
245 {
246  clearActions();
247 
248  QDomNode aaNode = layer_node.namedItem( QStringLiteral( "attributeactions" ) );
249 
250  if ( !aaNode.isNull() )
251  {
252  QDomNodeList actionsettings = aaNode.toElement().elementsByTagName( QStringLiteral( "actionsetting" ) );
253  for ( int i = 0; i < actionsettings.size(); ++i )
254  {
256  action.readXml( actionsettings.item( i ) );
257  addAction( action );
258  }
259 
260  QDomNodeList defaultActionNodes = aaNode.toElement().elementsByTagName( QStringLiteral( "defaultAction" ) );
261 
262  for ( int i = 0; i < defaultActionNodes.size(); ++i )
263  {
264  QDomElement defaultValueElem = defaultActionNodes.at( i ).toElement();
265  mDefaultActions.insert( defaultValueElem.attribute( QStringLiteral( "key" ) ), defaultValueElem.attribute( QStringLiteral( "value" ) ) );
266  }
267  }
268  return true;
269 }
270 
272 {
273  for ( const QgsAction &action : qgis::as_const( mActions ) )
274  {
275  if ( action.id() == id )
276  return action;
277  }
278 
279  return QgsAction();
280 }
281 
282 void QgsActionManager::setDefaultAction( const QString &actionScope, QUuid actionId )
283 {
284  mDefaultActions[ actionScope ] = actionId;
285 }
286 
287 QgsAction QgsActionManager::defaultAction( const QString &actionScope )
288 {
289  return action( mDefaultActions.value( actionScope ) );
290 }
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)
Gets an action by its id.
Utility class that encapsulates an action based on vector attributes.
Definition: qgsaction.h:36
QString notificationMessage() const
Returns the notification message that triggers the action.
Definition: qgsaction.h:163
QString name() const
The name of the action. This may be a longer description.
Definition: qgsaction.h:124
QSet< QString > actionScopes() const
The action scopes define where an action will be available.
Definition: qgsaction.cpp:90
void readXml(const QDomNode &actionNode)
Reads an XML definition from actionNode into this object.
Definition: qgsaction.cpp:100
bool runable() const
Checks if the action is runable on the current platform.
Definition: qgsaction.cpp:30
bool isValid() const
Returns true if this action was a default constructed one.
Definition: qgsaction.h:141
ActionType type() const
The action type.
Definition: qgsaction.h:166
QString command() const
Returns the command that is executed by this action.
Definition: qgsaction.h:156
bool capture() const
Whether to capture output for display when this action is run.
Definition: qgsaction.h:169
void writeXml(QDomNode &actionsNode) const
Appends an XML definition for this action as a new child node to actionsNode.
Definition: qgsaction.cpp:134
QUuid id() const
Returns a unique id for this action.
Definition: qgsaction.h:134
@ GenericPython
Definition: qgsaction.h:41
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 id, geometry and a list of field/values...
Definition: qgsfeature.h:56
QgsFields fields
Definition: qgsfeature.h:66
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:287
QString name
Definition: qgsfield.h:60
int size() const
Returns number of items.
Definition: qgsfields.cpp:138
QgsField at(int i) const
Gets 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:501
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
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.