QGIS API Documentation  2.12.0-Lyon
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: to_replace = "[%" + fields[attrIdx].name() + "]"; break;
179  case 1: to_replace = "[%" + mLayer->attributeDisplayName( attrIdx ) + "]"; break;
180  case 2: to_replace = "%" + fields[attrIdx].name(); break;
181  case 3: to_replace = "%" + mLayer->attributeDisplayName( attrIdx ); break;
182  }
183 
184  expanded_action = expanded_action.replace( to_replace, it.value().toString() );
185  }
186  }
187 
188  return expanded_action;
189 }
190 
192 {
193  // This function currently replaces each expression between [% and %]
194  // in the action with the result of its evaluation on the feature
195  // passed as argument.
196 
197  // Additional substitutions can be passed through the substitutionMap
198  // parameter
199 
200  QString expr_action;
201 
202  int index = 0;
203  while ( index < action.size() )
204  {
205  QRegExp rx = QRegExp( "\\[%([^\\]]+)%\\]" );
206 
207  int pos = rx.indexIn( action, index );
208  if ( pos < 0 )
209  break;
210 
211  int start = index;
212  index = pos + rx.matchedLength();
213 
214  QString to_replace = rx.cap( 1 ).trimmed();
215  QgsDebugMsg( "Found expression: " + to_replace );
216 
217  if ( substitutionMap && substitutionMap->contains( to_replace ) )
218  {
219  expr_action += action.mid( start, pos - start ) + substitutionMap->value( to_replace ).toString();
220  continue;
221  }
222 
223  QgsExpression exp( to_replace );
224  if ( exp.hasParserError() )
225  {
226  QgsDebugMsg( "Expression parser error: " + exp.parserErrorString() );
227  expr_action += action.midRef( start, index - start );
228  continue;
229  }
230 
231  QgsExpressionContext context = createExpressionContext();
232  context.setFeature( feat );
233 
234  QVariant result = exp.evaluate( &context );
235  if ( exp.hasEvalError() )
236  {
237  QgsDebugMsg( "Expression parser eval error: " + exp.evalErrorString() );
238  expr_action += action.midRef( start, index - start );
239  continue;
240  }
241 
242  QgsDebugMsg( "Expression result is: " + result.toString() );
243  expr_action += action.mid( start, pos - start ) + result.toString();
244  }
245 
246  expr_action += action.midRef( index );
247  return expr_action;
248 }
249 
250 
251 bool QgsAttributeAction::writeXML( QDomNode& layer_node, QDomDocument& doc ) const
252 {
253  QDomElement aActions = doc.createElement( "attributeactions" );
254 
255  for ( int i = 0; i < mActions.size(); i++ )
256  {
257  QDomElement actionSetting = doc.createElement( "actionsetting" );
258  actionSetting.setAttribute( "type", mActions[i].type() );
259  actionSetting.setAttribute( "name", mActions[i].name() );
260  actionSetting.setAttribute( "icon", mActions[i].iconPath() );
261  actionSetting.setAttribute( "action", mActions[i].action() );
262  actionSetting.setAttribute( "capture", mActions[i].capture() );
263  aActions.appendChild( actionSetting );
264  }
265  layer_node.appendChild( aActions );
266 
267  return true;
268 }
269 
270 bool QgsAttributeAction::readXML( const QDomNode& layer_node )
271 {
272  mActions.clear();
273 
274  QDomNode aaNode = layer_node.namedItem( "attributeactions" );
275 
276  if ( !aaNode.isNull() )
277  {
278  QDomNodeList actionsettings = aaNode.childNodes();
279  for ( int i = 0; i < actionsettings.size(); ++i )
280  {
281  QDomElement setting = actionsettings.item( i ).toElement();
282  addAction(( QgsAction::ActionType ) setting.attributeNode( "type" ).value().toInt(),
283  setting.attributeNode( "name" ).value(),
284  setting.attributeNode( "action" ).value(),
285  setting.attributeNode( "icon" ).value(),
286  setting.attributeNode( "capture" ).value().toInt() != 0 );
287  }
288  }
289  return true;
290 }
291 
292 void ( *QgsAttributeAction::smPythonExecute )( const QString & ) = 0;
293 
294 void QgsAttributeAction::setPythonExecute( void ( *runPython )( const QString & ) )
295 {
296  smPythonExecute = runPython;
297 }
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").
Definition: qgsexpression.h:92
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:177
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:176
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:311
QString expandAction(QString action, const QgsAttributeMap &attributes, uint defaultValueIndex)
Expands the given action, replacing all 's with the value as given.
QgsAction & at(int idx)
iterator end()
QString action() const
The action.
iterator begin()
static 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:238
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.
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
static Q_DECL_DEPRECATED QString replaceExpressionText(const QString &action, const QgsFeature *feat, QgsVectorLayer *layer, const QMap< QString, QVariant > *substitutionMap=0, const QgsDistanceArea *distanceArea=0)
This function currently replaces each expression between [% and %] in the string with the result of i...