QGIS API Documentation  2.2.0-Valmiera
 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 }