QGIS API Documentation  2.7.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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() )
98  QDesktopServices::openUrl( QUrl::fromLocalFile( action.action() ) );
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 
128 QString QgsAttributeAction::expandAction( QString action, const QgsAttributeMap &attributes,
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 
178 QString QgsAttributeAction::expandAction( QString action, QgsFeature &feat, const QMap<QString, QVariant> *substitutionMap )
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:87
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
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.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
Definition: qgsexpression.h:94
QMap< int, QVariant > QgsAttributeMap
Definition: qgsfeature.h:98
QVariant evaluate(const QgsFeature *f=NULL)
Evaluate the feature and return the result.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
Container of fields for a vector layer.
Definition: qgsfield.h:172
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:113
static QIcon icon(QString icon)
QString iconPath(QString iconFile)
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.
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
Utility class that encapsulates an action based on vector attributes.
int count() const
Return number of items.
Definition: qgsfield.h:214
QString expandAction(QString action, const QgsAttributeMap &attributes, uint defaultValueIndex)
QgsAction & at(int idx)
QString action() const
The action.
static void setPythonExecute(void(*)(const QString &))
ActionType type() const
The action type.
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:230
void doAction(int index, const QgsFeature &feat, int defaultValueIndex=0)
bool runable() const
Whether the action is runable on the current platform.
const QgsFields & pendingFields() const
returns field list in the to-be-committed state
static QgsRunProcess * create(const QString &action, bool capture)
Definition: qgsrunprocess.h:46
QString parserErrorString() const
Returns parser error.
Definition: qgsexpression.h:96
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.
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...