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