Skip to content

Commit 1fe82b3

Browse files
committed
[FEATURE] use expressions engine to evaluate feature actions
work done for Regione Toscana-SIGTA
1 parent 35f5da7 commit 1fe82b3

9 files changed

+242
-50
lines changed

src/app/qgsattributeactiondialog.cpp

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ back to QgsVectorLayer.
2222

2323
#include "qgsattributeactiondialog.h"
2424
#include "qgsattributeaction.h"
25+
#include "qgsexpressionbuilderdialog.h"
2526

2627
#include <QFileDialog>
2728
#include <QHeaderView>
2829
#include <QMessageBox>
29-
30+
#include <QSettings>
3031

3132
QgsAttributeActionDialog::QgsAttributeActionDialog( QgsAttributeAction* actions,
3233
const QgsFieldMap& fields,
@@ -50,6 +51,7 @@ QgsAttributeActionDialog::QgsAttributeActionDialog( QgsAttributeAction* actions,
5051
connect( insertButton, SIGNAL( clicked() ), this, SLOT( insert() ) );
5152
connect( updateButton, SIGNAL( clicked() ), this, SLOT( update() ) );
5253
connect( insertFieldButton, SIGNAL( clicked() ), this, SLOT( insertField() ) );
54+
connect( insertExpressionButton, SIGNAL( clicked() ), this, SLOT( insertExpression() ) );
5355

5456
init();
5557
// Populate the combo box with the field names. Will the field names
@@ -143,16 +145,36 @@ void QgsAttributeActionDialog::swapRows( int row1, int row2 )
143145

144146
void QgsAttributeActionDialog::browse()
145147
{
146-
// Popup a file browser and place the results into the actionName
147-
// widget
148-
148+
// Popup a file browser and place the results into the action widget
149149
QString action = QFileDialog::getOpenFileName(
150150
this, tr( "Select an action", "File dialog window title" ) );
151151

152152
if ( !action.isNull() )
153153
actionAction->insert( action );
154154
}
155155

156+
void QgsAttributeActionDialog::insertExpression()
157+
{
158+
QString selText = actionAction->selectedText();
159+
160+
// edit the selected expression if there's one
161+
if ( selText.startsWith( "[%" ) && selText.endsWith( "%]" ) )
162+
selText = selText.mid( 2, selText.size() - 3 );
163+
164+
// display the expression builder
165+
QgsExpressionBuilderDialog dlg( mActions->layer(), selText, this );
166+
dlg.setWindowTitle( tr( "Insert expression" ) );
167+
if ( dlg.exec() == QDialog::Accepted )
168+
{
169+
QString expression = dlg.expressionBuilder()->getExpressionString();
170+
//Only add the expression if the user has entered some text.
171+
if ( !expression.isEmpty() )
172+
{
173+
actionAction->insert( "[%" + expression + "%]" );
174+
}
175+
}
176+
}
177+
156178
void QgsAttributeActionDialog::remove()
157179
{
158180
QList<QTableWidgetItem *> selection = attributeActionTable->selectedItems();
@@ -234,13 +256,14 @@ void QgsAttributeActionDialog::update()
234256

235257
void QgsAttributeActionDialog::insertField()
236258
{
237-
// Take the selected field, preprend a % and insert into the action
238-
// field at the cursor position
259+
// Convert the selected field to an expression and
260+
// insert it into the action at the cursor position
239261

240262
if ( !fieldComboBox->currentText().isNull() )
241263
{
242-
QString field( "%" );
264+
QString field = "[% \"";
243265
field += fieldComboBox->currentText();
266+
field += "\" %]";
244267
actionAction->insert( field );
245268
}
246269
}

src/app/qgsattributeactiondialog.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class QgsAttributeActionDialog: public QWidget, private Ui::QgsAttributeActionDi
5050
void remove();
5151
void insert();
5252
void insertField();
53+
void insertExpression();
5354
void apply();
5455
void update();
5556
void itemSelectionChanged();

src/app/qgsfeatureaction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ QgsFeatureAction::QgsFeatureAction( const QString &name, QgsFeature &f, QgsVecto
3636

3737
void QgsFeatureAction::execute()
3838
{
39-
mLayer->actions()->doAction( mAction, mFeature.attributeMap(), mIdx );
39+
mLayer->actions()->doAction( mAction, mFeature, mIdx );
4040
}
4141

4242
QgsAttributeDialog *QgsFeatureAction::newDialog( bool cloneFeature )

src/app/qgsidentifyresults.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ void QgsIdentifyResults::contextMenuEvent( QContextMenuEvent* event )
429429

430430
mActionPopup = new QMenu();
431431

432-
int idx = 0;
432+
int idx = -1;
433433
QTreeWidgetItem *featItem = featureItem( item );
434434
if ( featItem )
435435
{
@@ -549,17 +549,15 @@ void QgsIdentifyResults::deactivate()
549549

550550
void QgsIdentifyResults::doAction( QTreeWidgetItem *item, int action )
551551
{
552-
int idx;
553-
QgsAttributeMap attributes;
554-
QTreeWidgetItem *featItem = retrieveAttributes( item, attributes, idx );
552+
QTreeWidgetItem *featItem = featureItem( item );
555553
if ( !featItem )
556554
return;
557555

558556
QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( featItem->parent()->data( 0, Qt::UserRole ).value<QObject *>() );
559557
if ( !layer )
560558
return;
561559

562-
idx = -1;
560+
int idx = -1;
563561
if ( item->parent() == featItem )
564562
{
565563
QString fieldName = item->data( 0, Qt::DisplayRole ).toString();
@@ -574,7 +572,8 @@ void QgsIdentifyResults::doAction( QTreeWidgetItem *item, int action )
574572
}
575573
}
576574

577-
layer->actions()->doAction( action, attributes, idx );
575+
int featIdx = featItem->data( 0, Qt::UserRole + 1 ).toInt();
576+
layer->actions()->doAction( action, mFeatures[ featIdx ], idx );
578577
}
579578

580579
QTreeWidgetItem *QgsIdentifyResults::featureItem( QTreeWidgetItem *item )

src/core/qgsattributeaction.cpp

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,23 @@
2222
* *
2323
***************************************************************************/
2424

25-
#include <QList>
26-
27-
#include <QStringList>
28-
#include <QDomElement>
29-
3025
#include "qgsattributeaction.h"
3126
#include "qgspythonrunner.h"
3227
#include "qgsrunprocess.h"
3328
#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+
3442

3543
void QgsAttributeAction::addAction( QgsAction::ActionType type, QString name, QString action, bool capture )
3644
{
@@ -44,7 +52,6 @@ void QgsAttributeAction::doAction( int index, const QgsAttributeMap &attributes,
4452
return;
4553

4654
const QgsAction &action = at( index );
47-
4855
if ( !action.runable() )
4956
return;
5057

@@ -58,29 +65,67 @@ void QgsAttributeAction::doAction( int index, const QgsAttributeMap &attributes,
5865
// the UI and the code in this function to select on the
5966
// action.capture() return value.
6067

61-
// The QgsRunProcess instance created by this static function
62-
// deletes itself when no longer needed.
6368
QString expandedAction = expandAction( action.action(), attributes, defaultValueIndex );
69+
if ( expandedAction.isEmpty() )
70+
return;
71+
72+
QgsAction newAction( action.type(), action.name(), expandedAction, action.capture() );
73+
runAction( newAction, executePython );
74+
}
75+
76+
void QgsAttributeAction::doAction( int index, QgsFeature &feat, int defaultValueIndex )
77+
{
78+
QMap<QString, QVariant> substitutionMap;
79+
if ( defaultValueIndex >= 0 )
80+
substitutionMap.insert( "$currfield", QVariant( defaultValueIndex ) );
81+
82+
doAction( index, feat, &substitutionMap );
83+
}
84+
85+
void QgsAttributeAction::doAction( int index, QgsFeature &feat,
86+
const QMap<QString, QVariant> *substitutionMap )
87+
{
88+
if ( index < 0 || index >= size() )
89+
return;
90+
91+
const QgsAction &action = at( index );
92+
if ( !action.runable() )
93+
return;
94+
95+
// search for expressions while expanding actions
96+
QString expandedAction = expandAction( action.action(), feat, substitutionMap );
97+
if ( expandedAction.isEmpty() )
98+
return;
99+
100+
QgsAction newAction( action.type(), action.name(), expandedAction, action.capture() );
101+
runAction( newAction );
102+
}
103+
104+
void QgsAttributeAction::runAction( const QgsAction &action, void ( *executePython )( const QString & ) )
105+
{
64106
if ( action.type() == QgsAction::GenericPython )
65107
{
66108
if ( executePython )
67109
{
68110
// deprecated
69-
executePython( expandedAction );
111+
executePython( action.action() );
70112
}
71113
else if ( smPythonExecute )
72114
{
73115
// deprecated
74-
smPythonExecute( expandedAction );
116+
smPythonExecute( action.action() );
75117
}
76118
else
77119
{
78-
QgsPythonRunner::run( expandedAction );
120+
// TODO: capture output from QgsPythonRunner
121+
QgsPythonRunner::run( action.action() );
79122
}
80123
}
81124
else
82125
{
83-
QgsRunProcess::create( expandedAction, action.capture() );
126+
// The QgsRunProcess instance created by this static function
127+
// deletes itself when no longer needed.
128+
QgsRunProcess::create( action.action(), action.capture() );
84129
}
85130
}
86131

@@ -89,7 +134,7 @@ QString QgsAttributeAction::expandAction( QString action, const QgsAttributeMap
89134
{
90135
// This function currently replaces all %% characters in the action
91136
// with the value from values[clickedOnValue].second, and then
92-
// searches for all strings that go %attribite_name, where
137+
// searches for all strings that go %attribute_name, where
93138
// attribute_name is found in values[x].first, and replaces any that
94139
// it finds by values[s].second.
95140

@@ -134,6 +179,63 @@ QString QgsAttributeAction::expandAction( QString action, const QgsAttributeMap
134179
return expanded_action;
135180
}
136181

182+
QString QgsAttributeAction::expandAction( QString action, QgsFeature &feat, const QMap<QString, QVariant> *substitutionMap )
183+
{
184+
// This function currently replaces each expression between [% and %]
185+
// in the action with the result of its evaluation on the feature
186+
// passed as argument.
187+
188+
// Additional substitutions can be passed through the substitutionMap
189+
// parameter
190+
191+
QString expr_action;
192+
193+
int index = 0;
194+
while ( index < action.size() )
195+
{
196+
QRegExp rx = QRegExp( "\\[%([^\\]]+)%\\]" );
197+
198+
int pos = rx.indexIn( action, index );
199+
if ( pos < 0 )
200+
break;
201+
202+
int start = index;
203+
index = pos + rx.matchedLength();
204+
205+
QString to_replace = rx.cap(1).trimmed();
206+
QgsDebugMsg( "Found expression:" + to_replace );
207+
208+
if ( substitutionMap && substitutionMap->contains( to_replace ) )
209+
{
210+
expr_action += action.mid( start, pos - start ) + substitutionMap->value( to_replace ).toString();
211+
continue;
212+
}
213+
214+
QgsExpression* exp = new QgsExpression( to_replace );
215+
if ( exp->hasParserError() )
216+
{
217+
QgsDebugMsg( "Expression parser error:" + exp->parserErrorString() );
218+
expr_action += action.mid( start, index - start );
219+
continue;
220+
}
221+
222+
QVariant result = exp->evaluate( &feat, mLayer->pendingFields() );
223+
if ( exp->hasEvalError() )
224+
{
225+
QgsDebugMsg( "Expression parser eval error:" + exp->evalErrorString() );
226+
expr_action += action.mid( start, index - start );
227+
continue;
228+
}
229+
230+
QgsDebugMsg( "Expression result is: " + result.toString() );
231+
expr_action += action.mid( start, pos - start ) + result.toString();
232+
}
233+
234+
expr_action += action.mid( index );
235+
return expr_action;
236+
}
237+
238+
137239
bool QgsAttributeAction::writeXML( QDomNode& layer_node, QDomDocument& doc ) const
138240
{
139241
QDomElement aActions = doc.createElement( "attributeactions" );

0 commit comments

Comments
 (0)