diff --git a/python/gui/auto_generated/qgsattributeeditorcontext.sip.in b/python/gui/auto_generated/qgsattributeeditorcontext.sip.in index 3b6509d88579..96115b3b52cf 100644 --- a/python/gui/auto_generated/qgsattributeeditorcontext.sip.in +++ b/python/gui/auto_generated/qgsattributeeditorcontext.sip.in @@ -31,6 +31,7 @@ showing an embedded form due to relations) { SingleEditMode, AddFeatureMode, + FixAttributeMode, MultiEditMode, SearchMode, AggregateSearchMode, diff --git a/src/app/qgsfixattributedialog.cpp b/src/app/qgsfixattributedialog.cpp index 8ae9a90b5791..d9349030f3f9 100644 --- a/src/app/qgsfixattributedialog.cpp +++ b/src/app/qgsfixattributedialog.cpp @@ -52,7 +52,7 @@ void QgsFixAttributeDialog::init( QgsVectorLayer *layer, const QgsAttributeEdito infoLayout->addWidget( mProgressBar ); QgsFeature feature; mAttributeForm = new QgsAttributeForm( layer, *mCurrentFeature, context, this ); - mAttributeForm->setMode( QgsAttributeEditorContext::SingleEditMode ); + mAttributeForm->setMode( QgsAttributeEditorContext::FixAttributeMode ); mAttributeForm->disconnectButtonBox(); layout()->addWidget( mAttributeForm ); diff --git a/src/core/qgsfeatureid.h b/src/core/qgsfeatureid.h index ccaafb8469fb..0a601e5b9072 100644 --- a/src/core/qgsfeatureid.h +++ b/src/core/qgsfeatureid.h @@ -21,7 +21,10 @@ email : matthias@opengis.ch // feature id (currently 64 bit) -// 64 bit feature ids +/** + * 64 bit feature ids + * negative numbers are used for uncommitted/newly added features + **/ typedef qint64 QgsFeatureId SIP_SKIP; #define FID_NULL std::numeric_limits::min() #define FID_IS_NULL(fid) ( fid == std::numeric_limits::min() ) diff --git a/src/gui/qgsattributeeditorcontext.h b/src/gui/qgsattributeeditorcontext.h index 46c2392e4b31..e1f86b1a3564 100644 --- a/src/gui/qgsattributeeditorcontext.h +++ b/src/gui/qgsattributeeditorcontext.h @@ -49,6 +49,7 @@ class GUI_EXPORT QgsAttributeEditorContext SingleEditMode, //!< Single edit mode, for editing a single feature AddFeatureMode, /*!< Add feature mode, for setting attributes for a new feature. In this mode the dialog will be editable even with an invalid feature and will add a new feature when the form is accepted. */ + FixAttributeMode, //!< Fix feature mode, for modifying the feature attributes without saving. The updated feature is available via `feature()` after `save()` MultiEditMode, //!< Multi edit mode, for editing fields of multiple features at once SearchMode, //!< Form values are used for searching/filtering the layer AggregateSearchMode, //!< Form is in aggregate search mode, show each widget in this mode \since QGIS 3.0 diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index f62aaa8bbf53..da88950bcad5 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -173,6 +173,10 @@ void QgsAttributeForm::setMode( QgsAttributeEditorContext::Mode mode ) w->setMode( QgsAttributeFormWidget::DefaultMode ); break; + case QgsAttributeEditorContext::FixAttributeMode: + w->setMode( QgsAttributeFormWidget::DefaultMode ); + break; + case QgsAttributeEditorContext::MultiEditMode: w->setMode( QgsAttributeFormWidget::MultiEditMode ); break; @@ -216,6 +220,11 @@ void QgsAttributeForm::setMode( QgsAttributeEditorContext::Mode mode ) mSearchButtonBox->setVisible( false ); break; + case QgsAttributeEditorContext::FixAttributeMode: + synchronizeEnabledState(); + mSearchButtonBox->setVisible( false ); + break; + case QgsAttributeEditorContext::MultiEditMode: resetMultiEdit( false ); synchronizeEnabledState(); @@ -278,6 +287,7 @@ void QgsAttributeForm::setFeature( const QgsFeature &feature ) case QgsAttributeEditorContext::SingleEditMode: case QgsAttributeEditorContext::IdentifyMode: case QgsAttributeEditorContext::AddFeatureMode: + case QgsAttributeEditorContext::FixAttributeMode: { resetValues(); @@ -320,7 +330,7 @@ bool QgsAttributeForm::saveEdits() // An add dialog should perform an action by default // and not only if attributes have "changed" - if ( mMode == QgsAttributeEditorContext::AddFeatureMode ) + if ( mMode == QgsAttributeEditorContext::AddFeatureMode || mMode == QgsAttributeEditorContext::FixAttributeMode ) doUpdate = true; QgsAttributes src = mFeature.attributes(); @@ -379,7 +389,11 @@ bool QgsAttributeForm::saveEdits() if ( doUpdate ) { - if ( mMode == QgsAttributeEditorContext::AddFeatureMode ) + if ( mMode == QgsAttributeEditorContext::FixAttributeMode ) + { + mFeature = updatedFeature; + } + else if ( mMode == QgsAttributeEditorContext::AddFeatureMode ) { mFeature.setValid( true ); mLayer->beginEditCommand( mEditCommandMessage ); @@ -735,6 +749,7 @@ bool QgsAttributeForm::save() { case QgsAttributeEditorContext::SingleEditMode: case QgsAttributeEditorContext::IdentifyMode: + case QgsAttributeEditorContext::FixAttributeMode: case QgsAttributeEditorContext::MultiEditMode: if ( !mDirty ) return true; @@ -761,6 +776,7 @@ bool QgsAttributeForm::save() case QgsAttributeEditorContext::SingleEditMode: case QgsAttributeEditorContext::IdentifyMode: case QgsAttributeEditorContext::AddFeatureMode: + case QgsAttributeEditorContext::FixAttributeMode: case QgsAttributeEditorContext::SearchMode: case QgsAttributeEditorContext::AggregateSearchMode: success = saveEdits(); @@ -848,6 +864,7 @@ void QgsAttributeForm::onAttributeChanged( const QVariant &value, const QVariant case QgsAttributeEditorContext::SingleEditMode: case QgsAttributeEditorContext::IdentifyMode: case QgsAttributeEditorContext::AddFeatureMode: + case QgsAttributeEditorContext::FixAttributeMode: { Q_NOWARN_DEPRECATED_PUSH emit attributeChanged( eww->field().name(), value ); @@ -2264,6 +2281,7 @@ void QgsAttributeForm::layerSelectionChanged() case QgsAttributeEditorContext::SingleEditMode: case QgsAttributeEditorContext::IdentifyMode: case QgsAttributeEditorContext::AddFeatureMode: + case QgsAttributeEditorContext::FixAttributeMode: case QgsAttributeEditorContext::SearchMode: case QgsAttributeEditorContext::AggregateSearchMode: break; diff --git a/tests/src/gui/testqgsattributeform.cpp b/tests/src/gui/testqgsattributeform.cpp index 23eafc55efac..6d96505f596c 100644 --- a/tests/src/gui/testqgsattributeform.cpp +++ b/tests/src/gui/testqgsattributeform.cpp @@ -50,6 +50,7 @@ class TestQgsAttributeForm : public QObject void testConstraintsOnJoinedFields(); void testEditableJoin(); void testUpsertOnEdit(); + void testFixAttributeForm(); void testAttributeFormInterface(); void testDefaultValueUpdate(); void testDefaultValueUpdateRecursion(); @@ -878,6 +879,41 @@ void TestQgsAttributeForm::testUpsertOnEdit() delete layerC; } +void TestQgsAttributeForm::testFixAttributeForm() +{ + QString def = QStringLiteral( "Point?field=id:integer&field=col1:integer" ); + QgsVectorLayer *layer = new QgsVectorLayer( def, QStringLiteral( "layer" ), QStringLiteral( "memory" ) ); + + QVERIFY( layer ); + + QgsFeature f( layer->fields() ); + f.setAttribute( 0, 1 ); + f.setAttribute( 1, 681 ); + + QgsAttributeForm form( layer ); + + form.setMode( QgsAttributeEditorContext::FixAttributeMode ); + form.setFeature( f ); + + QgsEditorWidgetWrapper *ww = qobject_cast( form.mWidgets[1] ); + QCOMPARE( ww->field().name(), QString( "col1" ) ); + QCOMPARE( ww->value(), QVariant( 681 ) ); + + // now change the value + ww->setValue( QVariant( 630 ) ); + + // the value should be updated + QCOMPARE( ww->value(), QVariant( 630 ) ); + // the feature is not saved yet, so contains the old value + QCOMPARE( form.feature().attribute( QStringLiteral( "col1" ) ), QVariant( 681 ) ); + // now save the feature and enjoy its new value, but don't update the layer + QVERIFY( form.save() ); + QCOMPARE( form.feature().attribute( QStringLiteral( "col1" ) ), QVariant( 630 ) ); + QCOMPARE( ( int )layer->featureCount(), 0 ); + + delete layer; +} + void TestQgsAttributeForm::testAttributeFormInterface() { // Issue https://github.com/qgis/QGIS/issues/29667