[FEATURE] use expressions engine to evaluate feature actions

work done for Regione Toscana-SIGTA
This commit is contained in:
Giuseppe Sucameli 2012-01-11 15:27:15 +01:00
parent 35f5da70cd
commit 1fe82b316b
9 changed files with 242 additions and 50 deletions

View File

@ -22,11 +22,12 @@ back to QgsVectorLayer.
#include "qgsattributeactiondialog.h"
#include "qgsattributeaction.h"
#include "qgsexpressionbuilderdialog.h"
#include <QFileDialog>
#include <QHeaderView>
#include <QMessageBox>
#include <QSettings>
QgsAttributeActionDialog::QgsAttributeActionDialog( QgsAttributeAction* actions,
const QgsFieldMap& fields,
@ -50,6 +51,7 @@ QgsAttributeActionDialog::QgsAttributeActionDialog( QgsAttributeAction* actions,
connect( insertButton, SIGNAL( clicked() ), this, SLOT( insert() ) );
connect( updateButton, SIGNAL( clicked() ), this, SLOT( update() ) );
connect( insertFieldButton, SIGNAL( clicked() ), this, SLOT( insertField() ) );
connect( insertExpressionButton, SIGNAL( clicked() ), this, SLOT( insertExpression() ) );
init();
// Populate the combo box with the field names. Will the field names
@ -143,9 +145,7 @@ void QgsAttributeActionDialog::swapRows( int row1, int row2 )
void QgsAttributeActionDialog::browse()
{
// Popup a file browser and place the results into the actionName
// widget
// Popup a file browser and place the results into the action widget
QString action = QFileDialog::getOpenFileName(
this, tr( "Select an action", "File dialog window title" ) );
@ -153,6 +153,28 @@ void QgsAttributeActionDialog::browse()
actionAction->insert( action );
}
void QgsAttributeActionDialog::insertExpression()
{
QString selText = actionAction->selectedText();
// edit the selected expression if there's one
if ( selText.startsWith( "[%" ) && selText.endsWith( "%]" ) )
selText = selText.mid( 2, selText.size() - 3 );
// display the expression builder
QgsExpressionBuilderDialog dlg( mActions->layer(), selText, this );
dlg.setWindowTitle( tr( "Insert expression" ) );
if ( dlg.exec() == QDialog::Accepted )
{
QString expression = dlg.expressionBuilder()->getExpressionString();
//Only add the expression if the user has entered some text.
if ( !expression.isEmpty() )
{
actionAction->insert( "[%" + expression + "%]" );
}
}
}
void QgsAttributeActionDialog::remove()
{
QList<QTableWidgetItem *> selection = attributeActionTable->selectedItems();
@ -234,13 +256,14 @@ void QgsAttributeActionDialog::update()
void QgsAttributeActionDialog::insertField()
{
// Take the selected field, preprend a % and insert into the action
// field at the cursor position
// Convert the selected field to an expression and
// insert it into the action at the cursor position
if ( !fieldComboBox->currentText().isNull() )
{
QString field( "%" );
QString field = "[% \"";
field += fieldComboBox->currentText();
field += "\" %]";
actionAction->insert( field );
}
}

View File

@ -50,6 +50,7 @@ class QgsAttributeActionDialog: public QWidget, private Ui::QgsAttributeActionDi
void remove();
void insert();
void insertField();
void insertExpression();
void apply();
void update();
void itemSelectionChanged();

View File

@ -36,7 +36,7 @@ QgsFeatureAction::QgsFeatureAction( const QString &name, QgsFeature &f, QgsVecto
void QgsFeatureAction::execute()
{
mLayer->actions()->doAction( mAction, mFeature.attributeMap(), mIdx );
mLayer->actions()->doAction( mAction, mFeature, mIdx );
}
QgsAttributeDialog *QgsFeatureAction::newDialog( bool cloneFeature )

View File

@ -429,7 +429,7 @@ void QgsIdentifyResults::contextMenuEvent( QContextMenuEvent* event )
mActionPopup = new QMenu();
int idx = 0;
int idx = -1;
QTreeWidgetItem *featItem = featureItem( item );
if ( featItem )
{
@ -549,9 +549,7 @@ void QgsIdentifyResults::deactivate()
void QgsIdentifyResults::doAction( QTreeWidgetItem *item, int action )
{
int idx;
QgsAttributeMap attributes;
QTreeWidgetItem *featItem = retrieveAttributes( item, attributes, idx );
QTreeWidgetItem *featItem = featureItem( item );
if ( !featItem )
return;
@ -559,7 +557,7 @@ void QgsIdentifyResults::doAction( QTreeWidgetItem *item, int action )
if ( !layer )
return;
idx = -1;
int idx = -1;
if ( item->parent() == featItem )
{
QString fieldName = item->data( 0, Qt::DisplayRole ).toString();
@ -574,7 +572,8 @@ void QgsIdentifyResults::doAction( QTreeWidgetItem *item, int action )
}
}
layer->actions()->doAction( action, attributes, idx );
int featIdx = featItem->data( 0, Qt::UserRole + 1 ).toInt();
layer->actions()->doAction( action, mFeatures[ featIdx ], idx );
}
QTreeWidgetItem *QgsIdentifyResults::featureItem( QTreeWidgetItem *item )

View File

@ -22,15 +22,23 @@
* *
***************************************************************************/
#include <QList>
#include <QStringList>
#include <QDomElement>
#include "qgsattributeaction.h"
#include "qgspythonrunner.h"
#include "qgsrunprocess.h"
#include "qgsvectorlayer.h"
#include "qgsproject.h"
#include <qgslogger.h>
#include "qgsexpression.h"
#include <QList>
#include <QStringList>
#include <QDomElement>
#include <QSettings>
#include <QDesktopServices>
#include <QUrl>
#include <QDir>
#include <QFileInfo>
void QgsAttributeAction::addAction( QgsAction::ActionType type, QString name, QString action, bool capture )
{
@ -44,7 +52,6 @@ void QgsAttributeAction::doAction( int index, const QgsAttributeMap &attributes,
return;
const QgsAction &action = at( index );
if ( !action.runable() )
return;
@ -58,29 +65,67 @@ void QgsAttributeAction::doAction( int index, const QgsAttributeMap &attributes,
// the UI and the code in this function to select on the
// action.capture() return value.
// The QgsRunProcess instance created by this static function
// deletes itself when no longer needed.
QString expandedAction = expandAction( action.action(), attributes, defaultValueIndex );
if ( expandedAction.isEmpty() )
return;
QgsAction newAction( action.type(), action.name(), expandedAction, action.capture() );
runAction( newAction, executePython );
}
void QgsAttributeAction::doAction( int index, QgsFeature &feat, int defaultValueIndex )
{
QMap<QString, QVariant> substitutionMap;
if ( defaultValueIndex >= 0 )
substitutionMap.insert( "$currfield", QVariant( defaultValueIndex ) );
doAction( index, feat, &substitutionMap );
}
void QgsAttributeAction::doAction( int index, QgsFeature &feat,
const QMap<QString, QVariant> *substitutionMap )
{
if ( index < 0 || index >= size() )
return;
const QgsAction &action = at( index );
if ( !action.runable() )
return;
// search for expressions while expanding actions
QString expandedAction = expandAction( action.action(), feat, substitutionMap );
if ( expandedAction.isEmpty() )
return;
QgsAction newAction( action.type(), action.name(), expandedAction, action.capture() );
runAction( newAction );
}
void QgsAttributeAction::runAction( const QgsAction &action, void ( *executePython )( const QString & ) )
{
if ( action.type() == QgsAction::GenericPython )
{
if ( executePython )
{
// deprecated
executePython( expandedAction );
executePython( action.action() );
}
else if ( smPythonExecute )
{
// deprecated
smPythonExecute( expandedAction );
smPythonExecute( action.action() );
}
else
{
QgsPythonRunner::run( expandedAction );
// TODO: capture output from QgsPythonRunner
QgsPythonRunner::run( action.action() );
}
}
else
{
QgsRunProcess::create( expandedAction, action.capture() );
// The QgsRunProcess instance created by this static function
// deletes itself when no longer needed.
QgsRunProcess::create( action.action(), action.capture() );
}
}
@ -89,7 +134,7 @@ QString QgsAttributeAction::expandAction( QString action, const QgsAttributeMap
{
// This function currently replaces all %% characters in the action
// with the value from values[clickedOnValue].second, and then
// searches for all strings that go %attribite_name, where
// searches for all strings that go %attribute_name, where
// attribute_name is found in values[x].first, and replaces any that
// it finds by values[s].second.
@ -134,6 +179,63 @@ QString QgsAttributeAction::expandAction( QString action, const QgsAttributeMap
return expanded_action;
}
QString QgsAttributeAction::expandAction( QString action, QgsFeature &feat, const QMap<QString, QVariant> *substitutionMap )
{
// This function currently replaces each expression between [% and %]
// in the action with the result of its evaluation on the feature
// passed as argument.
// Additional substitutions can be passed through the substitutionMap
// parameter
QString expr_action;
int index = 0;
while ( index < action.size() )
{
QRegExp rx = QRegExp( "\\[%([^\\]]+)%\\]" );
int pos = rx.indexIn( action, index );
if ( pos < 0 )
break;
int start = index;
index = pos + rx.matchedLength();
QString to_replace = rx.cap(1).trimmed();
QgsDebugMsg( "Found expression:" + to_replace );
if ( substitutionMap && substitutionMap->contains( to_replace ) )
{
expr_action += action.mid( start, pos - start ) + substitutionMap->value( to_replace ).toString();
continue;
}
QgsExpression* exp = new QgsExpression( to_replace );
if ( exp->hasParserError() )
{
QgsDebugMsg( "Expression parser error:" + exp->parserErrorString() );
expr_action += action.mid( start, index - start );
continue;
}
QVariant result = exp->evaluate( &feat, mLayer->pendingFields() );
if ( exp->hasEvalError() )
{
QgsDebugMsg( "Expression parser eval error:" + exp->evalErrorString() );
expr_action += action.mid( start, index - start );
continue;
}
QgsDebugMsg( "Expression result is: " + result.toString() );
expr_action += action.mid( start, pos - start ) + result.toString();
}
expr_action += action.mid( index );
return expr_action;
}
bool QgsAttributeAction::writeXML( QDomNode& layer_node, QDomDocument& doc ) const
{
QDomElement aActions = doc.createElement( "attributeactions" );

View File

@ -107,24 +107,64 @@ class CORE_EXPORT QgsAttributeAction
// dialog box.
void addAction( QgsAction::ActionType type, QString name, QString action, bool capture = false );
//! Does the action using the given values. defaultValueIndex is an
// index into values which indicates which value in the values vector
// is to be used if the action has a default placeholder.
// @note parameter executePython deprecated (and missing in python binding)
void doAction( int index,
/*! Does the action using the given values. defaultValueIndex is an
* index into values which indicates which value in the values vector
* is to be used if the action has a default placeholder.
* @note parameter executePython deprecated (and missing in python binding)
* @deprecated
*/
Q_DECL_DEPRECATED void doAction( int index,
const QgsAttributeMap &attributes,
int defaultValueIndex = 0,
void ( *executePython )( const QString & ) = 0 );
/*! Does the given values. defaultValueIndex is the index of the
* field to be used if the action has a $currfield placeholder.
* @note added in 1.9
*/
void doAction( int index,
QgsFeature &feat,
int defaultValueIndex = 0 );
/*! Does the action using the expression builder to expand it
* and getting values from the passed feature attribute map.
* substitutionMap is used to pass custom substitutions, to replace
* each key in the map with the associated value
* @note added in 1.9
*/
void doAction( int index,
QgsFeature &feat,
const QMap<QString, QVariant> *substitutionMap = 0 );
//! Removes all actions
void clearActions() { mActions.clear(); }
//! Expands the given action, replacing all %'s with the value as
// given.
QString expandAction( QString action,
//! Return the layer
QgsVectorLayer *layer() { return mLayer; }
/*! Expands the given action, replacing all %'s with the value as
* given.
* @deprecated
*/
Q_DECL_DEPRECATED QString expandAction( QString action,
const QgsAttributeMap &attributes,
uint defaultValueIndex );
/*! Expands the given action using the expression builder
* This function currently replaces each expression between [% and %]
* placeholders in the action with the result of its evaluation on
* the feature passed as argument.
*
* Additional substitutions can be passed through the substitutionMap
* parameter
*
* @note added in 1.9
*/
QString expandAction( QString action,
QgsFeature &feat,
const QMap<QString, QVariant> *substitutionMap = 0 );
//! Writes the actions out in XML format
bool writeXML( QDomNode& layer_node, QDomDocument& doc ) const;
@ -142,6 +182,9 @@ class CORE_EXPORT QgsAttributeAction
QList<QgsAction> mActions;
QgsVectorLayer *mLayer;
static void ( *smPythonExecute )( const QString & );
void runAction( const QgsAction &action,
void ( *executePython )( const QString & ) = 0 );
};
#endif

View File

@ -555,21 +555,13 @@ void QgsAttributeTableModel::incomingChangeLayout()
void QgsAttributeTableModel::executeAction( int action, const QModelIndex &idx ) const
{
QgsAttributeMap attributes;
for ( int i = 0; i < mAttributes.size(); i++ )
{
attributes.insert( mAttributes[i], data( index( idx.row(), i ), Qt::EditRole ) );
}
mLayer->actions()->doAction( action, attributes, fieldIdx( idx.column() ) );
QgsFeature f = feature( idx );
mLayer->actions()->doAction( action, f, fieldIdx( idx.column() ) );
}
QgsFeature QgsAttributeTableModel::feature( QModelIndex &idx )
QgsFeature QgsAttributeTableModel::feature( const QModelIndex &idx ) const
{
QgsFeature f;
QgsAttributeMap attributes;
f.setFeatureId( rowToId( idx.row() ) );
for ( int i = 0; i < mAttributes.size(); i++ )
{

View File

@ -138,7 +138,7 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
void executeAction( int action, const QModelIndex &idx ) const;
/** return feature attributes at given index */
QgsFeature feature( QModelIndex &idx );
QgsFeature feature( const QModelIndex &idx ) const;
signals:
/**

View File

@ -253,6 +253,38 @@
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="insertExpressionButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Inserts an expression into the action</string>
</property>
<property name="text">
<string>Insert expression...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QComboBox" name="fieldComboBox">
<property name="toolTip">