QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
qgsactionmanager.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsactionmanager.cpp
3 
4  A class that stores and controls the managment 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 
33 #include <QList>
34 #include <QStringList>
35 #include <QDomElement>
36 #include <QSettings>
37 #include <QDesktopServices>
38 #include <QUrl>
39 #include <QDir>
40 #include <QFileInfo>
41 
42 
43 void QgsActionManager::addAction( QgsAction::ActionType type, const QString& name, const QString& action, bool capture )
44 {
45  mActions << QgsAction( type, name, action, capture );
46 }
47 
48 void QgsActionManager::addAction( QgsAction::ActionType type, const QString& name, const QString& action, const QString& icon, bool capture )
49 {
50  mActions << QgsAction( type, name, action, icon, capture );
51 }
52 
54 {
55  mActions << action;
56 }
57 
59 {
60  if ( index >= 0 && index < mActions.size() )
61  {
62  mActions.removeAt( index );
63  }
64 }
65 
66 void QgsActionManager::doAction( int index, const QgsFeature& feat, int defaultValueIndex, const QgsExpressionContextScope &scope )
67 {
68  QgsExpressionContext context = createExpressionContext();
69  QgsExpressionContextScope* actionScope = new QgsExpressionContextScope( scope );
70  actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QString( "current_field" ), feat.attribute( defaultValueIndex ), true ) );
71  context << actionScope;
72  doAction( index, feat, context );
73 }
74 
75 void QgsActionManager::doAction( int index, const QgsFeature& feat, const QgsExpressionContext& context, const QMap<QString, QVariant> *substitutionMap )
76 {
77  if ( index < 0 || index >= size() )
78  return;
79 
80  const QgsAction &action = at( index );
81  if ( !action.runable() )
82  return;
83 
84  QgsExpressionContext actionContext( context );
85 
86  if ( mLayer )
87  actionContext << QgsExpressionContextUtils::layerScope( mLayer );
88  actionContext.setFeature( feat );
89 
90  QString expandedAction = QgsExpression::replaceExpressionText( action.action(), &actionContext, substitutionMap );
91  if ( expandedAction.isEmpty() )
92  return;
93 
94  QgsAction newAction( action.type(), action.name(), expandedAction, action.capture() );
95  runAction( newAction );
96 }
97 
98 void QgsActionManager::doAction( int index, const QgsFeature &feat, const QMap<QString, QVariant> *substitutionMap )
99 {
100  if ( index < 0 || index >= size() )
101  return;
102 
103  const QgsAction &action = at( index );
104  if ( !action.runable() )
105  return;
106 
107  // search for expressions while expanding actions
108  QgsExpressionContext context = createExpressionContext();
109  context.setFeature( feat );
110  QString expandedAction = QgsExpression::replaceExpressionText( action.action(), &context, substitutionMap );
111  if ( expandedAction.isEmpty() )
112  return;
113 
114  QgsAction newAction( action.type(), action.name(), expandedAction, action.capture() );
115  runAction( newAction );
116 }
117 
119 {
120  mActions.clear();
121 }
122 
124 {
125  return mActions;
126 }
127 
128 void QgsActionManager::runAction( const QgsAction &action, void ( *executePython )( const QString & ) )
129 {
130  if ( action.type() == QgsAction::OpenUrl )
131  {
132  QFileInfo finfo( action.action() );
133  if ( finfo.exists() && finfo.isFile() )
135  else
136  QDesktopServices::openUrl( QUrl( action.action(), QUrl::TolerantMode ) );
137  }
138  else if ( action.type() == QgsAction::GenericPython )
139  {
140  if ( executePython )
141  {
142  // deprecated
143  executePython( action.action() );
144  }
145  else if ( smPythonExecute )
146  {
147  // deprecated
148  smPythonExecute( action.action() );
149  }
150  else
151  {
152  // TODO: capture output from QgsPythonRunner (like QgsRunProcess does)
153  QgsPythonRunner::run( action.action() );
154  }
155  }
156  else
157  {
158  // The QgsRunProcess instance created by this static function
159  // deletes itself when no longer needed.
160  QgsRunProcess::create( action.action(), action.capture() );
161  }
162 }
163 
164 QgsExpressionContext QgsActionManager::createExpressionContext() const
165 {
166  QgsExpressionContext context;
169  if ( mLayer )
170  context << QgsExpressionContextUtils::layerScope( mLayer );
171 
172  return context;
173 }
174 
176  uint clickedOnValue )
177 {
178  // This function currently replaces all %% characters in the action
179  // with the value from values[clickedOnValue].second, and then
180  // searches for all strings that go %attribute_name, where
181  // attribute_name is found in values[x].first, and replaces any that
182  // it finds by values[s].second.
183 
184  // Additional substitutions could include symbols for $CWD, $HOME,
185  // etc (and their OSX and Windows equivalents)
186 
187  // This function will potentially fall apart if any of the
188  // substitutions produce text that could match another
189  // substitution. May be better to adopt a two pass approach - identify
190  // all matches and their substitutions and then do a second pass
191  // for the actual substitutions.
192 
193  QString expanded_action;
194  if ( attributes.contains( clickedOnValue ) )
195  expanded_action = action.replace( "%%", attributes[clickedOnValue].toString() );
196  else
197  expanded_action = action;
198 
199  const QgsFields &fields = mLayer->fields();
200 
201  for ( int i = 0; i < 4; i++ )
202  {
203  for ( QgsAttributeMap::const_iterator it = attributes.begin(); it != attributes.end(); ++it )
204  {
205  int attrIdx = it.key();
206  if ( attrIdx < 0 || attrIdx >= fields.count() )
207  continue;
208 
209  QString to_replace;
210  switch ( i )
211  {
212  case 0:
213  to_replace = "[%" + fields[attrIdx].name() + ']';
214  break;
215  case 1:
216  to_replace = "[%" + mLayer->attributeDisplayName( attrIdx ) + ']';
217  break;
218  case 2:
219  to_replace = '%' + fields[attrIdx].name();
220  break;
221  case 3:
222  to_replace = '%' + mLayer->attributeDisplayName( attrIdx );
223  break;
224  }
225 
226  expanded_action = expanded_action.replace( to_replace, it.value().toString() );
227  }
228  }
229 
230  return expanded_action;
231 }
232 
233 QString QgsActionManager::expandAction( const QString& action, QgsFeature &feat, const QMap<QString, QVariant> *substitutionMap )
234 {
235  // This function currently replaces each expression between [% and %]
236  // in the action with the result of its evaluation on the feature
237  // passed as argument.
238 
239  // Additional substitutions can be passed through the substitutionMap
240  // parameter
241 
242  QString expr_action;
243 
244  int index = 0;
245  while ( index < action.size() )
246  {
247  QRegExp rx = QRegExp( "\\[%([^\\]]+)%\\]" );
248 
249  int pos = rx.indexIn( action, index );
250  if ( pos < 0 )
251  break;
252 
253  int start = index;
254  index = pos + rx.matchedLength();
255 
256  QString to_replace = rx.cap( 1 ).trimmed();
257  QgsDebugMsg( "Found expression: " + to_replace );
258 
259  if ( substitutionMap && substitutionMap->contains( to_replace ) )
260  {
261  expr_action += action.mid( start, pos - start ) + substitutionMap->value( to_replace ).toString();
262  continue;
263  }
264 
265  QgsExpression exp( to_replace );
266  if ( exp.hasParserError() )
267  {
268  QgsDebugMsg( "Expression parser error: " + exp.parserErrorString() );
269  expr_action += action.midRef( start, index - start );
270  continue;
271  }
272 
273  QgsExpressionContext context = createExpressionContext();
274  context.setFeature( feat );
275 
276  QVariant result = exp.evaluate( &context );
277  if ( exp.hasEvalError() )
278  {
279  QgsDebugMsg( "Expression parser eval error: " + exp.evalErrorString() );
280  expr_action += action.midRef( start, index - start );
281  continue;
282  }
283 
284  QgsDebugMsg( "Expression result is: " + result.toString() );
285  expr_action += action.mid( start, pos - start ) + result.toString();
286  }
287 
288  expr_action += action.midRef( index );
289  return expr_action;
290 }
291 
292 
293 bool QgsActionManager::writeXML( QDomNode& layer_node, QDomDocument& doc ) const
294 {
295  QDomElement aActions = doc.createElement( "attributeactions" );
296  aActions.setAttribute( "default", mDefaultAction );
297 
298  Q_FOREACH ( const QgsAction& action, mActions )
299  {
300  QDomElement actionSetting = doc.createElement( "actionsetting" );
301  actionSetting.setAttribute( "type", action.type() );
302  actionSetting.setAttribute( "name", action.name() );
303  actionSetting.setAttribute( "shortTitle", action.shortTitle() );
304  actionSetting.setAttribute( "icon", action.iconPath() );
305  actionSetting.setAttribute( "action", action.action() );
306  actionSetting.setAttribute( "capture", action.capture() );
307  actionSetting.setAttribute( "showInAttributeTable", action.showInAttributeTable() );
308  aActions.appendChild( actionSetting );
309  }
310  layer_node.appendChild( aActions );
311 
312  return true;
313 }
314 
315 bool QgsActionManager::readXML( const QDomNode& layer_node )
316 {
317  mActions.clear();
318 
319  QDomNode aaNode = layer_node.namedItem( "attributeactions" );
320 
321  if ( !aaNode.isNull() )
322  {
323  mDefaultAction = aaNode.toElement().attribute( "default", 0 ).toInt();
324 
325  QDomNodeList actionsettings = aaNode.childNodes();
326  for ( int i = 0; i < actionsettings.size(); ++i )
327  {
328  QDomElement setting = actionsettings.item( i ).toElement();
329  mActions.append(
330  QgsAction( static_cast< QgsAction::ActionType >( setting.attributeNode( "type" ).value().toInt() ),
331  setting.attributeNode( "name" ).value(),
332  setting.attributeNode( "action" ).value(),
333  setting.attributeNode( "icon" ).value(),
334  setting.attributeNode( "capture" ).value().toInt() != 0,
335  !setting.attributes().contains( "showInAttributeTable" ) || setting.attributeNode( "showInAttributeTable" ).value().toInt() != 0,
336  setting.attributeNode( "shortTitle" ).value()
337  )
338  );
339  }
340  }
341  return true;
342 }
343 
344 void ( *QgsActionManager::smPythonExecute )( const QString & ) = nullptr;
345 
346 void QgsActionManager::setPythonExecute( void ( *runPython )( const QString & ) )
347 {
348  smPythonExecute = runPython;
349 }
QList< QgsAction > listActions() const
Return a list of all actions.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
void clear()
QString iconPath() const
The path to the icon.
Definition: qgsaction.h:103
Single variable definition for use within a QgsExpressionContextScope.
static unsigned index
QString cap(int nth) const
QDomNode item(int index) const
bool contains(const Key &key) const
Q_DECL_DEPRECATED QVariant evaluate(const QgsFeature *f)
Evaluate the feature and return the result.
bool contains(const QString &name) const
bool capture() const
Whether to capture output for display when this action is run.
Definition: qgsaction.h:115
QDomNode appendChild(const QDomNode &newChild)
void addAction(QgsAction::ActionType type, const QString &name, const QString &action, bool capture=false)
Add an action with the given name and action details.
ActionType type() const
The action type.
Definition: qgsaction.h:112
QString attribute(const QString &name, const QString &defValue) const
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
bool readXML(const QDomNode &layer_node)
Reads the actions in in XML format.
int size() const
void removeAt(int i)
QString evalErrorString() const
Returns evaluation error.
Container of fields for a vector layer.
Definition: qgsfield.h:252
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:187
QString shortTitle() const
The short title is used to label user interface elements like buttons.
Definition: qgsaction.h:100
QDomNodeList childNodes() const
int count() const
Return number of items.
Definition: qgsfield.cpp:402
QString parserErrorString() const
Returns parser error.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
const QgsAction & at(int idx) const
Get the action at the specified index.
int size() const
QgsFields fields() const
Returns the list of fields of this layer.
QDomElement toElement() const
int matchedLength() const
int indexIn(const QString &str, int offset, CaretMode caretMode) const
void append(const T &value)
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
void setAttribute(const QString &name, const QString &value)
int toInt(bool *ok, int base) const
Utility class that encapsulates an action based on vector attributes.
Definition: qgsaction.h:25
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
bool isEmpty() const
QString trimmed() const
void clearActions()
Removes all actions.
bool runable() const
Checks if the action is runable on the current platform.
Definition: qgsaction.cpp:19
void removeAction(int index)
Remove an action at given index.
QString name() const
The name of the action. This may be a longer description.
Definition: qgsaction.h:97
bool writeXML(QDomNode &layer_node, QDomDocument &doc) const
Writes the actions out in XML format.
iterator end()
bool showInAttributeTable() const
Whether this action should be shown on the attribute table.
Definition: qgsaction.h:118
Single scope for storing variables and functions for use within a QgsExpressionContext.
iterator begin()
static Q_DECL_DEPRECATED void setPythonExecute(void(*)(const QString &))
QDomNode namedItem(const QString &name) const
QStringRef midRef(int position, int n) const
QString value() const
bool isNull() const
QString & replace(int position, int n, QChar after)
QString mid(int position, int n) const
Q_DECL_DEPRECATED QString expandAction(QString action, const QgsAttributeMap &attributes, uint defaultValueIndex)
Expands the given action, replacing all &#39;s with the value as given.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a python statement.
static Q_DECL_DEPRECATED QString replaceExpressionText(const QString &action, const QgsFeature *feat, QgsVectorLayer *layer, const QMap< QString, QVariant > *substitutionMap=nullptr, const QgsDistanceArea *distanceArea=nullptr)
This function currently replaces each expression between [% and %] in the string with the result of i...
static QgsExpressionContextScope * projectScope()
Creates a new scope which contains variables and functions relating to the current QGIS project...
int size() const
QString action() const
The action.
Definition: qgsaction.h:109
static QgsRunProcess * create(const QString &action, bool capture)
Definition: qgsrunprocess.h:46
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
QDomElement createElement(const QString &tagName)
bool openUrl(const QUrl &url)
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:271
int size() const
Get the number of actions managed by this.
QDomAttr attributeNode(const QString &name)
QString toString() const
void doAction(int index, const QgsFeature &feat, int defaultValueIndex=0, const QgsExpressionContextScope &scope=QgsExpressionContextScope())
Does the given action.
QString attributeDisplayName(int attributeIndex) const
Convenience function that returns the attribute alias if defined or the field name else...
QUrl fromLocalFile(const QString &localFile)
const T value(const Key &key) const
QDomNamedNodeMap attributes() const