Display variables and functions from contexts in expression builder

This commit is contained in:
Nyall Dawson 2015-08-19 16:14:32 +10:00
parent c9c12bc7f9
commit bfc8f56ad4
18 changed files with 245 additions and 30 deletions

View File

@ -678,6 +678,12 @@ class QgsComposition : QGraphicsScene
*/
void refreshDataDefinedProperty( const QgsComposerObject::DataDefinedProperty property = QgsComposerObject::AllProperties, const QgsExpressionContext* context = 0 );
/** Creates an expression context relating to the compositions's current state. The context includes
* scopes for global, project, composition and atlas properties.
* @note added in QGIS 2.12
*/
QgsExpressionContext* createExpressionContext() const;
protected:
void init();

View File

@ -121,6 +121,7 @@ class QgsExpressionContextScope
QVariant variable( const QString& name ) const;
/** Returns a list of variable names contained within the scope.
* @see functionNames()
*/
QStringList variableNames() const;
@ -147,10 +148,17 @@ class QgsExpressionContextScope
* @param name function name
* @returns function, or null if matching function could not be found
* @see hasFunction()
* @see functionNames()
* @see variable()
*/
QgsExpression::Function* function( const QString &name ) const;
/** Retrieves a list of names of functions contained in the scope.
* @see function()
* @see variableNames()
*/
QStringList functionNames() const;
/** Adds a function to the scope.
* @param name function name
* @param function function to insert. Ownership is transferred to the scope.
@ -163,7 +171,7 @@ class QgsExpressionContextScope
* @param feature feature for scope
*/
void setFeature( const QgsFeature& feature );
/** Convenience function for setting a fields for the scope. Any existing
* fields set by the scope will be overwritten.
* @param fields fields for scope
@ -253,11 +261,20 @@ class QgsExpressionContext
/** Returns a list of variables names set by all scopes in the context.
* @returns list of unique variable names
* @see filteredVariableNames
* @see functionNames
* @see hasVariable
* @see variable
*/
QStringList variableNames() const;
/** Returns a filtered list of variables names set by all scopes in the context. The included
* variables are those which should be seen by users.
* @returns filtered list of unique variable names
* @see variableNames
*/
QStringList filteredVariableNames() const;
/** Returns whether a variable is read only, and should not be modifiable by users.
* @param name variable name
* @returns true if variable is read only. Read only status will be taken from last
@ -272,6 +289,12 @@ class QgsExpressionContext
*/
bool hasFunction( const QString& name ) const;
/** Retrieves a list of function names contained in the context.
* @see function()
* @see variableNames()
*/
QStringList functionNames() const;
/** Fetches a matching function from the context. The function will be fetched
* from the last scope contained within the context which has a matching
* function set.
@ -297,13 +320,13 @@ class QgsExpressionContext
* context. Ownership of the scope is transferred to the stack.
*/
QgsExpressionContext& operator<< ( QgsExpressionContextScope* scope /Transfer/ );
/** Convenience function for setting a feature for the context. The feature
* will be set within the last scope of the context, so will override any
* existing features within the context.
* @param feature feature for context
*/
void setFeature( const QgsFeature& feature );
void setFeature( const QgsFeature& feature );
/** Convenience function for setting a fields for the context. The fields
* will be set within the last scope of the context, so will override any
@ -311,7 +334,7 @@ class QgsExpressionContext
* @param fields fields for context
*/
void setFields( const QgsFields& fields );
};
/** \ingroup core
@ -378,12 +401,12 @@ class QgsExpressionContextUtils
* For instance, layer name, id and fields.
*/
static QgsExpressionContextScope* layerScope( QgsMapLayer* layer ) /Factory/;
/** Helper function for creating an expression context which contains just a feature and fields
* collection. Generally this method should not be used as the created context does not include
* standard scopes such as the global and project scopes.
*/
static QgsExpressionContext createFeatureBasedContext( const QgsFeature& feature, const QgsFields& fields );
static QgsExpressionContext createFeatureBasedContext( const QgsFeature& feature, const QgsFields& fields );
/** Sets a layer context variable. This variable will be contained within scopes retrieved via
* layerScope().

View File

@ -8,7 +8,8 @@ class QgsExpressionBuilderDialog : QDialog
%End
public:
QgsExpressionBuilderDialog( QgsVectorLayer* layer, QString startText = QString(), QWidget* parent /TransferThis/ = NULL, QString key = "generic" );
QgsExpressionBuilderDialog( QgsVectorLayer* layer, QString startText = QString(), QWidget* parent /TransferThis/ = NULL, QString key = "generic",
const QgsExpressionContext& context = QgsExpressionContext() );
/** The builder widget that is used by the dialog */
QgsExpressionBuilderWidget* expressionBuilder();
@ -17,6 +18,21 @@ class QgsExpressionBuilderDialog : QDialog
QString expressionText();
/** Returns the expression context for the dialog. The context is used for the expression
* preview result and for populating the list of available functions and variables.
* @see setExpressionContext
* @note added in QGIS 2.12
*/
QgsExpressionContext expressionContext() const;
/** Sets the expression context for the dialog. The context is used for the expression
* preview result and for populating the list of available functions and variables.
* @param context expression context
* @see expressionContext
* @note added in QGIS 2.12
*/
void setExpressionContext( const QgsExpressionContext& context );
/** Sets geometry calculator used in distance/area calculations. */
void setGeomCalculator( const QgsDistanceArea & da );

View File

@ -100,6 +100,21 @@ class QgsExpressionBuilderWidget : QWidget
/** Sets the expression string for the widget */
void setExpressionText( const QString& expression );
/** Returns the expression context for the widget. The context is used for the expression
* preview result and for populating the list of available functions and variables.
* @see setExpressionContext
* @note added in QGIS 2.12
*/
QgsExpressionContext expressionContext() const;
/** Sets the expression context for the widget. The context is used for the expression
* preview result and for populating the list of available functions and variables.
* @param context expression context
* @see expressionContext
* @note added in QGIS 2.12
*/
void setExpressionContext( const QgsExpressionContext& context );
/** Registers a node item for the expression builder.
* @param group The group the item will be show in the tree view. If the group doesn't exsit it will be created.
* @param label The label that is show to the user for the item in the tree.

View File

@ -94,8 +94,10 @@ void QgsAtlasCompositionWidget::on_mAtlasFilenameExpressionButton_clicked()
return;
}
QgsExpressionBuilderDialog exprDlg( atlasMap->coverageLayer(), mAtlasFilenamePatternEdit->text(), this );
QScopedPointer<QgsExpressionContext> context( mComposition->createExpressionContext() );
QgsExpressionBuilderDialog exprDlg( atlasMap->coverageLayer(), mAtlasFilenamePatternEdit->text(), this, "generic", *context );
exprDlg.setWindowTitle( tr( "Expression based filename" ) );
if ( exprDlg.exec() == QDialog::Accepted )
{
QString expression = exprDlg.expressionText();
@ -233,8 +235,10 @@ void QgsAtlasCompositionWidget::on_mAtlasFeatureFilterButton_clicked()
return;
}
QgsExpressionBuilderDialog exprDlg( vl, mAtlasFeatureFilterEdit->text(), this );
QScopedPointer<QgsExpressionContext> context( mComposition->createExpressionContext() );
QgsExpressionBuilderDialog exprDlg( vl, mAtlasFeatureFilterEdit->text(), this, "generic", *context );
exprDlg.setWindowTitle( tr( "Expression based filter" ) );
if ( exprDlg.exec() == QDialog::Accepted )
{
QString expression = exprDlg.expressionText();

View File

@ -798,7 +798,8 @@ void QgsComposerAttributeTableWidget::on_mFeatureFilterButton_clicked()
return;
}
QgsExpressionBuilderDialog exprDlg( mComposerTable->sourceLayer(), mFeatureFilterEdit->text(), this );
QScopedPointer<QgsExpressionContext> context( mComposerTable->createExpressionContext() );
QgsExpressionBuilderDialog exprDlg( mComposerTable->sourceLayer(), mFeatureFilterEdit->text(), this, "generic", *context );
exprDlg.setWindowTitle( tr( "Expression based filter" ) );
if ( exprDlg.exec() == QDialog::Accepted )
{

View File

@ -369,7 +369,8 @@ void QgsComposerHtmlWidget::on_mInsertExpressionButton_clicked()
// use the atlas coverage layer, if any
QgsVectorLayer* coverageLayer = atlasCoverageLayer();
QgsExpressionBuilderDialog exprDlg( coverageLayer, selText, this );
QScopedPointer<QgsExpressionContext> context( mHtml->createExpressionContext() );
QgsExpressionBuilderDialog exprDlg( coverageLayer, selText, this, "generic", *context );
exprDlg.setWindowTitle( tr( "Insert expression" ) );
if ( exprDlg.exec() == QDialog::Accepted )
{

View File

@ -152,7 +152,9 @@ void QgsComposerLabelWidget::on_mInsertExpressionButton_clicked()
// use the atlas coverage layer, if any
QgsVectorLayer* coverageLayer = atlasCoverageLayer();
QgsExpressionBuilderDialog exprDlg( coverageLayer, selText, this );
QScopedPointer<QgsExpressionContext> context( mComposerLabel->createExpressionContext() );
QgsExpressionBuilderDialog exprDlg( coverageLayer, selText, this, "generic", *context );
exprDlg.setWindowTitle( tr( "Insert expression" ) );
if ( exprDlg.exec() == QDialog::Accepted )
{

View File

@ -464,7 +464,9 @@ void QgsComposerTableWidget::on_mFeatureFilterButton_clicked()
return;
}
QgsExpressionBuilderDialog exprDlg( mComposerTable->vectorLayer(), mFeatureFilterEdit->text(), this );
QScopedPointer<QgsExpressionContext> context( mComposerTable->createExpressionContext() );
QgsExpressionBuilderDialog exprDlg( mComposerTable->vectorLayer(), mFeatureFilterEdit->text(), this, "generic", *context );
exprDlg.setWindowTitle( tr( "Expression based filter" ) );
if ( exprDlg.exec() == QDialog::Accepted )
{

View File

@ -741,6 +741,12 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene
*/
void refreshDataDefinedProperty( const QgsComposerObject::DataDefinedProperty property = QgsComposerObject::AllProperties, const QgsExpressionContext* context = 0 );
/** Creates an expression context relating to the compositions's current state. The context includes
* scopes for global, project, composition and atlas properties.
* @note added in QGIS 2.12
*/
QgsExpressionContext* createExpressionContext() const;
protected:
void init();
@ -900,12 +906,6 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene
*/
void prepareDataDefinedExpression( QgsDataDefined *dd, QMap< QgsComposerObject::DataDefinedProperty, QgsDataDefined* >* dataDefinedProperties, const QgsExpressionContext& context ) const;
/** Creates an expression context relating to the compositions's current state. The context includes
* scopes for global, project, composition and atlas properties.
* @note added in QGIS 2.12
*/
QgsExpressionContext* createExpressionContext() const;
/** Check whether any data defined page settings are active.
* @returns true if any data defined page settings are active.
* @note this method was added in version 2.5

View File

@ -3041,6 +3041,7 @@ QString QgsExpression::group( QString name )
gGroups.insert( "Color", QObject::tr( "Color" ) );
gGroups.insert( "GeometryGroup", QObject::tr( "Geometry" ) );
gGroups.insert( "Record", QObject::tr( "Record" ) );
gGroups.insert( "Variables", QObject::tr( "Variables" ) );
}
//return the translated name for this group. If group does not

View File

@ -127,6 +127,11 @@ QgsExpression::Function* QgsExpressionContextScope::function( const QString& nam
return mFunctions.contains( name ) ? mFunctions.value( name ) : 0;
}
QStringList QgsExpressionContextScope::functionNames() const
{
return mFunctions.keys();
}
void QgsExpressionContextScope::addFunction( const QString& name, QgsScopedExpressionFunction* function )
{
mFunctions.insert( name, function );
@ -248,6 +253,22 @@ QStringList QgsExpressionContext::variableNames() const
return names.toSet().toList();
}
QStringList QgsExpressionContext::filteredVariableNames() const
{
QStringList allVariables = variableNames();
QStringList filtered;
Q_FOREACH ( QString variable, allVariables )
{
if ( variable.startsWith( "_" ) )
continue;
filtered << variable;
}
filtered.sort();
return filtered;
}
bool QgsExpressionContext::isReadOnly( const QString& name ) const { Q_UNUSED( name ); return true; }
bool QgsExpressionContext::hasFunction( const QString &name ) const
@ -260,6 +281,18 @@ bool QgsExpressionContext::hasFunction( const QString &name ) const
return false;
}
QStringList QgsExpressionContext::functionNames() const
{
QStringList result;
Q_FOREACH ( const QgsExpressionContextScope* scope, mStack )
{
result << scope->functionNames();
}
result = result.toSet().toList();
result.sort();
return result;
}
QgsExpression::Function *QgsExpressionContext::function( const QString &name ) const
{
//iterate through stack backwards, so that higher priority variables take precedence

View File

@ -150,6 +150,7 @@ class CORE_EXPORT QgsExpressionContextScope
QVariant variable( const QString& name ) const;
/** Returns a list of variable names contained within the scope.
* @see functionNames()
*/
QStringList variableNames() const;
@ -176,10 +177,17 @@ class CORE_EXPORT QgsExpressionContextScope
* @param name function name
* @returns function, or null if matching function could not be found
* @see hasFunction()
* @see functionNames()
* @see variable()
*/
QgsExpression::Function* function( const QString &name ) const;
/** Retrieves a list of names of functions contained in the scope.
* @see function()
* @see variableNames()
*/
QStringList functionNames() const;
/** Adds a function to the scope.
* @param name function name
* @param function function to insert. Ownership is transferred to the scope.
@ -284,11 +292,20 @@ class CORE_EXPORT QgsExpressionContext
/** Returns a list of variables names set by all scopes in the context.
* @returns list of unique variable names
* @see filteredVariableNames
* @see functionNames
* @see hasVariable
* @see variable
*/
QStringList variableNames() const;
/** Returns a filtered list of variables names set by all scopes in the context. The included
* variables are those which should be seen by users.
* @returns filtered list of unique variable names
* @see variableNames
*/
QStringList filteredVariableNames() const;
/** Returns whether a variable is read only, and should not be modifiable by users.
* @param name variable name
* @returns true if variable is read only. Read only status will be taken from last
@ -303,6 +320,12 @@ class CORE_EXPORT QgsExpressionContext
*/
bool hasFunction( const QString& name ) const;
/** Retrieves a list of function names contained in the context.
* @see function()
* @see variableNames()
*/
QStringList functionNames() const;
/** Fetches a matching function from the context. The function will be fetched
* from the last scope contained within the context which has a matching
* function set.

View File

@ -16,7 +16,7 @@
#include "qgsexpressionbuilderdialog.h"
#include <QSettings>
QgsExpressionBuilderDialog::QgsExpressionBuilderDialog( QgsVectorLayer* layer, QString startText, QWidget* parent, QString key )
QgsExpressionBuilderDialog::QgsExpressionBuilderDialog( QgsVectorLayer* layer, QString startText, QWidget* parent, QString key, const QgsExpressionContext &context )
: QDialog( parent ), mRecentKey( key )
{
setupUi( this );
@ -24,6 +24,7 @@ QgsExpressionBuilderDialog::QgsExpressionBuilderDialog( QgsVectorLayer* layer, Q
QPushButton* okButton = buttonBox->button( QDialogButtonBox::Ok );
connect( builder, SIGNAL( expressionParsed( bool ) ), okButton, SLOT( setEnabled( bool ) ) );
builder->setExpressionContext( context );
builder->setLayer( layer );
builder->setExpressionText( startText );
builder->loadFieldNames();
@ -48,6 +49,16 @@ QString QgsExpressionBuilderDialog::expressionText()
return builder->expressionText();
}
QgsExpressionContext QgsExpressionBuilderDialog::expressionContext() const
{
return builder->expressionContext();
}
void QgsExpressionBuilderDialog::setExpressionContext( const QgsExpressionContext &context )
{
builder->setExpressionContext( context );
}
void QgsExpressionBuilderDialog::done( int r )
{
QDialog::done( r );

View File

@ -26,7 +26,8 @@
class GUI_EXPORT QgsExpressionBuilderDialog : public QDialog, private Ui::QgsExpressionBuilderDialogBase
{
public:
QgsExpressionBuilderDialog( QgsVectorLayer* layer, QString startText = QString(), QWidget* parent = NULL, QString key = "generic" );
QgsExpressionBuilderDialog( QgsVectorLayer* layer, QString startText = QString(), QWidget* parent = NULL, QString key = "generic",
const QgsExpressionContext& context = QgsExpressionContext() );
/** The builder widget that is used by the dialog */
QgsExpressionBuilderWidget* expressionBuilder();
@ -35,6 +36,21 @@ class GUI_EXPORT QgsExpressionBuilderDialog : public QDialog, private Ui::QgsExp
QString expressionText();
/** Returns the expression context for the dialog. The context is used for the expression
* preview result and for populating the list of available functions and variables.
* @see setExpressionContext
* @note added in QGIS 2.12
*/
QgsExpressionContext expressionContext() const;
/** Sets the expression context for the dialog. The context is used for the expression
* preview result and for populating the list of available functions and variables.
* @param context expression context
* @see expressionContext
* @note added in QGIS 2.12
*/
void setExpressionContext( const QgsExpressionContext& context );
/** Sets geometry calculator used in distance/area calculations. */
void setGeomCalculator( const QgsDistanceArea & da );

View File

@ -35,11 +35,6 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
{
setupUi( this );
//TODO - allow setting context for widget, so that preview has access to full context for expression
//and variables etc can be shown in builder widget
mExpressionContext << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope();
mValueGroupBox->hide();
mLoadGroupBox->hide();
// highlighter = new QgsExpressionHighlighter( txtExpressionString->document() );
@ -104,9 +99,8 @@ void QgsExpressionBuilderWidget::setLayer( QgsVectorLayer *layer )
{
mLayer = layer;
mExpressionContext = QgsExpressionContext();
mExpressionContext << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope();
//TODO - remove existing layer scope from context
if ( mLayer )
mExpressionContext << QgsExpressionContextUtils::layerScope( mLayer );
}
@ -464,6 +458,8 @@ void QgsExpressionBuilderWidget::updateFunctionTree()
QString name = specials[i]->name();
registerItem( specials[i]->group(), name, " " + name + " " );
}
loadExpressionContext();
}
void QgsExpressionBuilderWidget::setGeomCalculator( const QgsDistanceArea & da )
@ -481,6 +477,13 @@ void QgsExpressionBuilderWidget::setExpressionText( const QString& expression )
txtExpressionString->setText( expression );
}
void QgsExpressionBuilderWidget::setExpressionContext( const QgsExpressionContext &context )
{
mExpressionContext = context;
loadExpressionContext();
}
void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
{
QString text = expressionText();
@ -498,7 +501,6 @@ void QgsExpressionBuilderWidget::on_txtExpressionString_textChanged()
}
QgsExpression exp( text );
mExpressionContext.setFeature( QgsFeature() );
if ( mLayer )
{
@ -568,6 +570,28 @@ QString QgsExpressionBuilderWidget::formatPreviewString( const QString& previewS
}
}
void QgsExpressionBuilderWidget::loadExpressionContext()
{
QStringList variableNames = mExpressionContext.filteredVariableNames();
Q_FOREACH ( QString variable, variableNames )
{
registerItem( "Variables", variable, " @" + variable + " " );
}
// Load the functions from the expression context
QStringList contextFunctions = mExpressionContext.functionNames();
Q_FOREACH ( QString functionName, contextFunctions )
{
QgsExpression::Function* func = mExpressionContext.function( functionName );
QString name = func->name();
if ( name.startsWith( "_" ) ) // do not display private functions
continue;
if ( func->params() != 0 )
name += "(";
registerItem( func->group(), func->name(), " " + name + " ", func->helptext() );
}
}
void QgsExpressionBuilderWidget::on_txtSearchEdit_textChanged()
{
mProxyModel->setFilterWildcard( txtSearchEdit->text() );

View File

@ -142,6 +142,21 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp
/** Sets the expression string for the widget */
void setExpressionText( const QString& expression );
/** Returns the expression context for the widget. The context is used for the expression
* preview result and for populating the list of available functions and variables.
* @see setExpressionContext
* @note added in QGIS 2.12
*/
QgsExpressionContext expressionContext() const { return mExpressionContext; }
/** Sets the expression context for the widget. The context is used for the expression
* preview result and for populating the list of available functions and variables.
* @param context expression context
* @see expressionContext
* @note added in QGIS 2.12
*/
void setExpressionContext( const QgsExpressionContext& context );
/** Registers a node item for the expression builder.
* @param group The group the item will be show in the tree view. If the group doesn't exsit it will be created.
* @param label The label that is show to the user for the item in the tree.
@ -218,6 +233,8 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp
*/
QString formatPreviewString( const QString &previewString ) const;
void loadExpressionContext();
QString mFunctionsPath;
QgsVectorLayer *mLayer;
QStandardItemModel *mModel;

View File

@ -203,6 +203,13 @@ void TestQgsExpressionContext::contextScopeFunctions()
QVERIFY( scope.function( "get_test_value" ) );
QgsExpressionContext temp;
QCOMPARE( scope.function( "get_test_value" )->func( QVariantList(), &temp, 0 ).toInt(), 42 );
//test functionNames
scope.addFunction( "get_test_value2", new GetTestValueFunction() );
QStringList functionNames = scope.functionNames();
QCOMPARE( functionNames.count(), 2 );
QVERIFY( functionNames.contains( "get_test_value" ) );
QVERIFY( functionNames.contains( "get_test_value2" ) );
}
void TestQgsExpressionContext::contextStack()
@ -262,6 +269,13 @@ void TestQgsExpressionContext::contextStack()
QCOMPARE( context.variable( "test2" ).toInt(), 11 );
QCOMPARE( context.variableNames().length(), 2 );
//check filteredVariableNames method
scope2->setVariable( "_hidden", 5 );
QStringList filteredNames = context.filteredVariableNames();
QCOMPARE( filteredNames.count(), 2 );
QCOMPARE( filteredNames.at( 0 ), QString( "test" ) );
QCOMPARE( filteredNames.at( 1 ), QString( "test2" ) );
//test scopes method
QList< QgsExpressionContextScope*> scopes = context.scopes();
QCOMPARE( scopes.length(), 2 );
@ -325,6 +339,12 @@ void TestQgsExpressionContext::contextStackFunctions()
QVERIFY( context.hasFunction( "get_test_value2" ) );
QVERIFY( context.function( "get_test_value2" ) );
QCOMPARE( context.function( "get_test_value2" )->func( QVariantList(), &temp, 0 ).toInt(), 42 );
//test functionNames
QStringList names = context.functionNames();
QCOMPARE( names.count(), 2 );
QCOMPARE( names.at( 0 ), QString( "get_test_value" ) );
QCOMPARE( names.at( 1 ), QString( "get_test_value2" ) );
}
void TestQgsExpressionContext::evaluate()