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