From 9f3df3592466ac2163eeeab2d2770fdb2b1fb94f Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Sat, 3 Feb 2024 13:35:34 +0700 Subject: [PATCH 1/2] [attribute form] Insure that a field features multiple times in a feature form has its constraint properly reflected --- .../core/qgseditorwidgetwrapper.sip.in | 10 ++++++ .../core/qgseditorwidgetwrapper.sip.in | 10 ++++++ .../core/qgseditorwidgetwrapper.cpp | 9 +++++ .../core/qgseditorwidgetwrapper.h | 8 +++++ src/gui/qgsattributeform.cpp | 35 ++++++++++-------- tests/src/gui/testqgsattributeform.cpp | 36 +++++++++++++++++++ 6 files changed, 94 insertions(+), 14 deletions(-) diff --git a/python/PyQt6/gui/auto_generated/editorwidgets/core/qgseditorwidgetwrapper.sip.in b/python/PyQt6/gui/auto_generated/editorwidgets/core/qgseditorwidgetwrapper.sip.in index bd89941bb0ac..9580902de9ab 100644 --- a/python/PyQt6/gui/auto_generated/editorwidgets/core/qgseditorwidgetwrapper.sip.in +++ b/python/PyQt6/gui/auto_generated/editorwidgets/core/qgseditorwidgetwrapper.sip.in @@ -162,6 +162,16 @@ Update constraint on a feature coming from a specific layer. based constraints. .. versionadded:: 3.0 +%End + + void updateConstraint( QgsEditorWidgetWrapper::ConstraintResult constraintResult, const QString &constraintFailureReason ); +%Docstring +Update constraint manually by providing the constraint result value and failure reason(s). + +:param constraintResult: the constraint result value +:param constraintFailureReason: the constraint failure reason(s) (blank is the result passes) + +.. versionadded:: 3.36 %End bool isValidConstraint() const; diff --git a/python/gui/auto_generated/editorwidgets/core/qgseditorwidgetwrapper.sip.in b/python/gui/auto_generated/editorwidgets/core/qgseditorwidgetwrapper.sip.in index bd89941bb0ac..9580902de9ab 100644 --- a/python/gui/auto_generated/editorwidgets/core/qgseditorwidgetwrapper.sip.in +++ b/python/gui/auto_generated/editorwidgets/core/qgseditorwidgetwrapper.sip.in @@ -162,6 +162,16 @@ Update constraint on a feature coming from a specific layer. based constraints. .. versionadded:: 3.0 +%End + + void updateConstraint( QgsEditorWidgetWrapper::ConstraintResult constraintResult, const QString &constraintFailureReason ); +%Docstring +Update constraint manually by providing the constraint result value and failure reason(s). + +:param constraintResult: the constraint result value +:param constraintFailureReason: the constraint failure reason(s) (blank is the result passes) + +.. versionadded:: 3.36 %End bool isValidConstraint() const; diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index d124bdf7db47..7556b059366e 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -266,6 +266,15 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsVectorLayer *layer, int } } +void QgsEditorWidgetWrapper::updateConstraint( QgsEditorWidgetWrapper::ConstraintResult constraintResult, const QString &constraintFailureReason ) +{ + mValidConstraint = constraintResult == ConstraintResultPass; + mIsBlockingCommit = constraintResult == ConstraintResultFailHard; + mConstraintFailureReason = constraintResult != ConstraintResultPass ? constraintFailureReason : QString(); + mConstraintResult = constraintResult; + updateConstraintWidgetStatus(); +} + bool QgsEditorWidgetWrapper::isValidConstraint() const { return mValidConstraint; diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h index 0f5a8c876f9c..a74a43211ddf 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h @@ -175,6 +175,14 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper */ void updateConstraint( const QgsVectorLayer *layer, int index, const QgsFeature &feature, QgsFieldConstraints::ConstraintOrigin constraintOrigin = QgsFieldConstraints::ConstraintOriginNotSet ); + /** + * Update constraint manually by providing the constraint result value and failure reason(s). + * \param constraintResult the constraint result value + * \param constraintFailureReason the constraint failure reason(s) (blank is the result passes) + * \since QGIS 3.36 + */ + void updateConstraint( QgsEditorWidgetWrapper::ConstraintResult constraintResult, const QString &constraintFailureReason ); + /** * Gets the current constraint status. * \returns TRUE if the constraint is valid or if there's no constraint, diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index abd650f239c4..18a844140cad 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -1057,6 +1057,21 @@ void QgsAttributeForm::onAttributeChanged( const QVariant &value, const QVariant break; } + // Update other widgets pointing to the same field, required to happen now to insure + // currentFormValuesFeature() gets the right value when processing constraints + const QList formEditorWidgets = mFormEditorWidgets.values( eww->fieldIdx() ); + for ( QgsAttributeFormEditorWidget *formEditorWidget : formEditorWidgets ) + { + if ( formEditorWidget->editorWidget() == eww ) + continue; + + // formEditorWidget and eww points to the same field, so block signals + // as there is no need to handle valueChanged again for each duplicate + formEditorWidget->editorWidget()->blockSignals( true ); + formEditorWidget->editorWidget()->setValue( value ); + formEditorWidget->editorWidget()->blockSignals( false ); + } + updateConstraints( eww ); // Update dependent fields (only if form is not initializing) @@ -1072,20 +1087,6 @@ void QgsAttributeForm::onAttributeChanged( const QVariant &value, const QVariant updateLabels(); updateEditableState(); - // Update other widgets pointing to the same field - const QList formEditorWidgets = mFormEditorWidgets.values( eww->fieldIdx() ); - for ( QgsAttributeFormEditorWidget *formEditorWidget : formEditorWidgets ) - { - if ( formEditorWidget->editorWidget() == eww ) - continue; - - // formEditorWidget and eww points to the same field, so block signals - // as there is no need to handle valueChanged again for each duplicate - formEditorWidget->editorWidget()->blockSignals( true ); - formEditorWidget->editorWidget()->setValue( value ); - formEditorWidget->editorWidget()->blockSignals( false ); - } - if ( !signalEmitted ) { Q_NOWARN_DEPRECATED_PUSH @@ -1435,7 +1436,13 @@ void QgsAttributeForm::onConstraintStatusChanged( const QString &constraint, const QList formEditorWidgets = mFormEditorWidgets.values( eww->fieldIdx() ); for ( QgsAttributeFormEditorWidget *formEditorWidget : formEditorWidgets ) + { formEditorWidget->setConstraintStatus( constraint, description, err, result ); + if ( formEditorWidget->editorWidget() != eww ) + { + formEditorWidget->editorWidget()->updateConstraint( result, err ); + } + } } QList QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w ) diff --git a/tests/src/gui/testqgsattributeform.cpp b/tests/src/gui/testqgsattributeform.cpp index c691ce246af3..e830b8046ef6 100644 --- a/tests/src/gui/testqgsattributeform.cpp +++ b/tests/src/gui/testqgsattributeform.cpp @@ -61,6 +61,7 @@ class TestQgsAttributeForm : public QObject void testSameFieldSync(); void testZeroDoubles(); void testMinimumWidth(); + void testFieldConstraintDuplicateField(); private: QLabel *constraintsLabel( QgsAttributeForm *form, QgsEditorWidgetWrapper *ww ) @@ -1211,5 +1212,40 @@ void TestQgsAttributeForm::testMinimumWidth() } +void TestQgsAttributeForm::testFieldConstraintDuplicateField() +{ + // make a temporary vector layer + const QString def = QStringLiteral( "Point?field=col0:integer" ); + QgsVectorLayer *layer = new QgsVectorLayer( def, QStringLiteral( "test" ), QStringLiteral( "memory" ) ); + layer->setEditorWidgetSetup( 0, QgsEditorWidgetSetup( QStringLiteral( "Range" ), QVariantMap() ) ); + + // add same field twice so they get synced + QgsEditFormConfig editFormConfig = layer->editFormConfig(); + editFormConfig.clearTabs(); + editFormConfig.invisibleRootContainer()->addChildElement( new QgsAttributeEditorField( "col0", 0, editFormConfig.invisibleRootContainer() ) ); + editFormConfig.invisibleRootContainer()->addChildElement( new QgsAttributeEditorField( "col0", 0, editFormConfig.invisibleRootContainer() ) ); + editFormConfig.setLayout( Qgis::AttributeFormLayout::DragAndDrop ); + layer->setEditFormConfig( editFormConfig ); + + // add a feature to the vector layer + QgsFeature ft( layer->dataProvider()->fields(), 1 ); + ft.setAttribute( QStringLiteral( "col0" ), 1 ); + + // set a not null constraint + layer->setConstraintExpression( 0, QStringLiteral( "col0 > 10" ) ); + + // build a form for this feature + QgsAttributeForm form( layer ); + form.setFeature( ft ); + + const QList formEditorWidgets = form.mFormEditorWidgets.values( 0 ); + QCOMPARE( formEditorWidgets[0]->editorWidget()->constraintResult(), QgsEditorWidgetWrapper::ConstraintResultFailHard ); + QCOMPARE( formEditorWidgets[1]->editorWidget()->constraintResult(), QgsEditorWidgetWrapper::ConstraintResultFailHard ); + + formEditorWidgets[0]->editorWidget()->setValues( 20, QVariantList() ); + QCOMPARE( formEditorWidgets[0]->editorWidget()->constraintResult(), QgsEditorWidgetWrapper::ConstraintResultPass ); + QCOMPARE( formEditorWidgets[1]->editorWidget()->constraintResult(), QgsEditorWidgetWrapper::ConstraintResultPass ); +} + QGSTEST_MAIN( TestQgsAttributeForm ) #include "testqgsattributeform.moc" From 55fb32e9ed6bd627ab3e9a917b33a9623d0ec25e Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Mon, 5 Feb 2024 15:35:37 +0700 Subject: [PATCH 2/2] Update src/gui/qgsattributeform.cpp Co-authored-by: Even Rouault --- src/gui/qgsattributeform.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index 18a844140cad..b4d8d279b2e1 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -1067,9 +1067,7 @@ void QgsAttributeForm::onAttributeChanged( const QVariant &value, const QVariant // formEditorWidget and eww points to the same field, so block signals // as there is no need to handle valueChanged again for each duplicate - formEditorWidget->editorWidget()->blockSignals( true ); - formEditorWidget->editorWidget()->setValue( value ); - formEditorWidget->editorWidget()->blockSignals( false ); + whileBlocking( formEditorWidget->editorWidget() )->setValue( value ); } updateConstraints( eww );