From 0c4bf943c141ff88519ee7e2474d828230c84272 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 10 Oct 2016 12:27:11 +1000 Subject: [PATCH] Allow expression functions to appear in multiple groups (fix #15682) Now functions which make sense for multiple contexts (eg length, to_date) can appear in more than one group. --- python/core/qgsexpression.sip | 43 +++++++++++- src/core/qgsexpression.cpp | 22 +++---- src/core/qgsexpression.h | 90 ++++++++++++++++++++++++-- src/gui/qgsexpressionbuilderwidget.cpp | 14 +++- src/gui/qgsexpressionbuilderwidget.h | 14 ++++ 5 files changed, 163 insertions(+), 20 deletions(-) diff --git a/python/core/qgsexpression.sip b/python/core/qgsexpression.sip index f4dc271ecd86..0df7bff7ec7d 100644 --- a/python/core/qgsexpression.sip +++ b/python/core/qgsexpression.sip @@ -306,6 +306,19 @@ class QgsExpression bool handlesNull = false, bool isContextual = false ); + /** Constructor for function which uses unnamed parameters and group list + * @note added in QGIS 3.0 + */ + Function( const QString& fnname, + int params, + const QStringList& groups, + const QString& helpText = QString(), + bool usesGeometry = false, + const QSet& referencedColumns = QSet(), + bool lazyEval = false, + bool handlesNull = false, + bool isContextual = false ); + /** Constructor for function which uses named parameter list. * @note added in QGIS 2.16 */ @@ -319,6 +332,19 @@ class QgsExpression bool handlesNull = false, bool isContextual = false ); + /** Constructor for function which uses named parameter list and group list. + * @note added in QGIS 3.0 + */ + Function( const QString& fnname, + const QgsExpression::ParameterList& params, + const QStringList& groups, + const QString& helpText = QString(), + bool usesGeometry = false, + const QSet& referencedColumns = QSet(), + bool lazyEval = false, + bool handlesNull = false, + bool isContextual = false ); + virtual ~Function(); /** The name of the function. */ @@ -357,8 +383,23 @@ class QgsExpression */ bool isContextual() const; - /** The group the function belongs to. */ + /** Returns true if the function is deprecated and should not be presented as a valid option + * to users in expression builders. + * @note added in QGIS 3.0 + */ + virtual bool isDeprecated() const; + + /** Returns the first group which the function belongs to. + * @note consider using groups() instead, as some functions naturally belong in multiple groups + */ QString group() const; + + /** Returns a list of the groups the function belongs to. + * @note added in QGIS 3.0 + * @see group() + */ + QStringList groups() const; + /** The help text for the function. */ const QString helpText() const; diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp index 36f6133753a9..d6516a202d38 100644 --- a/src/core/qgsexpression.cpp +++ b/src/core/qgsexpression.cpp @@ -3403,7 +3403,7 @@ const QList& QgsExpression::Functions() << new StaticFunction( "sqrt", ParameterList() << Parameter( "value" ), fcnSqrt, "Math" ) << new StaticFunction( "radians", ParameterList() << Parameter( "degrees" ), fcnRadians, "Math" ) << new StaticFunction( "degrees", ParameterList() << Parameter( "radians" ), fcnDegrees, "Math" ) - << new StaticFunction( "azimuth", ParameterList() << Parameter( "point_a" ) << Parameter( "point_b" ), fcnAzimuth, "Math" ) + << new StaticFunction( "azimuth", ParameterList() << Parameter( "point_a" ) << Parameter( "point_b" ), fcnAzimuth, QStringList() << "Math" << "GeometryGroup" ) << new StaticFunction( "project", ParameterList() << Parameter( "point" ) << Parameter( "distance" ) << Parameter( "bearing" ), fcnProject, "GeometryGroup" ) << new StaticFunction( "abs", ParameterList() << Parameter( "value" ), fcnAbs, "Math" ) << new StaticFunction( "cos", ParameterList() << Parameter( "angle" ), fcnCos, "Math" ) @@ -3428,13 +3428,13 @@ const QList& QgsExpression::Functions() << new StaticFunction( "floor", 1, fcnFloor, "Math" ) << new StaticFunction( "ceil", 1, fcnCeil, "Math" ) << new StaticFunction( "pi", 0, fcnPi, "Math", QString(), false, QSet(), false, QStringList() << "$pi" ) - << new StaticFunction( "to_int", 1, fcnToInt, "Conversions", QString(), false, QSet(), false, QStringList() << "toint" ) - << new StaticFunction( "to_real", 1, fcnToReal, "Conversions", QString(), false, QSet(), false, QStringList() << "toreal" ) - << new StaticFunction( "to_string", 1, fcnToString, "Conversions", QString(), false, QSet(), false, QStringList() << "tostring" ) - << new StaticFunction( "to_datetime", 1, fcnToDateTime, "Conversions", QString(), false, QSet(), false, QStringList() << "todatetime" ) - << new StaticFunction( "to_date", 1, fcnToDate, "Conversions", QString(), false, QSet(), false, QStringList() << "todate" ) - << new StaticFunction( "to_time", 1, fcnToTime, "Conversions", QString(), false, QSet(), false, QStringList() << "totime" ) - << new StaticFunction( "to_interval", 1, fcnToInterval, "Conversions", QString(), false, QSet(), false, QStringList() << "tointerval" ) + << new StaticFunction( "to_int", ParameterList() << Parameter( "value" ), fcnToInt, "Conversions", QString(), false, QSet(), false, QStringList() << "toint" ) + << new StaticFunction( "to_real", ParameterList() << Parameter( "value" ), fcnToReal, "Conversions", QString(), false, QSet(), false, QStringList() << "toreal" ) + << new StaticFunction( "to_string", ParameterList() << Parameter( "value" ), fcnToString, QStringList() << "Conversions" << "String", QString(), false, QSet(), false, QStringList() << "tostring" ) + << new StaticFunction( "to_datetime", ParameterList() << Parameter( "value" ), fcnToDateTime, QStringList() << "Conversions" << "Date and Time", QString(), false, QSet(), false, QStringList() << "todatetime" ) + << new StaticFunction( "to_date", ParameterList() << Parameter( "value" ), fcnToDate, QStringList() << "Conversions" << "Date and Time", QString(), false, QSet(), false, QStringList() << "todate" ) + << new StaticFunction( "to_time", ParameterList() << Parameter( "value" ), fcnToTime, QStringList() << "Conversions" << "Date and Time", QString(), false, QSet(), false, QStringList() << "totime" ) + << new StaticFunction( "to_interval", ParameterList() << Parameter( "value" ), fcnToInterval, QStringList() << "Conversions" << "Date and Time", QString(), false, QSet(), false, QStringList() << "tointerval" ) << new StaticFunction( "coalesce", -1, fcnCoalesce, "Conditionals", QString(), false, QSet(), false, QStringList(), true ) << new StaticFunction( "if", 3, fcnIf, "Conditionals", QString(), False, QSet(), true ) << new StaticFunction( "aggregate", ParameterList() << Parameter( "layer" ) << Parameter( "aggregate" ) << Parameter( "expression" ) @@ -3462,7 +3462,7 @@ const QList& QgsExpression::Functions() << new StaticFunction( "collect", aggParams, fcnAggregateCollectGeometry, "Aggregates", QString(), False, QSet(), true ) << new StaticFunction( "concatenate", aggParams << Parameter( "concatenator", true ), fcnAggregateStringConcat, "Aggregates", QString(), False, QSet(), true ) - << new StaticFunction( "regexp_match", 2, fcnRegexpMatch, "Conditionals" ) + << new StaticFunction( "regexp_match", ParameterList() << Parameter( "string" ) << Parameter( "regex" ), fcnRegexpMatch, QStringList() << "Conditionals" << "String" ) << new StaticFunction( "now", 0, fcnNow, "Date and Time", QString(), false, QSet(), false, QStringList() << "$now" ) << new StaticFunction( "age", 2, fcnAge, "Date and Time" ) << new StaticFunction( "year", 1, fcnYear, "Date and Time" ) @@ -3483,7 +3483,7 @@ const QList& QgsExpression::Functions() << new StaticFunction( "soundex", 1, fcnSoundex, "Fuzzy Matching" ) << new StaticFunction( "char", 1, fcnChar, "String" ) << new StaticFunction( "wordwrap", ParameterList() << Parameter( "text" ) << Parameter( "length" ) << Parameter( "delimiter", true, "" ), fcnWordwrap, "String" ) - << new StaticFunction( "length", 1, fcnLength, "String" ) + << new StaticFunction( "length", ParameterList() << Parameter( "text", true, "" ), fcnLength, QStringList() << "String" << "GeometryGroup" ) << new StaticFunction( "replace", 3, fcnReplace, "String" ) << new StaticFunction( "regexp_replace", 3, fcnRegexpReplace, "String" ) << new StaticFunction( "regexp_substr", 2, fcnRegexpSubstr, "String" ) @@ -3496,7 +3496,7 @@ const QList& QgsExpression::Functions() << new StaticFunction( "lpad", 3, fcnLPad, "String" ) << new StaticFunction( "format", -1, fcnFormatString, "String" ) << new StaticFunction( "format_number", 2, fcnFormatNumber, "String" ) - << new StaticFunction( "format_date", 2, fcnFormatDate, "String" ) + << new StaticFunction( "format_date", ParameterList() << Parameter( "date" ) << Parameter( "format" ), fcnFormatDate, QStringList() << "String" << "Date and Time" ) << new StaticFunction( "color_rgb", 3, fcnColorRgb, "Color" ) << new StaticFunction( "color_rgba", 4, fncColorRgba, "Color" ) << new StaticFunction( "ramp_color", 2, fcnRampColor, "Color" ) diff --git a/src/core/qgsexpression.h b/src/core/qgsexpression.h index 2216afd0b6d0..0e3f07426197 100644 --- a/src/core/qgsexpression.h +++ b/src/core/qgsexpression.h @@ -458,7 +458,31 @@ class CORE_EXPORT QgsExpression : mName( fnname ) , mParams( params ) , mUsesGeometry( usesGeometry ) - , mGroup( group ) + , mGroups( group.isEmpty() ? QStringList() : QStringList() << group ) + , mHelpText( helpText ) + , mReferencedColumns( referencedColumns ) + , mLazyEval( lazyEval ) + , mHandlesNull( handlesNull ) + , mIsContextual( isContextual ) + { + } + + /** Constructor for function which uses unnamed parameters and group list + * @note added in QGIS 3.0 + */ + Function( const QString& fnname, + int params, + const QStringList& groups, + const QString& helpText = QString(), + bool usesGeometry = false, + const QSet& referencedColumns = QSet(), + bool lazyEval = false, + bool handlesNull = false, + bool isContextual = false ) + : mName( fnname ) + , mParams( params ) + , mUsesGeometry( usesGeometry ) + , mGroups( groups ) , mHelpText( helpText ) , mReferencedColumns( referencedColumns ) , mLazyEval( lazyEval ) @@ -483,7 +507,31 @@ class CORE_EXPORT QgsExpression , mParams( 0 ) , mParameterList( params ) , mUsesGeometry( usesGeometry ) - , mGroup( group ) + , mGroups( group.isEmpty() ? QStringList() : QStringList() << group ) + , mHelpText( helpText ) + , mReferencedColumns( referencedColumns ) + , mLazyEval( lazyEval ) + , mHandlesNull( handlesNull ) + , mIsContextual( isContextual ) + {} + + /** Constructor for function which uses named parameter list and group list. + * @note added in QGIS 3.0 + */ + Function( const QString& fnname, + const ParameterList& params, + const QStringList& groups, + const QString& helpText = QString(), + bool usesGeometry = false, + const QSet& referencedColumns = QSet(), + bool lazyEval = false, + bool handlesNull = false, + bool isContextual = false ) + : mName( fnname ) + , mParams( 0 ) + , mParameterList( params ) + , mUsesGeometry( usesGeometry ) + , mGroups( groups ) , mHelpText( helpText ) , mReferencedColumns( referencedColumns ) , mLazyEval( lazyEval ) @@ -541,8 +589,22 @@ class CORE_EXPORT QgsExpression */ bool isContextual() const { return mIsContextual; } - /** The group the function belongs to. */ - QString group() const { return mGroup; } + /** Returns true if the function is deprecated and should not be presented as a valid option + * to users in expression builders. + * @note added in QGIS 3.0 + */ + virtual bool isDeprecated() const { return mGroups.isEmpty() ? false : mGroups.contains( "deprecated" ); } + + /** Returns the first group which the function belongs to. + * @note consider using groups() instead, as some functions naturally belong in multiple groups + */ + QString group() const { return mGroups.isEmpty() ? QString() : mGroups.at( 0 ); } + + /** Returns a list of the groups the function belongs to. + * @note added in QGIS 3.0 + * @see group() + */ + QStringList groups() const { return mGroups; } /** The help text for the function. */ const QString helpText() const { return mHelpText.isEmpty() ? QgsExpression::helpText( mName ) : mHelpText; } @@ -564,7 +626,7 @@ class CORE_EXPORT QgsExpression int mParams; ParameterList mParameterList; bool mUsesGeometry; - QString mGroup; + QStringList mGroups; QString mHelpText; QSet mReferencedColumns; bool mLazyEval; @@ -614,6 +676,24 @@ class CORE_EXPORT QgsExpression , mAliases( aliases ) {} + /** Static function for evaluation against a QgsExpressionContext, using a named list of parameter values and list + * of groups. + */ + StaticFunction( const QString& fnname, + const ParameterList& params, + FcnEval fcn, + const QStringList& groups, + const QString& helpText = QString(), + bool usesGeometry = false, + const QSet& referencedColumns = QSet(), + bool lazyEval = false, + const QStringList& aliases = QStringList(), + bool handlesNull = false ) + : Function( fnname, params, groups, helpText, usesGeometry, referencedColumns, lazyEval, handlesNull ) + , mFnc( fcn ) + , mAliases( aliases ) + {} + virtual ~StaticFunction() {} /** Returns result of evaluating the function. diff --git a/src/gui/qgsexpressionbuilderwidget.cpp b/src/gui/qgsexpressionbuilderwidget.cpp index 8b3e35df97e8..cfbc54f62766 100644 --- a/src/gui/qgsexpressionbuilderwidget.cpp +++ b/src/gui/qgsexpressionbuilderwidget.cpp @@ -479,7 +479,7 @@ void QgsExpressionBuilderWidget::updateFunctionTree() QString name = func->name(); if ( name.startsWith( '_' ) ) // do not display private functions continue; - if ( func->group() == "deprecated" ) // don't show deprecated functions + if ( func->isDeprecated() ) // don't show deprecated functions continue; if ( func->isContextual() ) { @@ -491,7 +491,7 @@ void QgsExpressionBuilderWidget::updateFunctionTree() name += '('; else if ( !name.startsWith( '$' ) ) name += "()"; - registerItem( func->group(), func->name(), ' ' + name + ' ', func->helpText() ); + registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() ); } loadExpressionContext(); @@ -614,7 +614,15 @@ void QgsExpressionBuilderWidget::loadExpressionContext() continue; if ( func->params() != 0 ) name += '('; - registerItem( func->group(), func->name(), ' ' + name + ' ', func->helpText() ); + registerItemForAllGroups( func->groups(), func->name(), ' ' + name + ' ', func->helpText() ); + } +} + +void QgsExpressionBuilderWidget::registerItemForAllGroups( const QStringList& groups, const QString& label, const QString& expressionText, const QString& helpText, QgsExpressionItem::ItemType type, bool highlightedItem, int sortOrder ) +{ + Q_FOREACH ( const QString& group, groups ) + { + registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder ); } } diff --git a/src/gui/qgsexpressionbuilderwidget.h b/src/gui/qgsexpressionbuilderwidget.h index 40be6f978077..453abb7d7917 100644 --- a/src/gui/qgsexpressionbuilderwidget.h +++ b/src/gui/qgsexpressionbuilderwidget.h @@ -281,6 +281,20 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp void loadExpressionContext(); + /** Registers a node item for the expression builder, adding multiple items when the function exists in multiple groups + * @param groups The groups the item will be show in the tree view. If a group doesn't exist it will be created. + * @param label The label that is show to the user for the item in the tree. + * @param expressionText The text that is inserted into the expression area when the user double clicks on the item. + * @param helpText The help text that the user will see when item is selected. + * @param type The type of the expression item. + * @param highlightedItem set to true to make the item highlighted, which inserts a bold copy of the item at the top level + * @param sortOrder sort ranking for item + */ + void registerItemForAllGroups( const QStringList& groups, const QString& label, const QString& expressionText, + const QString& helpText = "", + QgsExpressionItem::ItemType type = QgsExpressionItem::ExpressionNode, + bool highlightedItem = false, int sortOrder = 1 ); + bool mAutoSave; QString mFunctionsPath; QgsVectorLayer *mLayer;