QGIS API Documentation  2.5.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups 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 
49 {
50  if ( index >= 0 && index < mActions.size() )
51  {
52  mActions.removeAt( index );
53  }
54 }
55 
56 void QgsAttributeAction::doAction( int index, const QgsFeature& feat, int defaultValueIndex )
57 {
58  QMap<QString, QVariant> substitutionMap;
59  if ( defaultValueIndex >= 0 )
60  {
61  QVariant defaultValue = feat.attribute( defaultValueIndex );
62  if ( defaultValue.isValid() )
63  substitutionMap.insert( "$currfield", defaultValue );
64  }
65 
66  doAction( index, feat, &substitutionMap );
67 }
68 
69 void QgsAttributeAction::doAction( int index, const QgsFeature &feat, const QMap<QString, QVariant> *substitutionMap )
70 {
71  if ( index < 0 || index >= size() )
72  return;
73 
74  const QgsAction &action = at( index );
75  if ( !action.runable() )
76  return;
77 
78  // search for expressions while expanding actions
79  QString expandedAction = QgsExpression::replaceExpressionText( action.action(), &feat, mLayer , substitutionMap );
80  if ( expandedAction.isEmpty() )
81  return;
82 
83  QgsAction newAction( action.type(), action.name(), expandedAction, action.capture() );
84  runAction( newAction );
85 }
86 
87 void QgsAttributeAction::runAction( const QgsAction &action, void ( *executePython )( const QString & ) )
88 {
89  if ( action.type() == QgsAction::OpenUrl )
90  {
91  QFileInfo finfo( action.action() );
92  if ( finfo.exists() && finfo.isFile() )
93  QDesktopServices::openUrl( QUrl::fromLocalFile( action.action() ) );
94  else
95  QDesktopServices::openUrl( QUrl( action.action(), QUrl::TolerantMode ) );
96  }
97  else if ( action.type() == QgsAction::GenericPython )
98  {
99  if ( executePython )
100  {
101  // deprecated
102  executePython( action.action() );
103  }
104  else if ( smPythonExecute )
105  {
106  // deprecated
107  smPythonExecute( action.action() );
108  }
109  else
110  {
111  // TODO: capture output from QgsPythonRunner (like QgsRunProcess does)
112  QgsPythonRunner::run( action.action() );
113  }
114  }
115  else
116  {
117  // The QgsRunProcess instance created by this static function
118  // deletes itself when no longer needed.
119  QgsRunProcess::create( action.action(), action.capture() );
120  }
121 }
122 
123 QString QgsAttributeAction::expandAction( QString action, const QgsAttributeMap &attributes,
124  uint clickedOnValue )
125 {
126  // This function currently replaces all %% characters in the action
127  // with the value from values[clickedOnValue].second, and then
128  // searches for all strings that go %attribute_name, where
129  // attribute_name is found in values[x].first, and replaces any that
130  // it finds by values[s].second.
131 
132  // Additional substitutions could include symbols for $CWD, $HOME,
133  // etc (and their OSX and Windows equivalents)
134 
135  // This function will potentially fall apart if any of the
136  // substitutions produce text that could match another
137  // substitution. May be better to adopt a two pass approach - identify
138  // all matches and their substitutions and then do a second pass
139  // for the actual substitutions.
140 
141  QString expanded_action;
142  if ( attributes.contains( clickedOnValue ) )
143  expanded_action = action.replace( "%%", attributes[clickedOnValue].toString() );
144  else
145  expanded_action = action;
146 
147  const QgsFields &fields = mLayer->pendingFields();
148 
149  for ( int i = 0; i < 4; i++ )
150  {
151  for ( QgsAttributeMap::const_iterator it = attributes.begin(); it != attributes.end(); ++it )
152  {
153  int attrIdx = it.key();
154  if ( attrIdx < 0 || attrIdx >= fields.count() )
155  continue;
156 
157  QString to_replace;
158  switch ( i )
159  {
160  case 0: to_replace = "[%" + fields[attrIdx].name() + "]"; break;
161  case 1: to_replace = "[%" + mLayer->attributeDisplayName( attrIdx ) + "]"; break;
162  case 2: to_replace = "%" + fields[attrIdx].name(); break;
163  case 3: to_replace = "%" + mLayer->attributeDisplayName( attrIdx ); break;
164  }
165 
166  expanded_action = expanded_action.replace( to_replace, it.value().toString() );
167  }
168  }
169 
170  return expanded_action;
171 }
172 
173 QString QgsAttributeAction::expandAction( QString action, QgsFeature &feat, const QMap<QString, QVariant> *substitutionMap )
174 {
175  // This function currently replaces each expression between [% and %]
176  // in the action with the result of its evaluation on the feature
177  // passed as argument.
178 
179  // Additional substitutions can be passed through the substitutionMap
180  // parameter
181 
182  QString expr_action;
183 
184  int index = 0;
185  while ( index < action.size() )
186  {
187  QRegExp rx = QRegExp( "\\[%([^\\]]+)%\\]" );
188 
189  int pos = rx.indexIn( action, index );
190  if ( pos < 0 )
191  break;
192 
193  int start = index;
194  index = pos + rx.matchedLength();
195 
196  QString to_replace = rx.cap( 1 ).trimmed();
197  QgsDebugMsg( "Found expression: " + to_replace );
198 
199  if ( substitutionMap && substitutionMap->contains( to_replace ) )
200  {
201  expr_action += action.mid( start, pos - start ) + substitutionMap->value( to_replace ).toString();
202  continue;
203  }
204 
205  QgsExpression exp( to_replace );
206  if ( exp.hasParserError() )
207  {
208  QgsDebugMsg( "Expression parser error: " + exp.parserErrorString() );
209  expr_action += action.mid( start, index - start );
210  continue;
211  }
212 
213  QVariant result = exp.evaluate( &feat, mLayer->pendingFields() );
214  if ( exp.hasEvalError() )
215  {
216  QgsDebugMsg( "Expression parser eval error: " + exp.evalErrorString() );
217  expr_action += action.mid( start, index - start );
218  continue;
219  }
220 
221  QgsDebugMsg( "Expression result is: " + result.toString() );
222  expr_action += action.mid( start, pos - start ) + result.toString();
223  }
224 
225  expr_action += action.mid( index );
226  return expr_action;
227 }
228 
229 
230 bool QgsAttributeAction::writeXML( QDomNode& layer_node, QDomDocument& doc ) const
231 {
232  QDomElement aActions = doc.createElement( "attributeactions" );
233 
234  for ( int i = 0; i < mActions.size(); i++ )
235  {
236  QDomElement actionSetting = doc.createElement( "actionsetting" );
237  actionSetting.setAttribute( "type", mActions[i].type() );
238  actionSetting.setAttribute( "name", mActions[i].name() );
239  actionSetting.setAttribute( "action", mActions[i].action() );
240  actionSetting.setAttribute( "capture", mActions[i].capture() );
241  aActions.appendChild( actionSetting );
242  }
243  layer_node.appendChild( aActions );
244 
245  return true;
246 }
247 
248 bool QgsAttributeAction::readXML( const QDomNode& layer_node )
249 {
250  mActions.clear();
251 
252  QDomNode aaNode = layer_node.namedItem( "attributeactions" );
253 
254  if ( !aaNode.isNull() )
255  {
256  QDomNodeList actionsettings = aaNode.childNodes();
257  for ( unsigned int i = 0; i < actionsettings.length(); ++i )
258  {
259  QDomElement setting = actionsettings.item( i ).toElement();
260  addAction(( QgsAction::ActionType ) setting.attributeNode( "type" ).value().toInt(),
261  setting.attributeNode( "name" ).value(),
262  setting.attributeNode( "action" ).value(),
263  setting.attributeNode( "capture" ).value().toInt() != 0 );
264  }
265  }
266  return true;
267 }
268 
269 void ( *QgsAttributeAction::smPythonExecute )( const QString & ) = 0;
270 
271 void QgsAttributeAction::setPythonExecute( void ( *runPython )( const QString & ) )
272 {
273  smPythonExecute = runPython;
274 }
Class for parsing and evaluation of expressions (formerly called "search strings").
Definition: qgsexpression.h:89
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:96
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:36
static void(* smPythonExecute)(const QString &)
Container of fields for a vector layer.
Definition: qgsfield.h:163
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:113
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.
QList< QgsAction > mActions
int count() const
Return number of items.
Definition: qgsfield.h:200
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.
void runAction(const QgsAction &action, void(*executePython)(const QString &)=0)
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
QgsVectorLayer * mLayer
QString parserErrorString() const
Returns parser error.
Definition: qgsexpression.h:98
QString evalErrorString() const
Returns evaluation error.
bool readXML(const QDomNode &layer_node)
Reads the actions in in XML format.
static QString replaceExpressionText(const QString &action, const QgsFeature *feat, QgsVectorLayer *layer, const QMap< QString, QVariant > *substitutionMap=0)
This function currently replaces each expression between [% and %] in the string with the result of i...
void removeAction(int index)
Remove an action at given index.