QGIS API Documentation  2.8.2-Wien
 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 
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 }