Skip to content

Commit

Permalink
Move algorithm expression context generation to QgsProcessingAlgorithm
Browse files Browse the repository at this point in the history
Fix error when selecting "from expression" in algorithm parameter dialog
  • Loading branch information
nyalldawson committed Jun 6, 2017
1 parent 607fed8 commit 39d20a4
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 2 deletions.
9 changes: 9 additions & 0 deletions python/core/processing/qgsprocessingalgorithm.sip
Expand Up @@ -228,6 +228,15 @@ class QgsProcessingAlgorithm
:rtype: QWidget
%End

QgsExpressionContext createExpressionContext( const QVariantMap &parameters,
QgsProcessingContext &context ) const;
%Docstring
Creates an expression context relating to the algorithm. This can be called by algorithms
to create a new expression context ready for evaluating expressions within the algorithm.
:rtype: QgsExpressionContext
%End


protected:

bool addParameter( QgsProcessingParameterDefinition *parameterDefinition /Transfer/ );
Expand Down
8 changes: 8 additions & 0 deletions python/core/qgsexpressioncontext.sip
Expand Up @@ -866,6 +866,14 @@ class QgsExpressionContextUtils
:rtype: QgsExpressionContext
%End

static QgsExpressionContextScope *processingAlgorithmScope( const QgsProcessingAlgorithm *algorithm, const QVariantMap &parameters, QgsProcessingContext &context ) /Factory/;
%Docstring
Creates a new scope which contains variables and functions relating to a processing ``algorithm``,
when used with the specified ``parameters`` and ``context``.
For instance, algorithm name and parameter functions.
:rtype: QgsExpressionContextScope
%End

static void registerContextFunctions();
%Docstring
Registers all known core functions provided by QgsExpressionContextScope objects.
Expand Down
8 changes: 6 additions & 2 deletions python/plugins/processing/gui/DestinationSelectionPanel.py
Expand Up @@ -36,12 +36,14 @@
from qgis.gui import QgsEncodingFileDialog, QgsExpressionBuilderDialog
from qgis.core import (QgsDataSourceUri,
QgsCredentials,
QgsExpression,
QgsSettings,
QgsProcessingParameterFeatureSink,
QgsProcessingFeatureSinkDefinition)
from processing.core.ProcessingConfig import ProcessingConfig
from processing.core.outputs import OutputVector
from processing.core.outputs import OutputDirectory
from processing.tools.dataobjects import createContext
from processing.gui.PostgisTableSelector import PostgisTableSelector
from processing.gui.ParameterGuiUtils import getFileFilter

Expand Down Expand Up @@ -122,11 +124,13 @@ def selectOutput(self):
popupMenu.exec_(QCursor.pos())

def showExpressionsBuilder(self):
context = self.alg.createExpressionContext({}, createContext())
dlg = QgsExpressionBuilderDialog(None, self.leText.text(), self, 'generic',
self.parameter.expressionContext(self.alg))
context)
dlg.setWindowTitle(self.tr('Expression based output'))
if dlg.exec_() == QDialog.Accepted:
self.leText.setText(dlg.expressionText())
expression = QgsExpression(dlg.expressionText())
self.leText.setText(expression.evaluate(context))

def saveToTemporary(self):
self.leText.setText('')
Expand Down
11 changes: 11 additions & 0 deletions resources/function_help/json/parameter
@@ -0,0 +1,11 @@
{
"name": "parameter",
"type": "function",
"description": "Returns the value of a processing algorithm input parameter.",
"arguments": [
{"arg":"name", "description":"name of the corresponding input parameter"}
],
"examples": [
{ "expression":"parameter('BUFFER_SIZE')", "returns":"5.6"}
]
}
3 changes: 3 additions & 0 deletions src/core/expression/qgsexpression.cpp
Expand Up @@ -685,6 +685,9 @@ void QgsExpression::initVariableHelp()
//cluster variables
sVariableHelpTexts.insert( QStringLiteral( "cluster_color" ), QCoreApplication::translate( "cluster_color", "Color of symbols within a cluster, or NULL if symbols have mixed colors." ) );
sVariableHelpTexts.insert( QStringLiteral( "cluster_size" ), QCoreApplication::translate( "cluster_size", "Number of symbols contained within a cluster." ) );

//processing variables
sVariableHelpTexts.insert( QStringLiteral( "algorithm_id" ), QCoreApplication::translate( "algorithm_id", "Unique ID for algorithm." ) );
}

QString QgsExpression::variableHelpText( const QString &variableName, bool showValue, const QVariant &value )
Expand Down
17 changes: 17 additions & 0 deletions src/core/processing/qgsprocessingalgorithm.cpp
Expand Up @@ -21,6 +21,7 @@
#include "qgsprocessingparameters.h"
#include "qgsprocessingoutputs.h"
#include "qgsrectangle.h"
#include "qgsprocessingcontext.h"

QgsProcessingAlgorithm::~QgsProcessingAlgorithm()
{
Expand Down Expand Up @@ -100,6 +101,22 @@ QWidget *QgsProcessingAlgorithm::createCustomParametersWidget( QWidget * ) const
return nullptr;
}

QgsExpressionContext QgsProcessingAlgorithm::createExpressionContext( const QVariantMap &parameters,
QgsProcessingContext &context ) const
{
// start with context's expression context
QgsExpressionContext c = context.expressionContext();
if ( c.scopeCount() == 0 )
{
//empty scope, populate with initial scopes
c << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope( context.project() );
}

c << QgsExpressionContextUtils::processingAlgorithmScope( this, parameters, context );
return c;
}

bool QgsProcessingAlgorithm::addParameter( QgsProcessingParameterDefinition *definition )
{
if ( !definition )
Expand Down
8 changes: 8 additions & 0 deletions src/core/processing/qgsprocessingalgorithm.h
Expand Up @@ -229,6 +229,14 @@ class CORE_EXPORT QgsProcessingAlgorithm
*/
virtual QWidget *createCustomParametersWidget( QWidget *parent = nullptr ) const SIP_FACTORY;

/**
* Creates an expression context relating to the algorithm. This can be called by algorithms
* to create a new expression context ready for evaluating expressions within the algorithm.
*/
QgsExpressionContext createExpressionContext( const QVariantMap &parameters,
QgsProcessingContext &context ) const;


protected:

/**
Expand Down
44 changes: 44 additions & 0 deletions src/core/qgsexpressioncontext.cpp
Expand Up @@ -29,6 +29,8 @@
#include "qgsapplication.h"
#include "qgsmapsettings.h"
#include "qgsmaplayerlistutils.h"
#include "qgsprocessingcontext.h"
#include "qgsprocessingalgorithm.h"

#include <QSettings>
#include <QDir>
Expand Down Expand Up @@ -707,6 +709,30 @@ class GetLayerVisibility : public QgsScopedExpressionFunction

};

class GetProcessingParameterValue : public QgsScopedExpressionFunction
{
public:
GetProcessingParameterValue( const QVariantMap &params )
: QgsScopedExpressionFunction( QStringLiteral( "parameter" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "name" ) ), QStringLiteral( "Processing" ) )
, mParams( params )
{}

QVariant func( const QVariantList &values, const QgsExpressionContext *, QgsExpression * ) override
{
return mParams.value( values.at( 0 ).toString() );
}

QgsScopedExpressionFunction *clone() const override
{
return new GetProcessingParameterValue( mParams );
}

private:

const QVariantMap mParams;

};

///@endcond

QgsExpressionContextScope *QgsExpressionContextUtils::projectScope( const QgsProject *project )
Expand Down Expand Up @@ -1077,11 +1103,29 @@ QgsExpressionContext QgsExpressionContextUtils::createFeatureBasedContext( const
return QgsExpressionContext() << scope;
}

QgsExpressionContextScope *QgsExpressionContextUtils::processingAlgorithmScope( const QgsProcessingAlgorithm *algorithm, const QVariantMap &parameters, QgsProcessingContext &context )
{
// set aside for future use
Q_UNUSED( context );

std::unique_ptr< QgsExpressionContextScope > scope( new QgsExpressionContextScope( QObject::tr( "Algorithm" ) ) );
if ( !algorithm )
return scope.release();

//add standard algorithm variables
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "algorithm_id" ), algorithm->id(), true ) );

scope->addFunction( QStringLiteral( "parameter" ), new GetProcessingParameterValue( parameters ) );

return scope.release();
}

void QgsExpressionContextUtils::registerContextFunctions()
{
QgsExpression::registerFunction( new GetNamedProjectColor( nullptr ) );
QgsExpression::registerFunction( new GetComposerItemVariables( nullptr ) );
QgsExpression::registerFunction( new GetLayerVisibility( QList<QgsMapLayer *>() ) );
QgsExpression::registerFunction( new GetProcessingParameterValue( QVariantMap() ) );
}

bool QgsScopedExpressionFunction::usesGeometry( const QgsExpressionNodeFunction *node ) const
Expand Down
9 changes: 9 additions & 0 deletions src/core/qgsexpressioncontext.h
Expand Up @@ -36,6 +36,8 @@ class QgsAtlasComposition;
class QgsMapSettings;
class QgsProject;
class QgsSymbol;
class QgsProcessingAlgorithm;
class QgsProcessingContext;

/** \ingroup core
* \class QgsScopedExpressionFunction
Expand Down Expand Up @@ -788,6 +790,13 @@ class CORE_EXPORT QgsExpressionContextUtils
*/
static QgsExpressionContext createFeatureBasedContext( const QgsFeature &feature, const QgsFields &fields );

/**
* Creates a new scope which contains variables and functions relating to a processing \a algorithm,
* when used with the specified \a parameters and \a context.
* For instance, algorithm name and parameter functions.
*/
static QgsExpressionContextScope *processingAlgorithmScope( const QgsProcessingAlgorithm *algorithm, const QVariantMap &parameters, QgsProcessingContext &context ) SIP_FACTORY;

/** Registers all known core functions provided by QgsExpressionContextScope objects.
*/
static void registerContextFunctions();
Expand Down
26 changes: 26 additions & 0 deletions tests/src/core/testqgsprocessing.cpp
Expand Up @@ -30,6 +30,7 @@
#include "qgspoint.h"
#include "qgsgeometry.h"
#include "qgsvectorfilewriter.h"
#include "qgsexpressioncontext.h"

class DummyAlgorithm : public QgsProcessingAlgorithm
{
Expand Down Expand Up @@ -219,6 +220,7 @@ class TestQgsProcessing: public QObject
void combineLayerExtent();
void processingFeatureSource();
void processingFeatureSink();
void algorithmScope();

private:

Expand Down Expand Up @@ -2425,5 +2427,29 @@ void TestQgsProcessing::processingFeatureSink()
QCOMPARE( layer2->crs().authid(), QStringLiteral( "EPSG:3113" ) );
}

void TestQgsProcessing::algorithmScope()
{
QgsProcessingContext pc;

// no alg
std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::processingAlgorithmScope( nullptr, QVariantMap(), pc ) );
QVERIFY( scope.get() );

// with alg
std::unique_ptr< QgsProcessingAlgorithm > alg( new DummyAlgorithm( "alg1" ) );
QVariantMap params;
params.insert( QStringLiteral( "a_param" ), 5 );
scope.reset( QgsExpressionContextUtils::processingAlgorithmScope( alg.get(), params, pc ) );
QVERIFY( scope.get() );
QCOMPARE( scope->variable( QStringLiteral( "algorithm_id" ) ).toString(), alg->id() );

QgsExpressionContext context;
context.appendScope( scope.release() );
QgsExpression exp( "parameter('bad')" );
QVERIFY( !exp.evaluate( &context ).isValid() );
QgsExpression exp2( "parameter('a_param')" );
QCOMPARE( exp2.evaluate( &context ).toInt(), 5 );
}

QGSTEST_MAIN( TestQgsProcessing )
#include "testqgsprocessing.moc"

0 comments on commit 39d20a4

Please sign in to comment.