From d03746bd9f72bc291037d28584b68a07ea661ccb Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Wed, 12 Feb 2025 17:02:12 +0200 Subject: [PATCH 01/12] Add merge policy as a member in QgsField --- .../PyQt6/core/auto_generated/qgsfield.sip.in | 20 +++++++ .../vector/qgsvectorlayer.sip.in | 21 ++++++++ python/core/auto_generated/qgsfield.sip.in | 20 +++++++ .../vector/qgsvectorlayer.sip.in | 21 ++++++++ src/core/qgsfield.cpp | 10 ++++ src/core/qgsfield.h | 20 +++++++ src/core/qgsfield_p.h | 5 ++ src/core/vector/qgsvectorlayer.cpp | 53 +++++++++++++++++++ src/core/vector/qgsvectorlayer.h | 30 +++++++++++ tests/src/core/testqgsfield.cpp | 3 ++ 10 files changed, 203 insertions(+) diff --git a/python/PyQt6/core/auto_generated/qgsfield.sip.in b/python/PyQt6/core/auto_generated/qgsfield.sip.in index ea73cfbb6cb7..4ac9a664e153 100644 --- a/python/PyQt6/core/auto_generated/qgsfield.sip.in +++ b/python/PyQt6/core/auto_generated/qgsfield.sip.in @@ -531,6 +531,26 @@ be handled during a duplicate operation. .. seealso:: :py:func:`duplicatePolicy` .. versionadded:: 3.38 +%End + + Qgis::FieldDomainMergePolicy mergePolicy() const /HoldGIL/; +%Docstring +Returns the field's merge policy, which indicates how field values should +be handled during a merge operation. + +.. seealso:: :py:func:`setMergePolicy` + +.. versionadded:: 3.44 +%End + + void setMergePolicy( Qgis::FieldDomainMergePolicy policy ) /HoldGIL/; +%Docstring +Sets the field's merge ``policy``, which indicates how field values should +be handled during a merge operation. + +.. seealso:: :py:func:`mergePolicy` + +.. versionadded:: 3.44 %End SIP_PYOBJECT __repr__(); diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in index c9924db14036..9288f7e74cfe 100644 --- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1953,6 +1953,27 @@ Sets a duplicate ``policy`` for the field with the specified index. } %End + void setFieldMergePolicy( int index, Qgis::FieldDomainMergePolicy policy ); +%Docstring +Sets a merge ``policy`` for the field with the specified index. + +:raises KeyError: if no field with the specified index exists + +.. versionadded:: 3.44 +%End + +%MethodCode + if ( a0 < 0 || a0 >= sipCpp->fields().count() ) + { + PyErr_SetString( PyExc_KeyError, QByteArray::number( a0 ) ); + sipIsErr = 1; + } + else + { + sipCpp->setFieldMergePolicy( a0, a1 ); + } +%End + QSet excludeAttributesWms() const /Deprecated="Since 3.16. Use fields().configurationFlags() instead."/; %Docstring A set of attributes that are not advertised in WMS requests with QGIS server. diff --git a/python/core/auto_generated/qgsfield.sip.in b/python/core/auto_generated/qgsfield.sip.in index ea73cfbb6cb7..4ac9a664e153 100644 --- a/python/core/auto_generated/qgsfield.sip.in +++ b/python/core/auto_generated/qgsfield.sip.in @@ -531,6 +531,26 @@ be handled during a duplicate operation. .. seealso:: :py:func:`duplicatePolicy` .. versionadded:: 3.38 +%End + + Qgis::FieldDomainMergePolicy mergePolicy() const /HoldGIL/; +%Docstring +Returns the field's merge policy, which indicates how field values should +be handled during a merge operation. + +.. seealso:: :py:func:`setMergePolicy` + +.. versionadded:: 3.44 +%End + + void setMergePolicy( Qgis::FieldDomainMergePolicy policy ) /HoldGIL/; +%Docstring +Sets the field's merge ``policy``, which indicates how field values should +be handled during a merge operation. + +.. seealso:: :py:func:`mergePolicy` + +.. versionadded:: 3.44 %End SIP_PYOBJECT __repr__(); diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in index c9924db14036..9288f7e74cfe 100644 --- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1953,6 +1953,27 @@ Sets a duplicate ``policy`` for the field with the specified index. } %End + void setFieldMergePolicy( int index, Qgis::FieldDomainMergePolicy policy ); +%Docstring +Sets a merge ``policy`` for the field with the specified index. + +:raises KeyError: if no field with the specified index exists + +.. versionadded:: 3.44 +%End + +%MethodCode + if ( a0 < 0 || a0 >= sipCpp->fields().count() ) + { + PyErr_SetString( PyExc_KeyError, QByteArray::number( a0 ) ); + sipIsErr = 1; + } + else + { + sipCpp->setFieldMergePolicy( a0, a1 ); + } +%End + QSet excludeAttributesWms() const /Deprecated="Since 3.16. Use fields().configurationFlags() instead."/; %Docstring A set of attributes that are not advertised in WMS requests with QGIS server. diff --git a/src/core/qgsfield.cpp b/src/core/qgsfield.cpp index 056bd9b4afc3..fddade799a93 100644 --- a/src/core/qgsfield.cpp +++ b/src/core/qgsfield.cpp @@ -772,6 +772,16 @@ void QgsField::setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ) d->duplicatePolicy = policy; } +Qgis::FieldDomainMergePolicy QgsField::mergePolicy() const +{ + return d->mergePolicy; +} + +void QgsField::setMergePolicy( Qgis::FieldDomainMergePolicy policy ) +{ + d->mergePolicy = policy; +} + /*************************************************************************** * This class is considered CRITICAL and any change MUST be accompanied with * full unit tests in testqgsfield.cpp. diff --git a/src/core/qgsfield.h b/src/core/qgsfield.h index 19bac8e57963..ba73a978fb1b 100644 --- a/src/core/qgsfield.h +++ b/src/core/qgsfield.h @@ -545,6 +545,26 @@ class CORE_EXPORT QgsField */ void setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ) SIP_HOLDGIL; + /** + * Returns the field's merge policy, which indicates how field values should + * be handled during a merge operation. + * + * \see setMergePolicy() + * + * \since QGIS 3.44 + */ + Qgis::FieldDomainMergePolicy mergePolicy() const SIP_HOLDGIL; + + /** + * Sets the field's merge \a policy, which indicates how field values should + * be handled during a merge operation. + * + * \see mergePolicy() + * + * \since QGIS 3.44 + */ + void setMergePolicy( Qgis::FieldDomainMergePolicy policy ) SIP_HOLDGIL; + #ifdef SIP_RUN SIP_PYOBJECT __repr__(); % MethodCode diff --git a/src/core/qgsfield_p.h b/src/core/qgsfield_p.h index 54d25c2bca8b..6ee0e9a8fda1 100644 --- a/src/core/qgsfield_p.h +++ b/src/core/qgsfield_p.h @@ -85,6 +85,7 @@ class QgsFieldPrivate : public QSharedData , editorWidgetSetup( other.editorWidgetSetup ) , splitPolicy( other.splitPolicy ) , duplicatePolicy( other.duplicatePolicy ) + , mergePolicy( other.mergePolicy ) , isReadOnly( other.isReadOnly ) { } @@ -101,6 +102,7 @@ class QgsFieldPrivate : public QSharedData && ( constraints == other.constraints ) && ( flags == other.flags ) && ( splitPolicy == other.splitPolicy ) && ( duplicatePolicy == other.duplicatePolicy ) + && ( mergePolicy == other.mergePolicy ) && ( isReadOnly == other.isReadOnly ) && ( editorWidgetSetup == other.editorWidgetSetup ) ); } @@ -149,6 +151,9 @@ class QgsFieldPrivate : public QSharedData //! Duplicate policy Qgis::FieldDuplicatePolicy duplicatePolicy = Qgis::FieldDuplicatePolicy::Duplicate; + //! Merge policy + Qgis::FieldDomainMergePolicy mergePolicy = Qgis::FieldDomainMergePolicy::UnsetField; + //! Read-only bool isReadOnly = false; diff --git a/src/core/vector/qgsvectorlayer.cpp b/src/core/vector/qgsvectorlayer.cpp index a08758adeaa2..25fe70e60117 100644 --- a/src/core/vector/qgsvectorlayer.cpp +++ b/src/core/vector/qgsvectorlayer.cpp @@ -2245,6 +2245,10 @@ bool QgsVectorLayer::setDataProvider( QString const &provider, const QgsDataProv { mAttributeDuplicatePolicy[ field.name() ] = field.duplicatePolicy(); } + if ( !mAttributeMergePolicy.contains( field.name() ) ) + { + mAttributeMergePolicy[ field.name() ] = field.mergePolicy(); + } } if ( profile ) @@ -2588,6 +2592,19 @@ bool QgsVectorLayer::readSymbology( const QDomNode &layerNode, QString &errorMes } } + const QDomNode mergePoliciesNode = layerNode.namedItem( QStringLiteral( "mergePolicies" ) ); + if ( !mergePoliciesNode.isNull() ) + { + const QDomNodeList mergePolicyNodeList = mergePoliciesNode.toElement().elementsByTagName( QStringLiteral( "policy" ) ); + for ( int i = 0; i < mergePolicyNodeList.size(); ++i ) + { + const QDomElement mergePolicyElem = mergePolicyNodeList.at( i ).toElement(); + const QString field = mergePolicyElem.attribute( QStringLiteral( "field" ) ); + const Qgis::FieldDomainMergePolicy policy = qgsEnumKeyToValue( mergePolicyElem.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDomainMergePolicy::DefaultValue ); + mAttributeMergePolicy.insert( field, policy ); + } + } + // default expressions mDefaultExpressionMap.clear(); QDomNode defaultsNode = layerNode.namedItem( QStringLiteral( "defaults" ) ); @@ -3122,6 +3139,19 @@ bool QgsVectorLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString node.appendChild( duplicatePoliciesElement ); } + //merge policies + { + QDomElement mergePoliciesElement = doc.createElement( QStringLiteral( "mergePolicies" ) ); + for ( const QgsField &field : std::as_const( mFields ) ) + { + QDomElement mergePolicyElem = doc.createElement( QStringLiteral( "policy" ) ); + mergePolicyElem.setAttribute( QStringLiteral( "field" ), field.name() ); + mergePolicyElem.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.mergePolicy() ) ); + mergePoliciesElement.appendChild( mergePolicyElem ); + } + node.appendChild( mergePoliciesElement ); + } + //default expressions QDomElement defaultsElem = doc.createElement( QStringLiteral( "defaults" ) ); for ( const QgsField &field : std::as_const( mFields ) ) @@ -3641,6 +3671,20 @@ void QgsVectorLayer::setFieldDuplicatePolicy( int index, Qgis::FieldDuplicatePol emit layerModified(); // TODO[MD]: should have a different signal? } +void QgsVectorLayer::setFieldMergePolicy( int index, Qgis::FieldDomainMergePolicy policy ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + if ( index < 0 || index >= fields().count() ) + return; + + const QString name = fields().at( index ).name(); + + mAttributeMergePolicy.insert( name, policy ); + mFields[ index ].setMergePolicy( policy ); + mEditFormConfig.setFields( mFields ); + emit layerModified(); // TODO[MD]: should have a different signal? +} QSet QgsVectorLayer::excludeAttributesWms() const { @@ -4523,6 +4567,15 @@ void QgsVectorLayer::updateFields() mFields[ index ].setDuplicatePolicy( duplicatePolicyIt.value() ); } + for ( auto mergePolicyIt = mAttributeMergePolicy.constBegin(); mergePolicyIt != mAttributeMergePolicy.constEnd(); ++mergePolicyIt ) + { + int index = mFields.lookupField( mergePolicyIt.key() ); + if ( index < 0 ) + continue; + + mFields[ index ].setMergePolicy( mergePolicyIt.value() ); + } + // Update configuration flags QMap< QString, Qgis::FieldConfigurationFlags >::const_iterator flagsIt = mFieldConfigurationFlags.constBegin(); for ( ; flagsIt != mFieldConfigurationFlags.constEnd(); ++flagsIt ) diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h index 9d92063ceaa5..c819ff48e199 100644 --- a/src/core/vector/qgsvectorlayer.h +++ b/src/core/vector/qgsvectorlayer.h @@ -1851,6 +1851,13 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * \since QGIS 3.38 */ void setFieldDuplicatePolicy( int index, Qgis::FieldDuplicatePolicy policy ); + + /** + * Sets a merge \a policy for the field with the specified index. + * + * \since QGIS 3.44 + */ + void setFieldMergePolicy( int index, Qgis::FieldDomainMergePolicy policy ); #else /** @@ -1892,6 +1899,26 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte sipCpp->setFieldDuplicatePolicy( a0, a1 ); } % End + + /** + * Sets a merge \a policy for the field with the specified index. + * + * \throws KeyError if no field with the specified index exists + * \since QGIS 3.44 + */ + void setFieldMergePolicy( int index, Qgis::FieldDomainMergePolicy policy ); + + % MethodCode + if ( a0 < 0 || a0 >= sipCpp->fields().count() ) + { + PyErr_SetString( PyExc_KeyError, QByteArray::number( a0 ) ); + sipIsErr = 1; + } + else + { + sipCpp->setFieldMergePolicy( a0, a1 ); + } + % End #endif /** @@ -2909,6 +2936,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte //! Map that stores the duplicate policy for attributes QMap< QString, Qgis::FieldDuplicatePolicy > mAttributeDuplicatePolicy; + //! Map that stores the merge policy for attributes + QMap< QString, Qgis::FieldDomainMergePolicy > mAttributeMergePolicy; + //! An internal structure to keep track of fields that have a defaultValueOnUpdate QSet mDefaultValueOnUpdateFields; diff --git a/tests/src/core/testqgsfield.cpp b/tests/src/core/testqgsfield.cpp index b88ac69bb074..0785c7d41a33 100644 --- a/tests/src/core/testqgsfield.cpp +++ b/tests/src/core/testqgsfield.cpp @@ -208,6 +208,9 @@ void TestQgsField::gettersSetters() field.setDuplicatePolicy( Qgis::FieldDuplicatePolicy::UnsetField ); QCOMPARE( field.duplicatePolicy(), Qgis::FieldDuplicatePolicy::UnsetField ); + field.setMergePolicy( Qgis::FieldDomainMergePolicy::GeometryWeighted ); + QCOMPARE( field.mergePolicy(), Qgis::FieldDomainMergePolicy::GeometryWeighted ); + field.setMetadata( { { static_cast( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" ) }, { 2, 5 } } ); QMap expected { { static_cast( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" ) }, { 2, 5 } }; QCOMPARE( field.metadata(), expected ); From b0a12f78aa73eb8e286e6cef107152065ac1f9b4 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Wed, 12 Feb 2025 17:06:13 +0200 Subject: [PATCH 02/12] Add new Field Domain merge policy --- python/PyQt6/core/auto_additions/qgis.py | 5 +++++ python/PyQt6/core/auto_generated/qgis.sip.in | 1 + python/core/auto_additions/qgis.py | 5 +++++ python/core/auto_generated/qgis.sip.in | 1 + src/app/browser/qgsinbuiltdataitemproviders.cpp | 3 +++ src/core/qgis.h | 1 + src/core/qgsogrutils.cpp | 4 ++++ 7 files changed, 20 insertions(+) diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index 3f0acb5a87d7..e19a4688905b 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -6928,6 +6928,7 @@ Qgis.FieldDomainMergePolicy.DefaultValue.__doc__ = "Use default field value" Qgis.FieldDomainMergePolicy.Sum.__doc__ = "Sum of values" Qgis.FieldDomainMergePolicy.GeometryWeighted.__doc__ = "New values are computed as the weighted average of the source values" +Qgis.FieldDomainMergePolicy.UnsetField.__doc__ = "Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.__doc__ = """Merge policy for field domains. When a feature is built by merging multiple features, defines how the value of @@ -6938,6 +6939,10 @@ * ``DefaultValue``: Use default field value * ``Sum``: Sum of values * ``GeometryWeighted``: New values are computed as the weighted average of the source values +* ``UnsetField``: Clears the field value so that the data provider backend will populate using any backend triggers or similar logic + + .. versionadded:: 3.44 + """ # -- diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index 2c8ae9e630eb..dc3d83001c0d 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -2112,6 +2112,7 @@ The development version DefaultValue, Sum, GeometryWeighted, + UnsetField, }; enum class FieldDuplicatePolicy /BaseType=IntEnum/ diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index a45fb29ec11d..d3968d16e6d8 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -6862,6 +6862,7 @@ Qgis.FieldDomainMergePolicy.DefaultValue.__doc__ = "Use default field value" Qgis.FieldDomainMergePolicy.Sum.__doc__ = "Sum of values" Qgis.FieldDomainMergePolicy.GeometryWeighted.__doc__ = "New values are computed as the weighted average of the source values" +Qgis.FieldDomainMergePolicy.UnsetField.__doc__ = "Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.__doc__ = """Merge policy for field domains. When a feature is built by merging multiple features, defines how the value of @@ -6872,6 +6873,10 @@ * ``DefaultValue``: Use default field value * ``Sum``: Sum of values * ``GeometryWeighted``: New values are computed as the weighted average of the source values +* ``UnsetField``: Clears the field value so that the data provider backend will populate using any backend triggers or similar logic + + .. versionadded:: 3.44 + """ # -- diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index f6e2c40a25db..f70979dd964d 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -2112,6 +2112,7 @@ The development version DefaultValue, Sum, GeometryWeighted, + UnsetField, }; enum class FieldDuplicatePolicy diff --git a/src/app/browser/qgsinbuiltdataitemproviders.cpp b/src/app/browser/qgsinbuiltdataitemproviders.cpp index 121d0bce532d..df2560a630f2 100644 --- a/src/app/browser/qgsinbuiltdataitemproviders.cpp +++ b/src/app/browser/qgsinbuiltdataitemproviders.cpp @@ -2244,6 +2244,9 @@ QString QgsFieldDomainDetailsWidget::htmlMetadata( QgsFieldDomain *domain, const case Qgis::FieldDomainMergePolicy::GeometryWeighted: metadata += tr( "Use geometry weighted value" ); break; + case Qgis::FieldDomainMergePolicy::UnsetField: + metadata += tr( "Unset field" ); + break; } metadata += QLatin1String( "\n

" ); diff --git a/src/core/qgis.h b/src/core/qgis.h index 701dfaa6d32f..9fd70ae67f57 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -3729,6 +3729,7 @@ class CORE_EXPORT Qgis DefaultValue, //!< Use default field value Sum, //!< Sum of values GeometryWeighted, //!< New values are computed as the weighted average of the source values + UnsetField, //!< Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \since QGIS 3.44 }; Q_ENUM( FieldDomainMergePolicy ) diff --git a/src/core/qgsogrutils.cpp b/src/core/qgsogrutils.cpp index e376b8a2432d..21e7361d0317 100644 --- a/src/core/qgsogrutils.cpp +++ b/src/core/qgsogrutils.cpp @@ -2298,6 +2298,10 @@ OGRFieldDomainH QgsOgrUtils::convertFieldDomain( const QgsFieldDomain *domain ) case Qgis::FieldDomainMergePolicy::Sum: OGR_FldDomain_SetMergePolicy( res, OFDMP_SUM ); break; + + case Qgis::FieldDomainMergePolicy::UnsetField: + // not supported + break; } switch ( domain->splitPolicy() ) From 1e7604686d91a3f28e744eed80e938d104686f28 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Wed, 12 Feb 2025 17:09:44 +0200 Subject: [PATCH 03/12] Apply merge policies in merge dialog --- src/app/qgisapp.cpp | 5 +- src/app/qgsmergeattributesdialog.cpp | 110 +++++++++++++++++++++++---- src/app/qgsmergeattributesdialog.h | 4 +- 3 files changed, 101 insertions(+), 18 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 03cb1fe9107d..efd0bb335405 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -9451,9 +9451,8 @@ void QgisApp::mergeAttributesOfSelectedFeatures() QgsFeatureList featureList = vl->selectedFeatures(); //merge the attributes together - QgsMergeAttributesDialog d( featureList, vl, mapCanvas() ); - //initialize dialog with all columns set to skip - d.setAllToSkip(); + QgsMergeAttributesDialog d( featureList, vl, mapCanvas(), true ); + if ( d.exec() == QDialog::Rejected ) { return; diff --git a/src/app/qgsmergeattributesdialog.cpp b/src/app/qgsmergeattributesdialog.cpp index c6faed8be5b6..cbce86f90cbf 100644 --- a/src/app/qgsmergeattributesdialog.cpp +++ b/src/app/qgsmergeattributesdialog.cpp @@ -50,7 +50,7 @@ const QList QgsMergeAttributesDialog::DISPLAY_STATS = QListverticalHeader(); if ( verticalHeader ) @@ -149,7 +153,7 @@ void QgsMergeAttributesDialog::setAttributeTableConfig( const QgsAttributeTableC } } -void QgsMergeAttributesDialog::createTableWidgetContents() +void QgsMergeAttributesDialog::createTableWidgetContents( bool skipAll ) { //get information about attributes from vector layer if ( !mVectorLayer ) @@ -238,26 +242,106 @@ void QgsMergeAttributesDialog::createTableWidgetContents() //initially set any fields with default values/default value clauses to that value for ( int j = 0; j < mTableWidget->columnCount(); j++ ) { + if ( skipAll ) + break; + int idx = mTableWidget->horizontalHeaderItem( j )->data( FieldIndex ).toInt(); bool setToManual = false; - if ( !mVectorLayer->dataProvider()->defaultValueClause( idx ).isEmpty() ) - { - QVariant v = mVectorLayer->dataProvider()->defaultValueClause( idx ); - mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, v ); - mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, v ); - setToManual = true; - } - else + const QgsField field = mVectorLayer->fields().at( idx ); + + switch ( field.mergePolicy() ) { - QVariant v = mVectorLayer->dataProvider()->defaultValue( idx ); - if ( v.isValid() ) + case Qgis::FieldDomainMergePolicy::Sum: { + if ( !field.isNumeric() ) + break; + + const double sum = std::accumulate( mFeatureList.constBegin(), mFeatureList.constEnd(), 0.0, [idx]( double sum, const QgsFeature &f ) { + return sum + f.attribute( idx ).toDouble(); + } ); + + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, sum ); + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, sum ); + setToManual = true; + + break; + } + + case Qgis::FieldDomainMergePolicy::DefaultValue: + { + // create a dummy feature with the combined geometry in case the default value expression uses the geometry. + // however populating the feature's fields is problematic because the values haven't been set yet and + // generating them at this point isn't possible since the expression might refer to another field + // with a default value expression leading to conflicts + QgsFeature f; + + QVector geoms; + for ( const QgsFeature &f : mFeatureList ) + geoms << f.geometry(); + + const QgsGeometry mergedGeom = QgsGeometry::unaryUnion( geoms ); + f.setGeometry( mergedGeom ); + + const QVariant v = mVectorLayer->defaultValue( idx, f ); + + if ( !v.isValid() ) + break; + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, v ); mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, v ); setToManual = true; + break; + } + + case Qgis::FieldDomainMergePolicy::GeometryWeighted: + { + if ( !field.isNumeric() || mVectorLayer->geometryType() == Qgis::GeometryType::Point ) + break; + + QVector geoms; + for ( const QgsFeature &f : mFeatureList ) + geoms << f.geometry(); + + const QgsGeometry mergedGeom = QgsGeometry::unaryUnion( geoms ); + const double mergedSize = mVectorLayer->geometryType() == Qgis::GeometryType::Polygon ? mergedGeom.area() : mergedGeom.length(); + + const double value = std::accumulate( mFeatureList.constBegin(), mFeatureList.constEnd(), 0.0, [&, idx]( double sum, const QgsFeature &f ) { + const double geomSize = mVectorLayer->geometryType() == Qgis::GeometryType::Polygon ? f.geometry().area() : f.geometry().length(); + const double weightMultiplier = geomSize / mergedSize; + return sum + ( f.attribute( idx ).toDouble() * weightMultiplier ); + } ); + + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, value ); + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, value ); + setToManual = true; + + break; + } + + case Qgis::FieldDomainMergePolicy::UnsetField: + { + if ( !mVectorLayer->dataProvider()->defaultValueClause( idx ).isEmpty() ) + { + QVariant v = mVectorLayer->dataProvider()->defaultValueClause( idx ); + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, v ); + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, v ); + setToManual = true; + } + else + { + QVariant v = mVectorLayer->dataProvider()->defaultValue( idx ); + if ( v.isValid() ) + { + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, v ); + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, v ); + setToManual = true; + } + } } } + + if ( setToManual ) { QComboBox *currentComboBox = qobject_cast( mTableWidget->cellWidget( 0, j ) ); diff --git a/src/app/qgsmergeattributesdialog.h b/src/app/qgsmergeattributesdialog.h index 6b3eccaceb9a..bebcf915c4cc 100644 --- a/src/app/qgsmergeattributesdialog.h +++ b/src/app/qgsmergeattributesdialog.h @@ -42,7 +42,7 @@ class APP_EXPORT QgsMergeAttributesDialog : public QDialog, private Ui::QgsMerge }; - QgsMergeAttributesDialog( const QgsFeatureList &features, QgsVectorLayer *vl, QgsMapCanvas *canvas, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags() ); + QgsMergeAttributesDialog( const QgsFeatureList &features, QgsVectorLayer *vl, QgsMapCanvas *canvas, bool skipAll = false, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags() ); ~QgsMergeAttributesDialog() override; QgsAttributes mergedAttributes() const; @@ -82,7 +82,7 @@ class APP_EXPORT QgsMergeAttributesDialog : public QDialog, private Ui::QgsMerge private: QgsMergeAttributesDialog(); //default constructor forbidden - void createTableWidgetContents(); + void createTableWidgetContents( bool skipAll ); void setAttributeTableConfig( const QgsAttributeTableConfig &config ); //! Create new combo box with the options for featureXX / mean / min / max From b0ce109adb11cf750a64a253468be749b9db5aed Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Wed, 12 Feb 2025 17:10:56 +0200 Subject: [PATCH 04/12] Allow setting merge policy in the GUI --- .../qgsattributetypedialog.cpp | 58 +++++++++++++++++++ .../qgsattributetypedialog.h | 20 +++++++ .../vector/qgsattributesformproperties.cpp | 17 ++++++ src/gui/vector/qgsattributesformproperties.h | 1 + .../qgsattributetypeedit.ui | 43 +++++++++----- 5 files changed, 126 insertions(+), 13 deletions(-) diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.cpp b/src/gui/attributeformconfig/qgsattributetypedialog.cpp index 2183a2c7b085..660e8f0be5ea 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.cpp +++ b/src/gui/attributeformconfig/qgsattributetypedialog.cpp @@ -126,6 +126,29 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx mDuplicatePolicyComboBox->addItem( tr( "Remove Value" ), QVariant::fromValue( Qgis::FieldDuplicatePolicy::UnsetField ) ); connect( mDuplicatePolicyComboBox, qOverload( &QComboBox::currentIndexChanged ), this, &QgsAttributeTypeDialog::updateDuplicatePolicyLabel ); updateDuplicatePolicyLabel(); + + if ( mLayer->isSpatial() ) + { + mMergePolicyComboBox->addItem( tr( "Remove Value" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::UnsetField ) ); + mMergePolicyComboBox->addItem( tr( "Use Default Value" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::DefaultValue ) ); + + if ( mLayer->fields().at( mFieldIdx ).isNumeric() ) + { + mMergePolicyComboBox->addItem( tr( "Use Sum" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::Sum ) ); + + if ( mLayer->geometryType() != Qgis::GeometryType::Point ) + mMergePolicyComboBox->addItem( tr( "Use Average Weighted by Geometry" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::GeometryWeighted ) ); + } + } + else + { + mMergePolicyComboBox->setEnabled( false ); + mMergePolicyLabel->setEnabled( false ); + mMergePolicyDescriptionLabel->hide(); + } + + connect( mMergePolicyComboBox, qOverload( &QComboBox::currentIndexChanged ), this, &QgsAttributeTypeDialog::updateMergePolicyLabel ); + updateMergePolicyLabel(); } QgsAttributeTypeDialog::~QgsAttributeTypeDialog() @@ -400,6 +423,17 @@ void QgsAttributeTypeDialog::setDuplicatePolicy( Qgis::FieldDuplicatePolicy poli updateSplitPolicyLabel(); } +void QgsAttributeTypeDialog::setMergePolicy( Qgis::FieldDomainMergePolicy policy ) +{ + mMergePolicyComboBox->setCurrentIndex( mMergePolicyComboBox->findData( QVariant::fromValue( policy ) ) ); + updateMergePolicyLabel(); +} + +Qgis::FieldDomainMergePolicy QgsAttributeTypeDialog::mergePolicy() const +{ + return mMergePolicyComboBox->currentData().value(); +} + QString QgsAttributeTypeDialog::constraintExpression() const { return constraintExpressionWidget->asExpression(); @@ -537,6 +571,30 @@ void QgsAttributeTypeDialog::updateDuplicatePolicyLabel() mDuplicatePolicyDescriptionLabel->setText( QStringLiteral( "%1" ).arg( helperText ) ); } +void QgsAttributeTypeDialog::updateMergePolicyLabel() +{ + QString helperText; + switch ( mMergePolicyComboBox->currentData().value() ) + { + case Qgis::FieldDomainMergePolicy::DefaultValue: + helperText = tr( "Use default field value." ); + break; + + case Qgis::FieldDomainMergePolicy::Sum: + helperText = tr( "Sum of values." ); + break; + + case Qgis::FieldDomainMergePolicy::GeometryWeighted: + helperText = tr( "New values are computed as the weighted average of the source values." ); + break; + + case Qgis::FieldDomainMergePolicy::UnsetField: + helperText = tr( "Clears the field to an unset state." ); + break; + } + mMergePolicyDescriptionLabel->setText( QStringLiteral( "%1" ).arg( helperText ) ); +} + QStandardItem *QgsAttributeTypeDialog::currentItem() const { QStandardItemModel *widgetTypeModel = qobject_cast( mWidgetTypeComboBox->model() ); diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.h b/src/gui/attributeformconfig/qgsattributetypedialog.h index 8b18c88911a7..634fe115f2d9 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.h +++ b/src/gui/attributeformconfig/qgsattributetypedialog.h @@ -271,6 +271,24 @@ class GUI_EXPORT QgsAttributeTypeDialog : public QWidget, private Ui::QgsAttribu */ void setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ); + /** + * Returns the field's merge policy. + * + * \see setMergePolicy() + * + * \since QGIS 3.42 + */ + Qgis::FieldDomainMergePolicy mergePolicy() const; + + /** + * Sets the field's merge policy. + * + * \see mergePolicy() + * + * \since QGIS 3.42 + */ + void setMergePolicy( Qgis::FieldDomainMergePolicy policy ); + private slots: /** @@ -285,6 +303,8 @@ class GUI_EXPORT QgsAttributeTypeDialog : public QWidget, private Ui::QgsAttribu void updateDuplicatePolicyLabel(); + void updateMergePolicyLabel(); + private: QgsVectorLayer *mLayer = nullptr; int mFieldIdx; diff --git a/src/gui/vector/qgsattributesformproperties.cpp b/src/gui/vector/qgsattributesformproperties.cpp index 99c9af2449e6..001c466f7876 100644 --- a/src/gui/vector/qgsattributesformproperties.cpp +++ b/src/gui/vector/qgsattributesformproperties.cpp @@ -343,6 +343,7 @@ void QgsAttributesFormProperties::loadAttributeTypeDialogFromConfiguration( cons mAttributeTypeDialog->setUniqueEnforced( constraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) == QgsFieldConstraints::ConstraintStrengthHard ); mAttributeTypeDialog->setSplitPolicy( config.mSplitPolicy ); mAttributeTypeDialog->setDuplicatePolicy( config.mDuplicatePolicy ); + mAttributeTypeDialog->setMergePolicy( config.mMergePolicy ); QgsFieldConstraints::Constraints providerConstraints = QgsFieldConstraints::Constraints(); if ( constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) == QgsFieldConstraints::ConstraintOriginProvider ) @@ -417,6 +418,7 @@ void QgsAttributesFormProperties::storeAttributeTypeDialog() cfg.mEditorWidgetConfig = mAttributeTypeDialog->editorWidgetConfig(); cfg.mSplitPolicy = mAttributeTypeDialog->splitPolicy(); cfg.mDuplicatePolicy = mAttributeTypeDialog->duplicatePolicy(); + cfg.mMergePolicy = mAttributeTypeDialog->mergePolicy(); const int fieldIndex = mAttributeTypeDialog->fieldIdx(); mLayer->setDefaultValueDefinition( fieldIndex, QgsDefaultValue( mAttributeTypeDialog->defaultValueExpression(), mAttributeTypeDialog->applyDefaultValueOnUpdate() ) ); @@ -1047,6 +1049,7 @@ void QgsAttributesFormProperties::apply() mLayer->setFieldAlias( idx, cfg.mAlias ); mLayer->setFieldSplitPolicy( idx, cfg.mSplitPolicy ); mLayer->setFieldDuplicatePolicy( idx, cfg.mDuplicatePolicy ); + mLayer->setFieldMergePolicy( idx, cfg.mMergePolicy ); } // tabs and groups @@ -1118,6 +1121,7 @@ QgsAttributesFormProperties::FieldConfig::FieldConfig( QgsVectorLayer *layer, in mEditorWidgetConfig = setup.config(); mSplitPolicy = layer->fields().at( idx ).splitPolicy(); mDuplicatePolicy = layer->fields().at( idx ).duplicatePolicy(); + mMergePolicy = layer->fields().at( idx ).mergePolicy(); } QgsAttributesFormProperties::FieldConfig::operator QVariant() @@ -2117,6 +2121,11 @@ void QgsAttributesFormProperties::copyWidgetConfiguration() duplicatePolicyElement.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.duplicatePolicy() ) ); documentElement.appendChild( duplicatePolicyElement ); + // Merge policy + QDomElement mergePolicyElement = doc.createElement( QStringLiteral( "mergePolicy" ) ); + mergePolicyElement.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.mergePolicy() ) ); + documentElement.appendChild( mergePolicyElement ); + // Default expressions QDomElement defaultElem = doc.createElement( QStringLiteral( "default" ) ); defaultElem.setAttribute( QStringLiteral( "expression" ), field.defaultValueDefinition().expression() ); @@ -2246,6 +2255,14 @@ void QgsAttributesFormProperties::pasteWidgetConfiguration() config.mDuplicatePolicy = policy; } + // Merge policy + const QDomElement mergePolicyElement = docElem.firstChildElement( QStringLiteral( "mergePolicy" ) ); + if ( !mergePolicyElement.isNull() ) + { + const Qgis::FieldDomainMergePolicy policy = qgsEnumKeyToValue( mergePolicyElement.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDomainMergePolicy::DefaultValue ); + config.mMergePolicy = policy; + } + // Default expressions const QDomElement defaultElement = docElem.firstChildElement( QStringLiteral( "default" ) ); if ( !defaultElement.isNull() ) diff --git a/src/gui/vector/qgsattributesformproperties.h b/src/gui/vector/qgsattributesformproperties.h index 5500fb619bf3..bc48cb6b98ba 100644 --- a/src/gui/vector/qgsattributesformproperties.h +++ b/src/gui/vector/qgsattributesformproperties.h @@ -374,6 +374,7 @@ class GUI_EXPORT QgsAttributesFormProperties : public QWidget, public QgsExpress QString mComment; Qgis::FieldDomainSplitPolicy mSplitPolicy = Qgis::FieldDomainSplitPolicy::Duplicate; Qgis::FieldDuplicatePolicy mDuplicatePolicy = Qgis::FieldDuplicatePolicy::Duplicate; + Qgis::FieldDomainMergePolicy mMergePolicy = Qgis::FieldDomainMergePolicy::DefaultValue; operator QVariant(); }; diff --git a/src/ui/attributeformconfig/qgsattributetypeedit.ui b/src/ui/attributeformconfig/qgsattributetypeedit.ui index 7c46ca9b4f3a..1c4ed70bea57 100644 --- a/src/ui/attributeformconfig/qgsattributetypeedit.ui +++ b/src/ui/attributeformconfig/qgsattributetypeedit.ui @@ -284,19 +284,6 @@ Policies - - - - - - - When duplicating features - - - - - - @@ -304,6 +291,9 @@ + + + @@ -314,6 +304,26 @@ + + + + + + + When merging features + + + + + + + When duplicating features + + + + + + @@ -324,6 +334,13 @@ + + + + TextLabel + + + From 64b330b79407002d58803c45b520c0285f24b037 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Wed, 12 Feb 2025 17:11:16 +0200 Subject: [PATCH 05/12] Add test for merge policies --- .../src/app/testqgsmergeattributesdialog.cpp | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/src/app/testqgsmergeattributesdialog.cpp b/tests/src/app/testqgsmergeattributesdialog.cpp index 27e10c582ee3..7183d36aa7f1 100644 --- a/tests/src/app/testqgsmergeattributesdialog.cpp +++ b/tests/src/app/testqgsmergeattributesdialog.cpp @@ -13,6 +13,8 @@ * * ***************************************************************************/ +#include "QtTest/qtestcase.h" +#include "qgsattributes.h" #include "qgstest.h" #include @@ -220,6 +222,50 @@ class TestQgsMergeattributesDialog : public QgsTest // QVariant gets turned into default value while saving the layer QCOMPARE( dialog.mergedAttributes(), QgsAttributes() << 1 << QVariant() ); } + + void testMergePolicies() + { + // Create test layer + QgsVectorFileWriter::SaveVectorOptions options; + QgsVectorLayer ml( "LineString", "test", "memory" ); + QVERIFY( ml.isValid() ); + + QgsField defaultValueField( QStringLiteral( "defaultValue" ), QMetaType::Type::Int ); + QgsField sumField( QStringLiteral( "sum" ), QMetaType::Type::Int ); + QgsField geometryWeightedField( QStringLiteral( "geometryWeighted" ), QMetaType::Type::Double ); + QVERIFY( ml.dataProvider()->addAttributes( { defaultValueField, sumField, geometryWeightedField } ) ); + ml.updateFields(); + + // set policies + ml.setFieldMergePolicy( 0, Qgis::FieldDomainMergePolicy::DefaultValue ); + ml.setFieldMergePolicy( 1, Qgis::FieldDomainMergePolicy::Sum ); + ml.setFieldMergePolicy( 2, Qgis::FieldDomainMergePolicy::GeometryWeighted ); + + // verify that policies have been correctly set + + QCOMPARE( ml.fields().field( 0 ).mergePolicy(), Qgis::FieldDomainMergePolicy::DefaultValue ); + QCOMPARE( ml.fields().field( 1 ).mergePolicy(), Qgis::FieldDomainMergePolicy::Sum ); + QCOMPARE( ml.fields().field( 2 ).mergePolicy(), Qgis::FieldDomainMergePolicy::GeometryWeighted ); + + // Create features + QgsFeature f1( ml.fields(), 1 ); + f1.setAttributes( QVector() << 10 << 200 << 5 ); + f1.setGeometry( QgsGeometry::fromWkt( "LINESTRING(0 0, 10 0)" ) ); + QVERIFY( ml.dataProvider()->addFeature( f1 ) ); + QCOMPARE( ml.featureCount(), 1 ); + + QgsFeature f2( ml.fields(), 2 ); + f2.setAttributes( QVector() << 15 << 100 << 7.5 ); + f2.setGeometry( QgsGeometry::fromWkt( "LINESTRING(10 0, 15 0)" ) ); + QVERIFY( ml.dataProvider()->addFeature( f2 ) ); + QCOMPARE( ml.featureCount(), 2 ); + + QgsMergeAttributesDialog dialog1( QgsFeatureList() << f1 << f2, &ml, mQgisApp->mapCanvas() ); + + QCOMPARE( dialog1.mergedAttributes().at( 0 ).toInt(), 10 ); + QCOMPARE( dialog1.mergedAttributes().at( 1 ).toInt(), 300 ); + QVERIFY( qgsDoubleNear( dialog1.mergedAttributes().at( 2 ).toDouble(), 5.83333, 0.00001 ) ); + } }; QGSTEST_MAIN( TestQgsMergeattributesDialog ) From 3786d07593733cda9c7b2ccdf027ba8d2f5326d5 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Tue, 18 Feb 2025 15:57:41 +0200 Subject: [PATCH 06/12] Add test for LargestGeometry policy --- tests/src/app/testqgsmergeattributesdialog.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/src/app/testqgsmergeattributesdialog.cpp b/tests/src/app/testqgsmergeattributesdialog.cpp index 7183d36aa7f1..c29f5bba1c8d 100644 --- a/tests/src/app/testqgsmergeattributesdialog.cpp +++ b/tests/src/app/testqgsmergeattributesdialog.cpp @@ -13,8 +13,6 @@ * * ***************************************************************************/ -#include "QtTest/qtestcase.h" -#include "qgsattributes.h" #include "qgstest.h" #include @@ -233,38 +231,44 @@ class TestQgsMergeattributesDialog : public QgsTest QgsField defaultValueField( QStringLiteral( "defaultValue" ), QMetaType::Type::Int ); QgsField sumField( QStringLiteral( "sum" ), QMetaType::Type::Int ); QgsField geometryWeightedField( QStringLiteral( "geometryWeighted" ), QMetaType::Type::Double ); - QVERIFY( ml.dataProvider()->addAttributes( { defaultValueField, sumField, geometryWeightedField } ) ); + QgsField largestGeometryField( QStringLiteral( "largestGeometry" ), QMetaType::Type::QString ); + QVERIFY( ml.dataProvider()->addAttributes( { defaultValueField, sumField, geometryWeightedField, largestGeometryField } ) ); ml.updateFields(); // set policies ml.setFieldMergePolicy( 0, Qgis::FieldDomainMergePolicy::DefaultValue ); ml.setFieldMergePolicy( 1, Qgis::FieldDomainMergePolicy::Sum ); ml.setFieldMergePolicy( 2, Qgis::FieldDomainMergePolicy::GeometryWeighted ); + ml.setFieldMergePolicy( 3, Qgis::FieldDomainMergePolicy::LargestGeometry ); // verify that policies have been correctly set QCOMPARE( ml.fields().field( 0 ).mergePolicy(), Qgis::FieldDomainMergePolicy::DefaultValue ); QCOMPARE( ml.fields().field( 1 ).mergePolicy(), Qgis::FieldDomainMergePolicy::Sum ); QCOMPARE( ml.fields().field( 2 ).mergePolicy(), Qgis::FieldDomainMergePolicy::GeometryWeighted ); + QCOMPARE( ml.fields().field( 3 ).mergePolicy(), Qgis::FieldDomainMergePolicy::LargestGeometry ); // Create features QgsFeature f1( ml.fields(), 1 ); - f1.setAttributes( QVector() << 10 << 200 << 5 ); - f1.setGeometry( QgsGeometry::fromWkt( "LINESTRING(0 0, 10 0)" ) ); + f1.setAttributes( QVector() << 10 << 200 << 7.5 << QStringLiteral( "smaller" ) ); + f1.setGeometry( QgsGeometry::fromWkt( "LINESTRING(10 0, 15 0)" ) ); QVERIFY( ml.dataProvider()->addFeature( f1 ) ); QCOMPARE( ml.featureCount(), 1 ); QgsFeature f2( ml.fields(), 2 ); - f2.setAttributes( QVector() << 15 << 100 << 7.5 ); - f2.setGeometry( QgsGeometry::fromWkt( "LINESTRING(10 0, 15 0)" ) ); + f2.setAttributes( QVector() << 15 << 100 << 5 << QStringLiteral( "bigger" ) ); + f2.setGeometry( QgsGeometry::fromWkt( "LINESTRING(0 0, 10 0)" ) ); QVERIFY( ml.dataProvider()->addFeature( f2 ) ); QCOMPARE( ml.featureCount(), 2 ); QgsMergeAttributesDialog dialog1( QgsFeatureList() << f1 << f2, &ml, mQgisApp->mapCanvas() ); + qDebug() << dialog1.mergedAttributes().at( 2 ).toDouble(); + QCOMPARE( dialog1.mergedAttributes().at( 0 ).toInt(), 10 ); QCOMPARE( dialog1.mergedAttributes().at( 1 ).toInt(), 300 ); QVERIFY( qgsDoubleNear( dialog1.mergedAttributes().at( 2 ).toDouble(), 5.83333, 0.00001 ) ); + QCOMPARE( dialog1.mergedAttributes().at( 3 ).toString(), QStringLiteral( "bigger" ) ); } }; From a2fda5c1c62f461a674ef2ff3a7b841ecc32eca5 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Tue, 18 Feb 2025 15:58:12 +0200 Subject: [PATCH 07/12] Add largest geometry policy to enum --- python/PyQt6/core/auto_additions/qgis.py | 5 +++++ python/PyQt6/core/auto_generated/qgis.sip.in | 1 + python/core/auto_additions/qgis.py | 5 +++++ python/core/auto_generated/qgis.sip.in | 1 + src/app/browser/qgsinbuiltdataitemproviders.cpp | 3 +++ src/core/qgis.h | 1 + src/core/qgsogrutils.cpp | 1 + .../attributeformconfig/qgsattributetypedialog.cpp | 11 +++++++++++ 8 files changed, 28 insertions(+) diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index e19a4688905b..d6fbf964d1b1 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -6929,6 +6929,7 @@ Qgis.FieldDomainMergePolicy.Sum.__doc__ = "Sum of values" Qgis.FieldDomainMergePolicy.GeometryWeighted.__doc__ = "New values are computed as the weighted average of the source values" Qgis.FieldDomainMergePolicy.UnsetField.__doc__ = "Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.LargestGeometry.__doc__ = "Use value from the feature with the largest geometry \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.__doc__ = """Merge policy for field domains. When a feature is built by merging multiple features, defines how the value of @@ -6943,6 +6944,10 @@ .. versionadded:: 3.44 +* ``LargestGeometry``: Use value from the feature with the largest geometry + + .. versionadded:: 3.44 + """ # -- diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index dc3d83001c0d..c3dfc7b47f7f 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -2113,6 +2113,7 @@ The development version Sum, GeometryWeighted, UnsetField, + LargestGeometry, }; enum class FieldDuplicatePolicy /BaseType=IntEnum/ diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index d3968d16e6d8..1f782520d1f0 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -6863,6 +6863,7 @@ Qgis.FieldDomainMergePolicy.Sum.__doc__ = "Sum of values" Qgis.FieldDomainMergePolicy.GeometryWeighted.__doc__ = "New values are computed as the weighted average of the source values" Qgis.FieldDomainMergePolicy.UnsetField.__doc__ = "Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.LargestGeometry.__doc__ = "Use value from the feature with the largest geometry \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.__doc__ = """Merge policy for field domains. When a feature is built by merging multiple features, defines how the value of @@ -6877,6 +6878,10 @@ .. versionadded:: 3.44 +* ``LargestGeometry``: Use value from the feature with the largest geometry + + .. versionadded:: 3.44 + """ # -- diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index f70979dd964d..0fd6411d552e 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -2113,6 +2113,7 @@ The development version Sum, GeometryWeighted, UnsetField, + LargestGeometry, }; enum class FieldDuplicatePolicy diff --git a/src/app/browser/qgsinbuiltdataitemproviders.cpp b/src/app/browser/qgsinbuiltdataitemproviders.cpp index df2560a630f2..b3e61c88b4e0 100644 --- a/src/app/browser/qgsinbuiltdataitemproviders.cpp +++ b/src/app/browser/qgsinbuiltdataitemproviders.cpp @@ -2247,6 +2247,9 @@ QString QgsFieldDomainDetailsWidget::htmlMetadata( QgsFieldDomain *domain, const case Qgis::FieldDomainMergePolicy::UnsetField: metadata += tr( "Unset field" ); break; + case Qgis::FieldDomainMergePolicy::LargestGeometry: + metadata += tr( "Largest geometry" ); + break; } metadata += QLatin1String( "\n

" ); diff --git a/src/core/qgis.h b/src/core/qgis.h index 9fd70ae67f57..cadea6430b3b 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -3730,6 +3730,7 @@ class CORE_EXPORT Qgis Sum, //!< Sum of values GeometryWeighted, //!< New values are computed as the weighted average of the source values UnsetField, //!< Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \since QGIS 3.44 + LargestGeometry, //!< Use value from the feature with the largest geometry \since QGIS 3.44 }; Q_ENUM( FieldDomainMergePolicy ) diff --git a/src/core/qgsogrutils.cpp b/src/core/qgsogrutils.cpp index 21e7361d0317..4db18a68d822 100644 --- a/src/core/qgsogrutils.cpp +++ b/src/core/qgsogrutils.cpp @@ -2300,6 +2300,7 @@ OGRFieldDomainH QgsOgrUtils::convertFieldDomain( const QgsFieldDomain *domain ) break; case Qgis::FieldDomainMergePolicy::UnsetField: + case Qgis::FieldDomainMergePolicy::LargestGeometry: // not supported break; } diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.cpp b/src/gui/attributeformconfig/qgsattributetypedialog.cpp index 660e8f0be5ea..4448f0231625 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.cpp +++ b/src/gui/attributeformconfig/qgsattributetypedialog.cpp @@ -137,7 +137,14 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx mMergePolicyComboBox->addItem( tr( "Use Sum" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::Sum ) ); if ( mLayer->geometryType() != Qgis::GeometryType::Point ) + { mMergePolicyComboBox->addItem( tr( "Use Average Weighted by Geometry" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::GeometryWeighted ) ); + } + } + + if ( mLayer->geometryType() != Qgis::GeometryType::Point ) + { + mMergePolicyComboBox->addItem( tr( "Use Largest Feature" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::LargestGeometry ) ); } } else @@ -591,6 +598,10 @@ void QgsAttributeTypeDialog::updateMergePolicyLabel() case Qgis::FieldDomainMergePolicy::UnsetField: helperText = tr( "Clears the field to an unset state." ); break; + + case Qgis::FieldDomainMergePolicy::LargestGeometry: + helperText = tr( "Use value from feature with the largest geometry." ); + break; } mMergePolicyDescriptionLabel->setText( QStringLiteral( "%1" ).arg( helperText ) ); } From f9f6259e1bc4806365f6b6e951b0ea94196c9793 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Tue, 18 Feb 2025 17:18:11 +0200 Subject: [PATCH 08/12] Implement LargestGeometry merge policy --- src/app/qgsmergeattributesdialog.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/app/qgsmergeattributesdialog.cpp b/src/app/qgsmergeattributesdialog.cpp index cbce86f90cbf..2e930424bf28 100644 --- a/src/app/qgsmergeattributesdialog.cpp +++ b/src/app/qgsmergeattributesdialog.cpp @@ -338,7 +338,25 @@ void QgsMergeAttributesDialog::createTableWidgetContents( bool skipAll ) setToManual = true; } } + break; } + + case Qgis::FieldDomainMergePolicy::LargestGeometry: + { + if ( mVectorLayer->geometryType() == Qgis::GeometryType::Unknown || mVectorLayer->geometryType() == Qgis::GeometryType::Null || mVectorLayer->geometryType() == Qgis::GeometryType::Point ) + break; + + std::function getSize = mVectorLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length; + + QList::iterator largestSelectedFeature = std::max_element( mFeatureList.begin(), mFeatureList.end(), [&getSize]( const QgsFeature &a, const QgsFeature &b ) -> bool { + return getSize( a.geometry() ) < getSize( b.geometry() ); + } ); + + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, largestSelectedFeature->attribute( idx ) ); + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, largestSelectedFeature->attribute( idx ) ); + setToManual = true; + } + break; } From ced40a2d8dac9d98f8bf1547933954b37f146485 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Thu, 20 Feb 2025 20:12:49 +0200 Subject: [PATCH 09/12] Add more policies --- python/PyQt6/core/auto_additions/qgis.py | 15 +++++++++++++++ python/PyQt6/core/auto_generated/qgis.sip.in | 3 +++ python/core/auto_additions/qgis.py | 15 +++++++++++++++ python/core/auto_generated/qgis.sip.in | 3 +++ .../browser/qgsinbuiltdataitemproviders.cpp | 9 +++++++++ src/core/qgis.h | 3 +++ src/core/qgsogrutils.cpp | 3 +++ .../qgsattributetypedialog.cpp | 15 +++++++++++++++ tests/src/app/testqgsmergeattributesdialog.cpp | 18 +++++++++++++++--- 9 files changed, 81 insertions(+), 3 deletions(-) diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index d6fbf964d1b1..c622151fc5dd 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -6930,6 +6930,9 @@ Qgis.FieldDomainMergePolicy.GeometryWeighted.__doc__ = "New values are computed as the weighted average of the source values" Qgis.FieldDomainMergePolicy.UnsetField.__doc__ = "Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.LargestGeometry.__doc__ = "Use value from the feature with the largest geometry \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.MinimumValue.__doc__ = "Use the minimum value from the features-to-be-merged \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.MaximumValue.__doc__ = "Use the maximum value from the features-to-be-merged \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.SkipAttribute.__doc__ = "Use a null value \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.__doc__ = """Merge policy for field domains. When a feature is built by merging multiple features, defines how the value of @@ -6948,6 +6951,18 @@ .. versionadded:: 3.44 +* ``MinimumValue``: Use the minimum value from the features-to-be-merged + + .. versionadded:: 3.44 + +* ``MaximumValue``: Use the maximum value from the features-to-be-merged + + .. versionadded:: 3.44 + +* ``SkipAttribute``: Use a null value + + .. versionadded:: 3.44 + """ # -- diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index c3dfc7b47f7f..b4e877333d86 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -2114,6 +2114,9 @@ The development version GeometryWeighted, UnsetField, LargestGeometry, + MinimumValue, + MaximumValue, + SkipAttribute, }; enum class FieldDuplicatePolicy /BaseType=IntEnum/ diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 1f782520d1f0..c572cfddac2e 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -6864,6 +6864,9 @@ Qgis.FieldDomainMergePolicy.GeometryWeighted.__doc__ = "New values are computed as the weighted average of the source values" Qgis.FieldDomainMergePolicy.UnsetField.__doc__ = "Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.LargestGeometry.__doc__ = "Use value from the feature with the largest geometry \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.MinimumValue.__doc__ = "Use the minimum value from the features-to-be-merged \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.MaximumValue.__doc__ = "Use the maximum value from the features-to-be-merged \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.SkipAttribute.__doc__ = "Use a null value \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.__doc__ = """Merge policy for field domains. When a feature is built by merging multiple features, defines how the value of @@ -6882,6 +6885,18 @@ .. versionadded:: 3.44 +* ``MinimumValue``: Use the minimum value from the features-to-be-merged + + .. versionadded:: 3.44 + +* ``MaximumValue``: Use the maximum value from the features-to-be-merged + + .. versionadded:: 3.44 + +* ``SkipAttribute``: Use a null value + + .. versionadded:: 3.44 + """ # -- diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index 0fd6411d552e..1b4f0159cd0e 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -2114,6 +2114,9 @@ The development version GeometryWeighted, UnsetField, LargestGeometry, + MinimumValue, + MaximumValue, + SkipAttribute, }; enum class FieldDuplicatePolicy diff --git a/src/app/browser/qgsinbuiltdataitemproviders.cpp b/src/app/browser/qgsinbuiltdataitemproviders.cpp index b3e61c88b4e0..19e7b2745a44 100644 --- a/src/app/browser/qgsinbuiltdataitemproviders.cpp +++ b/src/app/browser/qgsinbuiltdataitemproviders.cpp @@ -2250,6 +2250,15 @@ QString QgsFieldDomainDetailsWidget::htmlMetadata( QgsFieldDomain *domain, const case Qgis::FieldDomainMergePolicy::LargestGeometry: metadata += tr( "Largest geometry" ); break; + case Qgis::FieldDomainMergePolicy::MaximumValue: + metadata += tr( "Maximum value" ); + break; + case Qgis::FieldDomainMergePolicy::MinimumValue: + metadata += tr( "Minimum value" ); + break; + case Qgis::FieldDomainMergePolicy::SkipAttribute: + metadata += tr( "Skip attribute" ); + break; } metadata += QLatin1String( "\n

" ); diff --git a/src/core/qgis.h b/src/core/qgis.h index cadea6430b3b..a31471d4868a 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -3731,6 +3731,9 @@ class CORE_EXPORT Qgis GeometryWeighted, //!< New values are computed as the weighted average of the source values UnsetField, //!< Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \since QGIS 3.44 LargestGeometry, //!< Use value from the feature with the largest geometry \since QGIS 3.44 + MinimumValue, //!< Use the minimum value from the features-to-be-merged \since QGIS 3.44 + MaximumValue, //!< Use the maximum value from the features-to-be-merged \since QGIS 3.44 + SkipAttribute, //!< Use a null value \since QGIS 3.44 }; Q_ENUM( FieldDomainMergePolicy ) diff --git a/src/core/qgsogrutils.cpp b/src/core/qgsogrutils.cpp index 4db18a68d822..876cd502d8b0 100644 --- a/src/core/qgsogrutils.cpp +++ b/src/core/qgsogrutils.cpp @@ -2301,6 +2301,9 @@ OGRFieldDomainH QgsOgrUtils::convertFieldDomain( const QgsFieldDomain *domain ) case Qgis::FieldDomainMergePolicy::UnsetField: case Qgis::FieldDomainMergePolicy::LargestGeometry: + case Qgis::FieldDomainMergePolicy::MinimumValue: + case Qgis::FieldDomainMergePolicy::MaximumValue: + case Qgis::FieldDomainMergePolicy::SkipAttribute: // not supported break; } diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.cpp b/src/gui/attributeformconfig/qgsattributetypedialog.cpp index 4448f0231625..f57918d1ae17 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.cpp +++ b/src/gui/attributeformconfig/qgsattributetypedialog.cpp @@ -135,6 +135,9 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx if ( mLayer->fields().at( mFieldIdx ).isNumeric() ) { mMergePolicyComboBox->addItem( tr( "Use Sum" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::Sum ) ); + mMergePolicyComboBox->addItem( tr( "Use Maximum Value" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::MaximumValue ) ); + mMergePolicyComboBox->addItem( tr( "Use Minimum Value" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::MinimumValue ) ); + mMergePolicyComboBox->addItem( tr( "Skip Attribute" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::SkipAttribute ) ); if ( mLayer->geometryType() != Qgis::GeometryType::Point ) { @@ -602,6 +605,18 @@ void QgsAttributeTypeDialog::updateMergePolicyLabel() case Qgis::FieldDomainMergePolicy::LargestGeometry: helperText = tr( "Use value from feature with the largest geometry." ); break; + + case Qgis::FieldDomainMergePolicy::MinimumValue: + helperText = tr( "Use the lowest value from the selected features." ); + break; + + case Qgis::FieldDomainMergePolicy::MaximumValue: + helperText = tr( "Use the highest value from the selected features." ); + break; + + case Qgis::FieldDomainMergePolicy::SkipAttribute: + helperText = tr( "Skip attribute." ); + break; } mMergePolicyDescriptionLabel->setText( QStringLiteral( "%1" ).arg( helperText ) ); } diff --git a/tests/src/app/testqgsmergeattributesdialog.cpp b/tests/src/app/testqgsmergeattributesdialog.cpp index c29f5bba1c8d..c0b0f5698584 100644 --- a/tests/src/app/testqgsmergeattributesdialog.cpp +++ b/tests/src/app/testqgsmergeattributesdialog.cpp @@ -232,7 +232,10 @@ class TestQgsMergeattributesDialog : public QgsTest QgsField sumField( QStringLiteral( "sum" ), QMetaType::Type::Int ); QgsField geometryWeightedField( QStringLiteral( "geometryWeighted" ), QMetaType::Type::Double ); QgsField largestGeometryField( QStringLiteral( "largestGeometry" ), QMetaType::Type::QString ); - QVERIFY( ml.dataProvider()->addAttributes( { defaultValueField, sumField, geometryWeightedField, largestGeometryField } ) ); + QgsField minimumValueField( QStringLiteral( "minimumValue" ), QMetaType::Type::Int ); + QgsField maximumValueField( QStringLiteral( "maximumValue" ), QMetaType::Type::Int ); + QgsField skipAttributeField( QStringLiteral( "skipAttribute" ), QMetaType::Type::Int ); + QVERIFY( ml.dataProvider()->addAttributes( { defaultValueField, sumField, geometryWeightedField, largestGeometryField, minimumValueField, maximumValueField, skipAttributeField } ) ); ml.updateFields(); // set policies @@ -240,6 +243,9 @@ class TestQgsMergeattributesDialog : public QgsTest ml.setFieldMergePolicy( 1, Qgis::FieldDomainMergePolicy::Sum ); ml.setFieldMergePolicy( 2, Qgis::FieldDomainMergePolicy::GeometryWeighted ); ml.setFieldMergePolicy( 3, Qgis::FieldDomainMergePolicy::LargestGeometry ); + ml.setFieldMergePolicy( 4, Qgis::FieldDomainMergePolicy::MinimumValue ); + ml.setFieldMergePolicy( 5, Qgis::FieldDomainMergePolicy::MaximumValue ); + ml.setFieldMergePolicy( 6, Qgis::FieldDomainMergePolicy::SkipAttribute ); // verify that policies have been correctly set @@ -247,16 +253,19 @@ class TestQgsMergeattributesDialog : public QgsTest QCOMPARE( ml.fields().field( 1 ).mergePolicy(), Qgis::FieldDomainMergePolicy::Sum ); QCOMPARE( ml.fields().field( 2 ).mergePolicy(), Qgis::FieldDomainMergePolicy::GeometryWeighted ); QCOMPARE( ml.fields().field( 3 ).mergePolicy(), Qgis::FieldDomainMergePolicy::LargestGeometry ); + QCOMPARE( ml.fields().field( 4 ).mergePolicy(), Qgis::FieldDomainMergePolicy::MinimumValue ); + QCOMPARE( ml.fields().field( 5 ).mergePolicy(), Qgis::FieldDomainMergePolicy::MaximumValue ); + QCOMPARE( ml.fields().field( 6 ).mergePolicy(), Qgis::FieldDomainMergePolicy::SkipAttribute ); // Create features QgsFeature f1( ml.fields(), 1 ); - f1.setAttributes( QVector() << 10 << 200 << 7.5 << QStringLiteral( "smaller" ) ); + f1.setAttributes( QVector() << 10 << 200 << 7.5 << QStringLiteral( "smaller" ) << 10 << -10 << 0 ); f1.setGeometry( QgsGeometry::fromWkt( "LINESTRING(10 0, 15 0)" ) ); QVERIFY( ml.dataProvider()->addFeature( f1 ) ); QCOMPARE( ml.featureCount(), 1 ); QgsFeature f2( ml.fields(), 2 ); - f2.setAttributes( QVector() << 15 << 100 << 5 << QStringLiteral( "bigger" ) ); + f2.setAttributes( QVector() << 15 << 100 << 5 << QStringLiteral( "bigger" ) << -10 << 10 << 5 ); f2.setGeometry( QgsGeometry::fromWkt( "LINESTRING(0 0, 10 0)" ) ); QVERIFY( ml.dataProvider()->addFeature( f2 ) ); QCOMPARE( ml.featureCount(), 2 ); @@ -269,6 +278,9 @@ class TestQgsMergeattributesDialog : public QgsTest QCOMPARE( dialog1.mergedAttributes().at( 1 ).toInt(), 300 ); QVERIFY( qgsDoubleNear( dialog1.mergedAttributes().at( 2 ).toDouble(), 5.83333, 0.00001 ) ); QCOMPARE( dialog1.mergedAttributes().at( 3 ).toString(), QStringLiteral( "bigger" ) ); + QCOMPARE( dialog1.mergedAttributes().at( 4 ).toInt(), 10 ); + QCOMPARE( dialog1.mergedAttributes().at( 5 ).toInt(), -10 ); + QCOMPARE( dialog1.mergedAttributes().at( 6 ).toString(), QStringLiteral( "Skipped" ) ); } }; From e7903979b2c16d33c4c0e9d938d570dda5ea03e0 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Fri, 21 Feb 2025 16:46:16 +0200 Subject: [PATCH 10/12] Implement new merge policies --- src/app/qgsmergeattributesdialog.cpp | 72 ++++++++++++++----- .../qgsattributetypedialog.cpp | 9 +-- .../src/app/testqgsmergeattributesdialog.cpp | 19 ++--- 3 files changed, 67 insertions(+), 33 deletions(-) diff --git a/src/app/qgsmergeattributesdialog.cpp b/src/app/qgsmergeattributesdialog.cpp index 2e930424bf28..80dafad0ab8b 100644 --- a/src/app/qgsmergeattributesdialog.cpp +++ b/src/app/qgsmergeattributesdialog.cpp @@ -249,6 +249,7 @@ void QgsMergeAttributesDialog::createTableWidgetContents( bool skipAll ) bool setToManual = false; const QgsField field = mVectorLayer->fields().at( idx ); + QComboBox *currentComboBox = qobject_cast( mTableWidget->cellWidget( 0, j ) ); switch ( field.mergePolicy() ) { @@ -257,14 +258,8 @@ void QgsMergeAttributesDialog::createTableWidgetContents( bool skipAll ) if ( !field.isNumeric() ) break; - const double sum = std::accumulate( mFeatureList.constBegin(), mFeatureList.constEnd(), 0.0, [idx]( double sum, const QgsFeature &f ) { - return sum + f.attribute( idx ).toDouble(); - } ); - - mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, sum ); - mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, sum ); - setToManual = true; - + if ( currentComboBox ) + currentComboBox->setCurrentIndex( currentComboBox->findData( static_cast( Qgis::Statistic::Sum ) ) ); break; } @@ -343,26 +338,67 @@ void QgsMergeAttributesDialog::createTableWidgetContents( bool skipAll ) case Qgis::FieldDomainMergePolicy::LargestGeometry: { - if ( mVectorLayer->geometryType() == Qgis::GeometryType::Unknown || mVectorLayer->geometryType() == Qgis::GeometryType::Null || mVectorLayer->geometryType() == Qgis::GeometryType::Point ) + if ( mVectorLayer->geometryType() == Qgis::GeometryType::Unknown || mVectorLayer->geometryType() == Qgis::GeometryType::Null ) break; - std::function getSize = mVectorLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length; + QgsFeatureId largestFeatureId = FID_NULL; - QList::iterator largestSelectedFeature = std::max_element( mFeatureList.begin(), mFeatureList.end(), [&getSize]( const QgsFeature &a, const QgsFeature &b ) -> bool { - return getSize( a.geometry() ) < getSize( b.geometry() ); - } ); + if ( mVectorLayer->geometryType() == Qgis::GeometryType::Point ) + { + QList::iterator largestSelectedFeature = std::max_element( mFeatureList.begin(), mFeatureList.end(), []( const QgsFeature &a, const QgsFeature &b ) -> bool { + return a.geometry().constGet()->partCount() < b.geometry().constGet()->partCount(); + } ); - mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, largestSelectedFeature->attribute( idx ) ); - mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, largestSelectedFeature->attribute( idx ) ); - setToManual = true; + largestFeatureId = largestSelectedFeature->id(); + } + else + { + std::function getSize = mVectorLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length; + + QList::iterator largestSelectedFeature = std::max_element( mFeatureList.begin(), mFeatureList.end(), [&getSize]( const QgsFeature &a, const QgsFeature &b ) -> bool { + return getSize( a.geometry() ) < getSize( b.geometry() ); + } ); + + largestFeatureId = largestSelectedFeature->id(); + } + + if ( largestFeatureId == FID_NULL ) + break; + + if ( currentComboBox ) + currentComboBox->setCurrentIndex( currentComboBox->findData( QStringLiteral( "f%1" ).arg( FID_TO_STRING( largestFeatureId ) ) ) ); + + break; + } + + case Qgis::FieldDomainMergePolicy::MinimumValue: + { + if ( currentComboBox ) + currentComboBox->setCurrentIndex( currentComboBox->findData( static_cast( Qgis::Statistic::Min ) ) ); + + break; + } + + case Qgis::FieldDomainMergePolicy::MaximumValue: + { + if ( currentComboBox ) + currentComboBox->setCurrentIndex( currentComboBox->findData( static_cast( Qgis::Statistic::Max ) ) ); + + break; + } + + case Qgis::FieldDomainMergePolicy::SkipAttribute: + { + if ( currentComboBox ) + currentComboBox->setCurrentIndex( currentComboBox->findData( QStringLiteral( "skip" ) ) ); + + break; } break; } - if ( setToManual ) { - QComboBox *currentComboBox = qobject_cast( mTableWidget->cellWidget( 0, j ) ); if ( currentComboBox ) { currentComboBox->blockSignals( true ); diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.cpp b/src/gui/attributeformconfig/qgsattributetypedialog.cpp index f57918d1ae17..73ea9c1c4be1 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.cpp +++ b/src/gui/attributeformconfig/qgsattributetypedialog.cpp @@ -137,18 +137,13 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx mMergePolicyComboBox->addItem( tr( "Use Sum" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::Sum ) ); mMergePolicyComboBox->addItem( tr( "Use Maximum Value" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::MaximumValue ) ); mMergePolicyComboBox->addItem( tr( "Use Minimum Value" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::MinimumValue ) ); - mMergePolicyComboBox->addItem( tr( "Skip Attribute" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::SkipAttribute ) ); if ( mLayer->geometryType() != Qgis::GeometryType::Point ) - { mMergePolicyComboBox->addItem( tr( "Use Average Weighted by Geometry" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::GeometryWeighted ) ); - } } - if ( mLayer->geometryType() != Qgis::GeometryType::Point ) - { - mMergePolicyComboBox->addItem( tr( "Use Largest Feature" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::LargestGeometry ) ); - } + mMergePolicyComboBox->addItem( tr( "Use Largest Feature" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::LargestGeometry ) ); + mMergePolicyComboBox->addItem( tr( "Skip Attribute" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::SkipAttribute ) ); } else { diff --git a/tests/src/app/testqgsmergeattributesdialog.cpp b/tests/src/app/testqgsmergeattributesdialog.cpp index c0b0f5698584..fb16c31ee070 100644 --- a/tests/src/app/testqgsmergeattributesdialog.cpp +++ b/tests/src/app/testqgsmergeattributesdialog.cpp @@ -235,7 +235,9 @@ class TestQgsMergeattributesDialog : public QgsTest QgsField minimumValueField( QStringLiteral( "minimumValue" ), QMetaType::Type::Int ); QgsField maximumValueField( QStringLiteral( "maximumValue" ), QMetaType::Type::Int ); QgsField skipAttributeField( QStringLiteral( "skipAttribute" ), QMetaType::Type::Int ); - QVERIFY( ml.dataProvider()->addAttributes( { defaultValueField, sumField, geometryWeightedField, largestGeometryField, minimumValueField, maximumValueField, skipAttributeField } ) ); + QgsField unsetField( QStringLiteral( "unsetField" ), QMetaType::Type::Int ); + + QVERIFY( ml.dataProvider()->addAttributes( { defaultValueField, sumField, geometryWeightedField, largestGeometryField, minimumValueField, maximumValueField, skipAttributeField, unsetField } ) ); ml.updateFields(); // set policies @@ -246,6 +248,7 @@ class TestQgsMergeattributesDialog : public QgsTest ml.setFieldMergePolicy( 4, Qgis::FieldDomainMergePolicy::MinimumValue ); ml.setFieldMergePolicy( 5, Qgis::FieldDomainMergePolicy::MaximumValue ); ml.setFieldMergePolicy( 6, Qgis::FieldDomainMergePolicy::SkipAttribute ); + ml.setFieldMergePolicy( 7, Qgis::FieldDomainMergePolicy::UnsetField ); // verify that policies have been correctly set @@ -256,31 +259,31 @@ class TestQgsMergeattributesDialog : public QgsTest QCOMPARE( ml.fields().field( 4 ).mergePolicy(), Qgis::FieldDomainMergePolicy::MinimumValue ); QCOMPARE( ml.fields().field( 5 ).mergePolicy(), Qgis::FieldDomainMergePolicy::MaximumValue ); QCOMPARE( ml.fields().field( 6 ).mergePolicy(), Qgis::FieldDomainMergePolicy::SkipAttribute ); + QCOMPARE( ml.fields().field( 7 ).mergePolicy(), Qgis::FieldDomainMergePolicy::UnsetField ); // Create features QgsFeature f1( ml.fields(), 1 ); - f1.setAttributes( QVector() << 10 << 200 << 7.5 << QStringLiteral( "smaller" ) << 10 << -10 << 0 ); + f1.setAttributes( QVector() << 10 << 200 << 7.5 << QStringLiteral( "smaller" ) << 10 << -10 << 0 << 20 ); f1.setGeometry( QgsGeometry::fromWkt( "LINESTRING(10 0, 15 0)" ) ); QVERIFY( ml.dataProvider()->addFeature( f1 ) ); QCOMPARE( ml.featureCount(), 1 ); QgsFeature f2( ml.fields(), 2 ); - f2.setAttributes( QVector() << 15 << 100 << 5 << QStringLiteral( "bigger" ) << -10 << 10 << 5 ); + f2.setAttributes( QVector() << 15 << 100 << 5 << QStringLiteral( "bigger" ) << -10 << 10 << 5 << 12 ); f2.setGeometry( QgsGeometry::fromWkt( "LINESTRING(0 0, 10 0)" ) ); QVERIFY( ml.dataProvider()->addFeature( f2 ) ); QCOMPARE( ml.featureCount(), 2 ); QgsMergeAttributesDialog dialog1( QgsFeatureList() << f1 << f2, &ml, mQgisApp->mapCanvas() ); - qDebug() << dialog1.mergedAttributes().at( 2 ).toDouble(); - QCOMPARE( dialog1.mergedAttributes().at( 0 ).toInt(), 10 ); QCOMPARE( dialog1.mergedAttributes().at( 1 ).toInt(), 300 ); QVERIFY( qgsDoubleNear( dialog1.mergedAttributes().at( 2 ).toDouble(), 5.83333, 0.00001 ) ); QCOMPARE( dialog1.mergedAttributes().at( 3 ).toString(), QStringLiteral( "bigger" ) ); - QCOMPARE( dialog1.mergedAttributes().at( 4 ).toInt(), 10 ); - QCOMPARE( dialog1.mergedAttributes().at( 5 ).toInt(), -10 ); - QCOMPARE( dialog1.mergedAttributes().at( 6 ).toString(), QStringLiteral( "Skipped" ) ); + QCOMPARE( dialog1.mergedAttributes().at( 4 ).toInt(), -10 ); + QCOMPARE( dialog1.mergedAttributes().at( 5 ).toInt(), 10 ); + QVERIFY( !dialog1.mergedAttributes().at( 6 ).isValid() ); + QCOMPARE( dialog1.mergedAttributes().at( 7 ).toInt(), 20 ); } }; From 2e2a77ac7c5cbbdd8ab07ad34062eebc009610c4 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Tue, 25 Feb 2025 16:20:38 +0200 Subject: [PATCH 11/12] SkipAttribute merge policy -> SetToNull --- python/PyQt6/core/auto_additions/qgis.py | 4 ++-- python/PyQt6/core/auto_generated/qgis.sip.in | 2 +- python/core/auto_additions/qgis.py | 4 ++-- python/core/auto_generated/qgis.sip.in | 2 +- src/app/browser/qgsinbuiltdataitemproviders.cpp | 2 +- src/app/qgsmergeattributesdialog.cpp | 2 +- src/core/qgis.h | 2 +- src/core/qgsogrutils.cpp | 2 +- src/gui/attributeformconfig/qgsattributetypedialog.cpp | 4 ++-- tests/src/app/testqgsmergeattributesdialog.cpp | 4 ++-- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index c622151fc5dd..791f3727a046 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -6932,7 +6932,7 @@ Qgis.FieldDomainMergePolicy.LargestGeometry.__doc__ = "Use value from the feature with the largest geometry \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.MinimumValue.__doc__ = "Use the minimum value from the features-to-be-merged \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.MaximumValue.__doc__ = "Use the maximum value from the features-to-be-merged \n.. versionadded:: 3.44" -Qgis.FieldDomainMergePolicy.SkipAttribute.__doc__ = "Use a null value \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.SetToNull.__doc__ = "Use a null value \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.__doc__ = """Merge policy for field domains. When a feature is built by merging multiple features, defines how the value of @@ -6959,7 +6959,7 @@ .. versionadded:: 3.44 -* ``SkipAttribute``: Use a null value +* ``SetToNull``: Use a null value .. versionadded:: 3.44 diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index b4e877333d86..3097b5eea976 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -2116,7 +2116,7 @@ The development version LargestGeometry, MinimumValue, MaximumValue, - SkipAttribute, + SetToNull, }; enum class FieldDuplicatePolicy /BaseType=IntEnum/ diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index c572cfddac2e..1f7746ded7f7 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -6866,7 +6866,7 @@ Qgis.FieldDomainMergePolicy.LargestGeometry.__doc__ = "Use value from the feature with the largest geometry \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.MinimumValue.__doc__ = "Use the minimum value from the features-to-be-merged \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.MaximumValue.__doc__ = "Use the maximum value from the features-to-be-merged \n.. versionadded:: 3.44" -Qgis.FieldDomainMergePolicy.SkipAttribute.__doc__ = "Use a null value \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.SetToNull.__doc__ = "Use a null value \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.__doc__ = """Merge policy for field domains. When a feature is built by merging multiple features, defines how the value of @@ -6893,7 +6893,7 @@ .. versionadded:: 3.44 -* ``SkipAttribute``: Use a null value +* ``SetToNull``: Use a null value .. versionadded:: 3.44 diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index 1b4f0159cd0e..ab61ce224161 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -2116,7 +2116,7 @@ The development version LargestGeometry, MinimumValue, MaximumValue, - SkipAttribute, + SetToNull, }; enum class FieldDuplicatePolicy diff --git a/src/app/browser/qgsinbuiltdataitemproviders.cpp b/src/app/browser/qgsinbuiltdataitemproviders.cpp index 19e7b2745a44..d2b54c1b44d4 100644 --- a/src/app/browser/qgsinbuiltdataitemproviders.cpp +++ b/src/app/browser/qgsinbuiltdataitemproviders.cpp @@ -2256,7 +2256,7 @@ QString QgsFieldDomainDetailsWidget::htmlMetadata( QgsFieldDomain *domain, const case Qgis::FieldDomainMergePolicy::MinimumValue: metadata += tr( "Minimum value" ); break; - case Qgis::FieldDomainMergePolicy::SkipAttribute: + case Qgis::FieldDomainMergePolicy::SetToNull: metadata += tr( "Skip attribute" ); break; } diff --git a/src/app/qgsmergeattributesdialog.cpp b/src/app/qgsmergeattributesdialog.cpp index 80dafad0ab8b..2bde12ee5f44 100644 --- a/src/app/qgsmergeattributesdialog.cpp +++ b/src/app/qgsmergeattributesdialog.cpp @@ -387,7 +387,7 @@ void QgsMergeAttributesDialog::createTableWidgetContents( bool skipAll ) break; } - case Qgis::FieldDomainMergePolicy::SkipAttribute: + case Qgis::FieldDomainMergePolicy::SetToNull: { if ( currentComboBox ) currentComboBox->setCurrentIndex( currentComboBox->findData( QStringLiteral( "skip" ) ) ); diff --git a/src/core/qgis.h b/src/core/qgis.h index a31471d4868a..9adfc6b0cdcf 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -3733,7 +3733,7 @@ class CORE_EXPORT Qgis LargestGeometry, //!< Use value from the feature with the largest geometry \since QGIS 3.44 MinimumValue, //!< Use the minimum value from the features-to-be-merged \since QGIS 3.44 MaximumValue, //!< Use the maximum value from the features-to-be-merged \since QGIS 3.44 - SkipAttribute, //!< Use a null value \since QGIS 3.44 + SetToNull, //!< Use a null value \since QGIS 3.44 }; Q_ENUM( FieldDomainMergePolicy ) diff --git a/src/core/qgsogrutils.cpp b/src/core/qgsogrutils.cpp index 876cd502d8b0..dcadd00cd563 100644 --- a/src/core/qgsogrutils.cpp +++ b/src/core/qgsogrutils.cpp @@ -2303,7 +2303,7 @@ OGRFieldDomainH QgsOgrUtils::convertFieldDomain( const QgsFieldDomain *domain ) case Qgis::FieldDomainMergePolicy::LargestGeometry: case Qgis::FieldDomainMergePolicy::MinimumValue: case Qgis::FieldDomainMergePolicy::MaximumValue: - case Qgis::FieldDomainMergePolicy::SkipAttribute: + case Qgis::FieldDomainMergePolicy::SetToNull: // not supported break; } diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.cpp b/src/gui/attributeformconfig/qgsattributetypedialog.cpp index 73ea9c1c4be1..040682fc0d1e 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.cpp +++ b/src/gui/attributeformconfig/qgsattributetypedialog.cpp @@ -143,7 +143,7 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx } mMergePolicyComboBox->addItem( tr( "Use Largest Feature" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::LargestGeometry ) ); - mMergePolicyComboBox->addItem( tr( "Skip Attribute" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::SkipAttribute ) ); + mMergePolicyComboBox->addItem( tr( "Skip Attribute" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::SetToNull ) ); } else { @@ -609,7 +609,7 @@ void QgsAttributeTypeDialog::updateMergePolicyLabel() helperText = tr( "Use the highest value from the selected features." ); break; - case Qgis::FieldDomainMergePolicy::SkipAttribute: + case Qgis::FieldDomainMergePolicy::SetToNull: helperText = tr( "Skip attribute." ); break; } diff --git a/tests/src/app/testqgsmergeattributesdialog.cpp b/tests/src/app/testqgsmergeattributesdialog.cpp index fb16c31ee070..353efdc2daa9 100644 --- a/tests/src/app/testqgsmergeattributesdialog.cpp +++ b/tests/src/app/testqgsmergeattributesdialog.cpp @@ -247,7 +247,7 @@ class TestQgsMergeattributesDialog : public QgsTest ml.setFieldMergePolicy( 3, Qgis::FieldDomainMergePolicy::LargestGeometry ); ml.setFieldMergePolicy( 4, Qgis::FieldDomainMergePolicy::MinimumValue ); ml.setFieldMergePolicy( 5, Qgis::FieldDomainMergePolicy::MaximumValue ); - ml.setFieldMergePolicy( 6, Qgis::FieldDomainMergePolicy::SkipAttribute ); + ml.setFieldMergePolicy( 6, Qgis::FieldDomainMergePolicy::SetToNull ); ml.setFieldMergePolicy( 7, Qgis::FieldDomainMergePolicy::UnsetField ); // verify that policies have been correctly set @@ -258,7 +258,7 @@ class TestQgsMergeattributesDialog : public QgsTest QCOMPARE( ml.fields().field( 3 ).mergePolicy(), Qgis::FieldDomainMergePolicy::LargestGeometry ); QCOMPARE( ml.fields().field( 4 ).mergePolicy(), Qgis::FieldDomainMergePolicy::MinimumValue ); QCOMPARE( ml.fields().field( 5 ).mergePolicy(), Qgis::FieldDomainMergePolicy::MaximumValue ); - QCOMPARE( ml.fields().field( 6 ).mergePolicy(), Qgis::FieldDomainMergePolicy::SkipAttribute ); + QCOMPARE( ml.fields().field( 6 ).mergePolicy(), Qgis::FieldDomainMergePolicy::SetToNull ); QCOMPARE( ml.fields().field( 7 ).mergePolicy(), Qgis::FieldDomainMergePolicy::UnsetField ); // Create features From 7b51a6b94f68fe4bf613ddac625322452f0b7018 Mon Sep 17 00:00:00 2001 From: Juho Ervasti Date: Tue, 25 Feb 2025 16:21:58 +0200 Subject: [PATCH 12/12] Fix QGIS version in \since command --- src/gui/attributeformconfig/qgsattributetypedialog.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.h b/src/gui/attributeformconfig/qgsattributetypedialog.h index 634fe115f2d9..88e8b9ec80f5 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.h +++ b/src/gui/attributeformconfig/qgsattributetypedialog.h @@ -276,7 +276,7 @@ class GUI_EXPORT QgsAttributeTypeDialog : public QWidget, private Ui::QgsAttribu * * \see setMergePolicy() * - * \since QGIS 3.42 + * \since QGIS 3.44 */ Qgis::FieldDomainMergePolicy mergePolicy() const; @@ -285,7 +285,7 @@ class GUI_EXPORT QgsAttributeTypeDialog : public QWidget, private Ui::QgsAttribu * * \see mergePolicy() * - * \since QGIS 3.42 + * \since QGIS 3.44 */ void setMergePolicy( Qgis::FieldDomainMergePolicy policy );