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