QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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)
203  QgsPythonRunner::run( action.command() );
204  }
205  else
206  {
207  // The QgsRunProcess instance created by this static function
208  // deletes itself when no longer needed.
209  QgsRunProcess::create( action.command(), action.capture() );
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 }
Single variable definition for use within a QgsExpressionContextScope.
void doAction(QUuid actionId, const QgsFeature &feature, int defaultValueIndex=0, const QgsExpressionContextScope &scope=QgsExpressionContextScope())
Does the given action.
int size() const
Returns number of items.
Definition: qgsfields.cpp:138
QgsAction action(QUuid id)
Gets an action by its id.
bool writeXml(QDomNode &layer_node) const
Writes the actions out in XML format.
QString name
Definition: qgsfield.h:58
bool capture() const
Whether to capture output for display when this action is run.
Definition: qgsaction.h:169
void setDefaultAction(const QString &actionScope, QUuid actionId)
Each scope can have a default action.
ActionType type() const
The action type.
Definition: qgsaction.h:166
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QString notificationMessage() const
Returns the notification message that triggers the action.
Definition: qgsaction.h:163
QSet< QString > actionScopes() const
The action scopes define where an action will be available.
Definition: qgsaction.cpp:90
QList< QgsAction > actions(const QString &actionScope=QString()) const
Returns a list of actions that are available in the given action scope.
void readXml(const QDomNode &actionNode)
Reads an XML definition from actionNode into this object.
Definition: qgsaction.cpp:100
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
QgsFields fields
Definition: qgsfeature.h:66
bool isValid() const
Returns true if this action was a default constructed one.
Definition: qgsaction.h:141
void notify(const QString &msg)
Emitted when the datasource issues a notification.
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 addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
bool readXml(const QDomNode &layer_node)
Reads the actions in in XML format.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
virtual void setListening(bool isListening)
Set whether the provider will listen to datasource notifications If set, the provider will issue noti...
void writeXml(QDomNode &actionsNode) const
Appends an XML definition for this action as a new child node to actionsNode.
Definition: qgsaction.cpp:134
Utility class that encapsulates an action based on vector attributes.
Definition: qgsaction.h:35
static QgsExpressionContextScope * notificationScope(const QString &message=QString())
Creates a new scope which contains variables and functions relating to provider notifications.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void clearActions()
Removes all actions.
bool runable() const
Checks if the action is runable on the current platform.
Definition: qgsaction.cpp:30
QString name() const
The name of the action. This may be a longer description.
Definition: qgsaction.h:124
Single scope for storing variables and functions for use within a QgsExpressionContext.
QgsAction defaultAction(const QString &actionScope)
Each scope can have a default action.
void removeAction(QUuid actionId)
Remove an action by its id.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:438
QUuid id() const
Returns a unique id for this action.
Definition: qgsaction.h:134
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer&#39;s data provider, it may be nullptr.
static QgsRunProcess * create(const QString &action, bool capture)
Definition: qgsrunprocess.h:53
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
QString command() const
Returns the command that is executed by this action.
Definition: qgsaction.h:156
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...