diff --git a/python/gui/qgsexpressionbuilderdialog.sip b/python/gui/qgsexpressionbuilderdialog.sip index d5eede96e9d7..2aa1db0e419a 100644 --- a/python/gui/qgsexpressionbuilderdialog.sip +++ b/python/gui/qgsexpressionbuilderdialog.sip @@ -60,6 +60,33 @@ The builder widget that is used by the dialog void setGeomCalculator( const QgsDistanceArea &da ); %Docstring Sets geometry calculator used in distance/area calculations. +%End + + bool allowEvalErrors() const; +%Docstring + Allow accepting invalid expressions. This can be useful when we are not able to + provide an expression context of which we are sure it's completely populated. + +.. versionadded:: 3.0 + :rtype: bool +%End + + void setAllowEvalErrors( bool allowEvalErrors ); +%Docstring + Allow accepting expressions with evaluation errors. This can be useful when we are not able to + provide an expression context of which we are sure it's completely populated. + +.. versionadded:: 3.0 +%End + + signals: + + void allowEvalErrorsChanged(); +%Docstring + Allow accepting expressions with evaluation errors. This can be useful when we are not able to + provide an expression context of which we are sure it's completely populated. + +.. versionadded:: 3.0 %End protected: diff --git a/python/gui/qgsexpressionbuilderwidget.sip b/python/gui/qgsexpressionbuilderwidget.sip index 6f17c55ed930..924cec74aecd 100644 --- a/python/gui/qgsexpressionbuilderwidget.sip +++ b/python/gui/qgsexpressionbuilderwidget.sip @@ -260,6 +260,24 @@ Sets the expression string for the widget .. versionadded:: 3.0 %End + bool evalError() const; +%Docstring + Will be set to true if the current expression text reported an eval error + with the context. + +.. versionadded:: 3.0 + :rtype: bool +%End + + bool parserError() const; +%Docstring + Will be set to true if the current expression text reports a parser error + with the context. + +.. versionadded:: 3.0 + :rtype: bool +%End + public slots: void loadSampleValues(); @@ -294,6 +312,22 @@ Sets the expression string for the widget \param isValid Is true if the expression the user has typed is valid. %End + void evalErrorChanged(); +%Docstring + Will be set to true if the current expression text reported an eval error + with the context. + +.. versionadded:: 3.0 +%End + + void parserErrorChanged(); +%Docstring + Will be set to true if the current expression text reported a parser error + with the context. + +.. versionadded:: 3.0 +%End + protected: virtual void showEvent( QShowEvent *e ); diff --git a/python/gui/qgsfieldexpressionwidget.sip b/python/gui/qgsfieldexpressionwidget.sip index d3e4a1da321e..b993738d256e 100644 --- a/python/gui/qgsfieldexpressionwidget.sip +++ b/python/gui/qgsfieldexpressionwidget.sip @@ -123,6 +123,23 @@ set the geometry calculator used in the expression dialog \param generator A QgsExpressionContextGenerator class that will be used to create an expression context when required. .. versionadded:: 3.0 +%End + + bool allowEvalErrors() const; +%Docstring + Allow accepting expressions with evaluation errors. This can be useful when we are not able to + provide an expression context of which we are sure it's completely populated. + +.. versionadded:: 3.0 + :rtype: bool +%End + + void setAllowEvalErrors( bool allowEvalErrors ); +%Docstring + Allow accepting expressions with evaluation errors. This can be useful when we are not able to + provide an expression context of which we are sure it's completely populated. + +.. versionadded:: 3.0 %End signals: @@ -134,6 +151,14 @@ the signal is emitted when the currently selected field changes void fieldChanged( const QString &fieldName, bool isValid ); %Docstring fieldChanged signal with indication of the validity of the expression +%End + + void allowEvalErrorsChanged(); +%Docstring + Allow accepting expressions with evaluation errors. This can be useful when we are not able to + provide an expression context of which we are sure it's completely populated. + +.. versionadded:: 3.0 %End public slots: diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 717e32ce8b23..b479eacc1594 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -3452,37 +3452,34 @@ static QVariant fcnRepresentValue( const QVariantList &values, const QgsExpressi else if ( values.size() == 2 ) fieldName = QgsExpressionUtils::getStringValue( values.at( 1 ), parent ); } + QVariant value = values.at( 0 ); - QgsVectorLayer *layer = QgsExpressionUtils::getVectorLayer( context->variable( "layer" ), parent ); + const QgsFields fields = context->fields(); + int fieldIndex = fields.lookupField( fieldName ); - if ( layer ) + if ( fieldIndex == -1 ) + { + parent->setEvalErrorString( QCoreApplication::translate( "expression", "%1: Field not found %2" ).arg( QStringLiteral( "represent_value" ), fieldName ) ); + } + else { - const QgsFields fields = layer->fields(); - int index = fields.lookupField( fieldName ); + QgsVectorLayer *layer = QgsExpressionUtils::getVectorLayer( context->variable( "layer" ), parent ); + const QgsEditorWidgetSetup setup = fields.at( fieldIndex ).editorWidgetSetup(); + const QgsFieldFormatter *formatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() ); + + const QString cacheKey = QStringLiteral( "repvalfcn:%1:%2" ).arg( layer ? layer->id() : QStringLiteral( "[None]" ), fieldName ); - if ( index == -1 ) + QVariant cache; + if ( !context->hasCachedValue( cacheKey ) ) { - parent->setEvalErrorString( QCoreApplication::translate( "expression", "%1: Field not found %2" ).arg( QStringLiteral( "represent_value" ), fieldName ) ); + cache = formatter->createCache( layer, fieldIndex, setup.config() ); + context->setCachedValue( cacheKey, cache ); } else - { - QgsEditorWidgetSetup setup = layer->editorWidgetSetup( index ); - QgsFieldFormatter *formatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() ); + cache = context->cachedValue( cacheKey ); - QString cacheKey = QStringLiteral( "repvalfcn:%1:%2" ).arg( layer->id(), fieldName ); - - QVariant cache; - if ( !context->hasCachedValue( cacheKey ) ) - { - cache = formatter->createCache( layer, index, setup.config() ); - context->setCachedValue( cacheKey, cache ); - } - else - cache = context->cachedValue( cacheKey ); - - result = formatter->representValue( layer, index, setup.config(), cache, value ); - } + result = formatter->representValue( layer, fieldIndex, setup.config(), cache, value ); } return result; diff --git a/src/gui/qgsexpressionbuilderdialog.cpp b/src/gui/qgsexpressionbuilderdialog.cpp index 7898211b5776..b11344c1f5b4 100644 --- a/src/gui/qgsexpressionbuilderdialog.cpp +++ b/src/gui/qgsexpressionbuilderdialog.cpp @@ -25,8 +25,8 @@ QgsExpressionBuilderDialog::QgsExpressionBuilderDialog( QgsVectorLayer *layer, c setupUi( this ); QgsGui::instance()->enableAutoGeometryRestore( this ); - QPushButton *okButton = buttonBox->button( QDialogButtonBox::Ok ); - connect( builder, &QgsExpressionBuilderWidget::expressionParsed, okButton, &QWidget::setEnabled ); + connect( builder, &QgsExpressionBuilderWidget::parserErrorChanged, this, &QgsExpressionBuilderDialog::syncOkButtonEnabledState ); + connect( builder, &QgsExpressionBuilderWidget::evalErrorChanged, this, &QgsExpressionBuilderDialog::syncOkButtonEnabledState ); builder->setExpressionContext( context ); builder->setLayer( layer ); @@ -34,7 +34,6 @@ QgsExpressionBuilderDialog::QgsExpressionBuilderDialog( QgsVectorLayer *layer, c builder->loadFieldNames(); builder->loadRecent( mRecentKey ); - connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsExpressionBuilderDialog::showHelp ); } @@ -80,7 +79,34 @@ void QgsExpressionBuilderDialog::setGeomCalculator( const QgsDistanceArea &da ) builder->setGeomCalculator( da ); } +bool QgsExpressionBuilderDialog::allowEvalErrors() const +{ + return mAllowEvalErrors; +} + +void QgsExpressionBuilderDialog::setAllowEvalErrors( bool allowEvalErrors ) +{ + if ( allowEvalErrors == mAllowEvalErrors ) + return; + + mAllowEvalErrors = allowEvalErrors; + syncOkButtonEnabledState(); + emit allowEvalErrorsChanged(); +} + void QgsExpressionBuilderDialog::showHelp() { QgsHelp::openHelp( QStringLiteral( "working_with_vector/expression.html" ) ); } + +void QgsExpressionBuilderDialog::syncOkButtonEnabledState() +{ + QPushButton *okButton = buttonBox->button( QDialogButtonBox::Ok ); + + if ( builder->parserError() ) + okButton->setEnabled( false ); + else if ( !builder->evalError() || mAllowEvalErrors ) + okButton->setEnabled( true ); + else + okButton->setEnabled( true ); +} diff --git a/src/gui/qgsexpressionbuilderdialog.h b/src/gui/qgsexpressionbuilderdialog.h index 220329e82fdc..b8fea7e793f4 100644 --- a/src/gui/qgsexpressionbuilderdialog.h +++ b/src/gui/qgsexpressionbuilderdialog.h @@ -31,6 +31,8 @@ class GUI_EXPORT QgsExpressionBuilderDialog : public QDialog, private Ui::QgsExp { Q_OBJECT + Q_PROPERTY( bool allowEvalErrors READ allowEvalErrors WRITE setAllowEvalErrors NOTIFY allowEvalErrorsChanged ) + public: QgsExpressionBuilderDialog( QgsVectorLayer *layer, const QString &startText = QString(), @@ -65,6 +67,32 @@ class GUI_EXPORT QgsExpressionBuilderDialog : public QDialog, private Ui::QgsExp //! Sets geometry calculator used in distance/area calculations. void setGeomCalculator( const QgsDistanceArea &da ); + /** + * Allow accepting invalid expressions. This can be useful when we are not able to + * provide an expression context of which we are sure it's completely populated. + * + * \since QGIS 3.0 + */ + bool allowEvalErrors() const; + + /** + * Allow accepting expressions with evaluation errors. This can be useful when we are not able to + * provide an expression context of which we are sure it's completely populated. + * + * \since QGIS 3.0 + */ + void setAllowEvalErrors( bool allowEvalErrors ); + + signals: + + /** + * Allow accepting expressions with evaluation errors. This can be useful when we are not able to + * provide an expression context of which we are sure it's completely populated. + * + * \since QGIS 3.0 + */ + void allowEvalErrorsChanged(); + protected: /** @@ -79,9 +107,11 @@ class GUI_EXPORT QgsExpressionBuilderDialog : public QDialog, private Ui::QgsExp private: QString mRecentKey; + bool mAllowEvalErrors = false; private slots: void showHelp(); + void syncOkButtonEnabledState(); }; diff --git a/src/gui/qgsexpressionbuilderwidget.cpp b/src/gui/qgsexpressionbuilderwidget.cpp index 8436c157343d..feeb2fcf347b 100644 --- a/src/gui/qgsexpressionbuilderwidget.cpp +++ b/src/gui/qgsexpressionbuilderwidget.cpp @@ -578,6 +578,8 @@ void QgsExpressionBuilderWidget::txtExpressionString_textChanged() txtExpressionString->setToolTip( QLatin1String( "" ) ); lblPreview->setToolTip( QLatin1String( "" ) ); emit expressionParsed( false ); + setParserError( true ); + setEvalError( true ); return; } @@ -614,14 +616,18 @@ void QgsExpressionBuilderWidget::txtExpressionString_textChanged() txtExpressionString->setToolTip( tooltip ); lblPreview->setToolTip( tooltip ); emit expressionParsed( false ); + setParserError( exp.hasParserError() ); + setEvalError( exp.hasEvalError() ); return; } else { - lblPreview->setStyleSheet( QLatin1String( "" ) ); - txtExpressionString->setToolTip( QLatin1String( "" ) ); - lblPreview->setToolTip( QLatin1String( "" ) ); + lblPreview->setStyleSheet( QString() ); + txtExpressionString->setToolTip( QString() ); + lblPreview->setToolTip( QString() ); emit expressionParsed( true ); + setParserError( false ); + setEvalError( false ); } } @@ -672,6 +678,34 @@ QString QgsExpressionBuilderWidget::formatLayerHelp( const QgsMapLayer *layer ) return text; } +bool QgsExpressionBuilderWidget::parserError() const +{ + return mParserError; +} + +void QgsExpressionBuilderWidget::setParserError( bool parserError ) +{ + if ( parserError == mParserError ) + return; + + mParserError = parserError; + emit parserErrorChanged(); +} + +bool QgsExpressionBuilderWidget::evalError() const +{ + return mEvalError; +} + +void QgsExpressionBuilderWidget::setEvalError( bool evalError ) +{ + if ( evalError == mEvalError ) + return; + + mEvalError = evalError; + emit evalErrorChanged(); +} + QStandardItemModel *QgsExpressionBuilderWidget::model() { return mModel; diff --git a/src/gui/qgsexpressionbuilderwidget.h b/src/gui/qgsexpressionbuilderwidget.h index 32dd182c0e3d..5ad1fa2f62ca 100644 --- a/src/gui/qgsexpressionbuilderwidget.h +++ b/src/gui/qgsexpressionbuilderwidget.h @@ -266,6 +266,22 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp */ void setProject( QgsProject *project ); + /** + * Will be set to true if the current expression text reported an eval error + * with the context. + * + * \since QGIS 3.0 + */ + bool evalError() const; + + /** + * Will be set to true if the current expression text reports a parser error + * with the context. + * + * \since QGIS 3.0 + */ + bool parserError() const; + public slots: /** @@ -310,12 +326,28 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp /** * Emitted when the user changes the expression in the widget. - * Users of this widget should connect to this signal to decide if to let the user - * continue. - * \param isValid Is true if the expression the user has typed is valid. - */ + * Users of this widget should connect to this signal to decide if to let the user + * continue. + * \param isValid Is true if the expression the user has typed is valid. + */ void expressionParsed( bool isValid ); + /** + * Will be set to true if the current expression text reported an eval error + * with the context. + * + * \since QGIS 3.0 + */ + void evalErrorChanged(); + + /** + * Will be set to true if the current expression text reported a parser error + * with the context. + * + * \since QGIS 3.0 + */ + void parserErrorChanged(); + protected: void showEvent( QShowEvent *e ) override; @@ -359,6 +391,22 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp */ QString formatLayerHelp( const QgsMapLayer *layer ) const; + /** + * Will be set to true if the current expression text reported an eval error + * with the context. + * + * \since QGIS 3.0 + */ + void setEvalError( bool evalError ); + + /** + * Will be set to true if the current expression text reports a parser error + * with the context. + * + * \since QGIS 3.0 + */ + void setParserError( bool parserError ); + bool mAutoSave = true; QString mFunctionsPath; QgsVectorLayer *mLayer = nullptr; @@ -374,6 +422,8 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp QMap mFieldValues; QgsExpressionContext mExpressionContext; QPointer< QgsProject > mProject; + bool mEvalError = true; + bool mParserError = true; }; // clazy:excludeall=qstring-allocations diff --git a/src/gui/qgsfieldexpressionwidget.cpp b/src/gui/qgsfieldexpressionwidget.cpp index 1510c7424ac5..2bf556e7b0c7 100644 --- a/src/gui/qgsfieldexpressionwidget.cpp +++ b/src/gui/qgsfieldexpressionwidget.cpp @@ -216,6 +216,7 @@ void QgsFieldExpressionWidget::editExpression() dlg.setGeomCalculator( *mDa ); } dlg.setWindowTitle( mExpressionDialogTitle ); + dlg.setAllowEvalErrors( mAllowEvalErrors ); if ( dlg.exec() ) { @@ -265,6 +266,20 @@ void QgsFieldExpressionWidget::afterResetModel() mCombo->lineEdit()->setText( mBackupExpression ); } +bool QgsFieldExpressionWidget::allowEvalErrors() const +{ + return mAllowEvalErrors; +} + +void QgsFieldExpressionWidget::setAllowEvalErrors( bool allowEvalErrors ) +{ + if ( allowEvalErrors == mAllowEvalErrors ) + return; + + mAllowEvalErrors = allowEvalErrors; + emit allowEvalErrorsChanged(); +} + void QgsFieldExpressionWidget::currentFieldChanged() { updateLineEditStyle(); diff --git a/src/gui/qgsfieldexpressionwidget.h b/src/gui/qgsfieldexpressionwidget.h index 2aca72387ab0..294c7b19855f 100644 --- a/src/gui/qgsfieldexpressionwidget.h +++ b/src/gui/qgsfieldexpressionwidget.h @@ -47,8 +47,10 @@ class GUI_EXPORT QgsFieldExpressionWidget : public QWidget { Q_OBJECT Q_PROPERTY( QString expressionDialogTitle READ expressionDialogTitle WRITE setExpressionDialogTitle ) - Q_FLAGS( QgsFieldProxyModel::Filters ) Q_PROPERTY( QgsFieldProxyModel::Filters filters READ filters WRITE setFilters ) + Q_PROPERTY( bool allowEvalErrors READ allowEvalErrors WRITE setAllowEvalErrors NOTIFY allowEvalErrorsChanged ) + + Q_FLAGS( QgsFieldProxyModel::Filters ) public: @@ -130,6 +132,22 @@ class GUI_EXPORT QgsFieldExpressionWidget : public QWidget */ void registerExpressionContextGenerator( const QgsExpressionContextGenerator *generator ); + /** + * Allow accepting expressions with evaluation errors. This can be useful when we are not able to + * provide an expression context of which we are sure it's completely populated. + * + * \since QGIS 3.0 + */ + bool allowEvalErrors() const; + + /** + * Allow accepting expressions with evaluation errors. This can be useful when we are not able to + * provide an expression context of which we are sure it's completely populated. + * + * \since QGIS 3.0 + */ + void setAllowEvalErrors( bool allowEvalErrors ); + signals: //! the signal is emitted when the currently selected field changes void fieldChanged( const QString &fieldName ); @@ -137,6 +155,14 @@ class GUI_EXPORT QgsFieldExpressionWidget : public QWidget //! fieldChanged signal with indication of the validity of the expression void fieldChanged( const QString &fieldName, bool isValid ); + /** + * Allow accepting expressions with evaluation errors. This can be useful when we are not able to + * provide an expression context of which we are sure it's completely populated. + * + * \since QGIS 3.0 + */ + void allowEvalErrorsChanged(); + public slots: /** @@ -198,6 +224,7 @@ class GUI_EXPORT QgsFieldExpressionWidget : public QWidget QgsExpressionContext mExpressionContext; const QgsExpressionContextGenerator *mExpressionContextGenerator = nullptr; QString mBackupExpression; + bool mAllowEvalErrors = false; friend class TestQgsFieldExpressionWidget; };