QGIS API Documentation  2.11.0-Master
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, QString name, QString action, bool capture )
44 {
45  mActions << QgsAction( type, name, action, capture );
46 }
47 
48 void QgsAttributeAction::addAction( QgsAction::ActionType type, QString name, 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  QString expandedAction = QgsExpression::replaceExpressionText( action.action(), &feat, mLayer, substitutionMap );
85  if ( expandedAction.isEmpty() )
86  return;
87 
88  QgsAction newAction( action.type(), action.name(), expandedAction, action.capture() );
89  runAction( newAction );
90 }
91 
92 void QgsAttributeAction::runAction( const QgsAction &action, void ( *executePython )( const QString & ) )
93 {
94  if ( action.type() == QgsAction::OpenUrl )
95  {
96  QFileInfo finfo( action.action() );
97  if ( finfo.exists() && finfo.isFile() )
99  else
100  QDesktopServices::openUrl( QUrl( action.action(), QUrl::TolerantMode ) );
101  }
102  else if ( action.type() == QgsAction::GenericPython )
103  {
104  if ( executePython )
105  {
106  // deprecated
107  executePython( action.action() );
108  }
109  else if ( smPythonExecute )
110  {
111  // deprecated
112  smPythonExecute( action.action() );
113  }
114  else
115  {
116  // TODO: capture output from QgsPythonRunner (like QgsRunProcess does)
117  QgsPythonRunner::run( action.action() );
118  }
119  }
120  else
121  {
122  // The QgsRunProcess instance created by this static function
123  // deletes itself when no longer needed.
124  QgsRunProcess::create( action.action(), action.capture() );
125  }
126 }
127 
129  uint clickedOnValue )
130 {
131  // This function currently replaces all %% characters in the action
132  // with the value from values[clickedOnValue].second, and then
133  // searches for all strings that go %attribute_name, where
134  // attribute_name is found in values[x].first, and replaces any that
135  // it finds by values[s].second.
136 
137  // Additional substitutions could include symbols for $CWD, $HOME,
138  // etc (and their OSX and Windows equivalents)
139 
140  // This function will potentially fall apart if any of the
141  // substitutions produce text that could match another
142  // substitution. May be better to adopt a two pass approach - identify
143  // all matches and their substitutions and then do a second pass
144  // for the actual substitutions.
145 
146  QString expanded_action;
147  if ( attributes.contains( clickedOnValue ) )
148  expanded_action = action.replace( "%%", attributes[clickedOnValue].toString() );
149  else
150  expanded_action = action;
151 
152  const QgsFields &fields = mLayer->pendingFields();
153 
154  for ( int i = 0; i < 4; i++ )
155  {
156  for ( QgsAttributeMap::const_iterator it = attributes.begin(); it != attributes.end(); ++it )
157  {
158  int attrIdx = it.key();
159  if ( attrIdx < 0 || attrIdx >= fields.count() )
160  continue;
161 
162  QString to_replace;
163  switch ( i )
164  {
165  case 0: to_replace = "[%" + fields[attrIdx].name() + "]"; break;
166  case 1: to_replace = "[%" + mLayer->attributeDisplayName( attrIdx ) + "]"; break;
167  case 2: to_replace = "%" + fields[attrIdx].name(); break;
168  case 3: to_replace = "%" + mLayer->attributeDisplayName( attrIdx ); break;
169  }
170 
171  expanded_action = expanded_action.replace( to_replace, it.value().toString() );
172  }
173  }
174 
175  return expanded_action;
176 }
177 
179 {
180  // This function currently replaces each expression between [% and %]
181  // in the action with the result of its evaluation on the feature
182  // passed as argument.
183 
184  // Additional substitutions can be passed through the substitutionMap
185  // parameter
186 
187  QString expr_action;
188 
189  int index = 0;
190  while ( index < action.size() )
191  {
192  QRegExp rx = QRegExp( "\\[%([^\\]]+)%\\]" );
193 
194  int pos = rx.indexIn( action, index );
195  if ( pos < 0 )
196  break;
197 
198  int start = index;
199  index = pos + rx.matchedLength();
200 
201  QString to_replace = rx.cap( 1 ).trimmed();
202  QgsDebugMsg( "Found expression: " + to_replace );
203 
204  if ( substitutionMap && substitutionMap->contains( to_replace ) )
205  {
206  expr_action += action.mid( start, pos - start ) + substitutionMap->value( to_replace ).toString();
207  continue;
208  }
209 
210  QgsExpression exp( to_replace );
211  if ( exp.hasParserError() )
212  {
213  QgsDebugMsg( "Expression parser error: " + exp.parserErrorString() );
214  expr_action += action.mid( start, index - start );
215  continue;
216  }
217 
218  QVariant result = exp.evaluate( &feat, mLayer->pendingFields() );
219  if ( exp.hasEvalError() )
220  {
221  QgsDebugMsg( "Expression parser eval error: " + exp.evalErrorString() );
222  expr_action += action.mid( start, index - start );
223  continue;
224  }
225 
226  QgsDebugMsg( "Expression result is: " + result.toString() );
227  expr_action += action.mid( start, pos - start ) + result.toString();
228  }
229 
230  expr_action += action.mid( index );
231  return expr_action;
232 }
233 
234 
235 bool QgsAttributeAction::writeXML( QDomNode& layer_node, QDomDocument& doc ) const
236 {
237  QDomElement aActions = doc.createElement( "attributeactions" );
238 
239  for ( int i = 0; i < mActions.size(); i++ )
240  {
241  QDomElement actionSetting = doc.createElement( "actionsetting" );
242  actionSetting.setAttribute( "type", mActions[i].type() );
243  actionSetting.setAttribute( "name", mActions[i].name() );
244  actionSetting.setAttribute( "icon", mActions[i].iconPath() );
245  actionSetting.setAttribute( "action", mActions[i].action() );
246  actionSetting.setAttribute( "capture", mActions[i].capture() );
247  aActions.appendChild( actionSetting );
248  }
249  layer_node.appendChild( aActions );
250 
251  return true;
252 }
253 
254 bool QgsAttributeAction::readXML( const QDomNode& layer_node )
255 {
256  mActions.clear();
257 
258  QDomNode aaNode = layer_node.namedItem( "attributeactions" );
259 
260  if ( !aaNode.isNull() )
261  {
262  QDomNodeList actionsettings = aaNode.childNodes();
263  for ( unsigned int i = 0; i < actionsettings.length(); ++i )
264  {
265  QDomElement setting = actionsettings.item( i ).toElement();
266  addAction(( QgsAction::ActionType ) setting.attributeNode( "type" ).value().toInt(),
267  setting.attributeNode( "name" ).value(),
268  setting.attributeNode( "action" ).value(),
269  setting.attributeNode( "icon" ).value(),
270  setting.attributeNode( "capture" ).value().toInt() != 0 );
271  }
272  }
273  return true;
274 }
275 
276 void ( *QgsAttributeAction::smPythonExecute )( const QString & ) = 0;
277 
278 void QgsAttributeAction::setPythonExecute( void ( *runPython )( const QString & ) )
279 {
280  smPythonExecute = runPython;
281 }
Class for parsing and evaluation of expressions (formerly called "search strings").
Definition: qgsexpression.h:86
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
void clear()
static unsigned index
void addAction(QgsAction::ActionType type, QString name, QString action, bool capture=false)
Add an action with the given name and action details.
QString cap(int nth) const
QDomNode item(int index) const
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
Definition: qgsexpression.h:93
bool contains(const Key &key) const
QDomNode appendChild(const QDomNode &newChild)
QVariant evaluate(const QgsFeature *f=NULL)
Evaluate the feature and return the result.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
int size() const
void removeAt(int i)
Container of fields for a vector layer.
Definition: qgsfield.h:173
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:119
QDomNodeList childNodes() const
QString iconPath(QString iconFile)
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.
QString attributeDisplayName(int attributeIndex) const
Convenience function that returns the attribute alias if defined or the field name else...
static bool run(QString command, QString messageOnError=QString())
execute a python statement
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.
bool isEmpty() const
QString trimmed() const
int count() const
Return number of items.
Definition: qgsfield.cpp:283
QString expandAction(QString action, const QgsAttributeMap &attributes, uint defaultValueIndex)
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.
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:236
QString mid(int position, int n) const
void doAction(int index, const QgsFeature &feat, int defaultValueIndex=0)
bool isValid() const
bool runable() const
Whether the action is runable on the current platform.
iterator insert(const Key &key, const T &value)
const QgsFields & pendingFields() const
returns field list in the to-be-committed state
uint length() const
static QgsRunProcess * create(const QString &action, bool capture)
Definition: qgsrunprocess.h:46
QDomElement createElement(const QString &tagName)
bool openUrl(const QUrl &url)
QString parserErrorString() const
Returns parser error.
Definition: qgsexpression.h:95
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 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...