Skip to content

Commit

Permalink
Fix issues for multiediting and updating for attribute form with more…
Browse files Browse the repository at this point in the history
… widgets pointing to the same field (#50410)
  • Loading branch information
domi4484 committed Nov 18, 2022
1 parent 26277ba commit f469f46
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 76 deletions.
134 changes: 65 additions & 69 deletions src/gui/qgsattributeform.cpp
Expand Up @@ -750,23 +750,25 @@ bool QgsAttributeForm::saveMultiEdits()
{
//find changed attributes
QgsAttributeMap newAttributeValues;
QMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
const QList<int> fieldIndexes = mFormEditorWidgets.uniqueKeys();
mFormEditorWidgets.constBegin();
for ( int fieldIndex : fieldIndexes )
{
QgsAttributeFormEditorWidget *w = wIt.value();
if ( !w->hasChanged() )
const QList<QgsAttributeFormEditorWidget *> widgets = mFormEditorWidgets.values( fieldIndex );
if ( !widgets.first()->hasChanged() )
continue;

if ( !w->currentValue().isValid() // if the widget returns invalid (== do not change)
|| !fieldIsEditable( wIt.key() ) ) // or the field cannot be edited ...
if ( !widgets.first()->currentValue().isValid() // if the widget returns invalid (== do not change)
|| !fieldIsEditable( fieldIndex ) ) // or the field cannot be edited ...
{
continue;
}

// let editor know we've accepted the changes
w->changesCommitted();
for ( QgsAttributeFormEditorWidget *widget : widgets )
widget->changesCommitted();

newAttributeValues.insert( wIt.key(), w->currentValue() );
newAttributeValues.insert( fieldIndex, widgets.first()->currentValue() );
}

if ( newAttributeValues.isEmpty() )
Expand Down Expand Up @@ -1043,6 +1045,15 @@ void QgsAttributeForm::onAttributeChanged( const QVariant &value, const QVariant
// Updates expression controlled labels
updateLabels();

// Update other widgets pointing to the same field
const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->fieldIdx() );
for ( QgsAttributeFormEditorWidget *formEditorWidget : formEditorWidgets )
{
if ( formEditorWidget->editorWidget() == eww )
continue;
formEditorWidget->editorWidget()->setValue( value );
}

if ( !signalEmitted )
{
Q_NOWARN_DEPRECATED_PUSH
Expand Down Expand Up @@ -1355,9 +1366,9 @@ void QgsAttributeForm::onConstraintStatusChanged( const QString &constraint,
QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
Q_ASSERT( eww );

QgsAttributeFormEditorWidget *formEditorWidget = mFormEditorWidgets.value( eww->fieldIdx() );
const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( eww->fieldIdx() );

if ( formEditorWidget )
for ( QgsAttributeFormEditorWidget *formEditorWidget : formEditorWidgets )
formEditorWidget->setConstraintStatus( constraint, description, err, result );
}

Expand Down Expand Up @@ -1460,9 +1471,9 @@ void QgsAttributeForm::synchronizeState()
QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
if ( eww )
{
QgsAttributeFormEditorWidget *formWidget = mFormEditorWidgets.value( eww->fieldIdx() );
const QList<QgsAttributeFormEditorWidget *> formWidgets = mFormEditorWidgets.values( eww->fieldIdx() );

if ( formWidget )
for ( QgsAttributeFormEditorWidget *formWidget : formWidgets )
formWidget->setConstraintResultVisible( isEditable );

eww->setConstraintResultVisible( isEditable );
Expand Down Expand Up @@ -1877,7 +1888,7 @@ void QgsAttributeForm::init()

if ( eww )
{
addWidgetWrapper( eww );
mWidgets.append( eww );
mIconMap[eww->widget()] = i;
}

Expand Down Expand Up @@ -2222,7 +2233,7 @@ QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAtt
formWidget->createSearchWidgetWrappers( mContext );

newWidgetInfo.widget = formWidget;
addWidgetWrapper( eww );
mWidgets.append( eww );

newWidgetInfo.widget->setObjectName( fields.at( fldIdx ).name() );
newWidgetInfo.hint = fields.at( fldIdx ).comment();
Expand Down Expand Up @@ -2484,27 +2495,6 @@ QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAtt
return newWidgetInfo;
}

void QgsAttributeForm::addWidgetWrapper( QgsEditorWidgetWrapper *eww )
{
for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
{
QgsEditorWidgetWrapper *meww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
if ( meww )
{
// if another widget wrapper exists for the same field
// synchronise them
if ( meww->field() == eww->field() )
{
connect( meww, &QgsEditorWidgetWrapper::valuesChanged, eww, &QgsEditorWidgetWrapper::setValues );
connect( eww, &QgsEditorWidgetWrapper::valuesChanged, meww, &QgsEditorWidgetWrapper::setValues );
break;
}
}
}

mWidgets.append( eww );
}

void QgsAttributeForm::createWrappers()
{
QList<QWidget *> myWidgets = findChildren<QWidget *>();
Expand Down Expand Up @@ -2538,7 +2528,7 @@ void QgsAttributeForm::createWrappers()
int idx = mLayer->fields().lookupField( field.name() );

QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( mLayer, idx, myWidget, this, mContext );
addWidgetWrapper( eww );
mWidgets.append( eww );
}
}
}
Expand Down Expand Up @@ -2661,7 +2651,7 @@ void QgsAttributeForm::setMultiEditFeatureIds( const QgsFeatureIds &fids )
if ( fids.isEmpty() )
{
// no selected features
QMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
QMultiMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
{
wIt.value()->initialize( QVariant() );
Expand All @@ -2685,49 +2675,55 @@ void QgsAttributeForm::setMultiEditFeatureIds( const QgsFeatureIds &fids )
const auto constMixedValueFields = mixedValueFields;
for ( int fieldIndex : std::as_const( mixedValueFields ) )
{
if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( fieldIndex, nullptr ) )
{
const QStringList additionalFields = w->editorWidget()->additionalFields();
QVariantList additionalFieldValues;
for ( const QString &additionalField : additionalFields )
additionalFieldValues << firstFeature.attribute( additionalField );
const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( fieldIndex );
if ( formEditorWidgets.isEmpty() )
continue;

const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
QVariantList additionalFieldValues;
for ( const QString &additionalField : additionalFields )
additionalFieldValues << firstFeature.attribute( additionalField );

for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
w->initialize( firstFeature.attribute( fieldIndex ), true, additionalFieldValues );
}
}
QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
{
if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( sharedValueIt.key(), nullptr ) )
const QList<QgsAttributeFormEditorWidget *> formEditorWidgets = mFormEditorWidgets.values( sharedValueIt.key() );
if ( formEditorWidgets.isEmpty() )
continue;

bool mixed = false;
const QStringList additionalFields = formEditorWidgets.first()->editorWidget()->additionalFields();
for ( const QString &additionalField : additionalFields )
{
bool mixed = false;
const QStringList additionalFields = w->editorWidget()->additionalFields();
for ( const QString &additionalField : additionalFields )
int index = mLayer->fields().indexFromName( additionalField );
if ( constMixedValueFields.contains( index ) )
{
int index = mLayer->fields().indexFromName( additionalField );
if ( constMixedValueFields.contains( index ) )
{
// if additional field are mixed, it is considered as mixed
mixed = true;
break;
}
// if additional field are mixed, it is considered as mixed
mixed = true;
break;
}
QVariantList additionalFieldValues;
if ( mixed )
{
for ( const QString &additionalField : additionalFields )
additionalFieldValues << firstFeature.attribute( additionalField );
}
QVariantList additionalFieldValues;
if ( mixed )
{
for ( const QString &additionalField : additionalFields )
additionalFieldValues << firstFeature.attribute( additionalField );
for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
w->initialize( firstFeature.attribute( sharedValueIt.key() ), true, additionalFieldValues );
}
else
}
else
{
for ( const QString &additionalField : additionalFields )
{
for ( const QString &additionalField : additionalFields )
{
int index = mLayer->fields().indexFromName( additionalField );
Q_ASSERT( fieldSharedValues.contains( index ) );
additionalFieldValues << fieldSharedValues.value( index );
}
w->initialize( sharedValueIt.value(), false, additionalFieldValues );
int index = mLayer->fields().indexFromName( additionalField );
Q_ASSERT( fieldSharedValues.contains( index ) );
additionalFieldValues << fieldSharedValues.value( index );
}
for ( QgsAttributeFormEditorWidget *w : formEditorWidgets )
w->initialize( sharedValueIt.value(), false, additionalFieldValues );
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/gui/qgsattributeform.h
Expand Up @@ -408,8 +408,6 @@ class GUI_EXPORT QgsAttributeForm : public QWidget

WidgetInfo createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context );

void addWidgetWrapper( QgsEditorWidgetWrapper *eww );

/**
* Creates widget wrappers for all suitable widgets found.
* Called once maximally.
Expand Down Expand Up @@ -465,7 +463,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
QDialogButtonBox *mButtonBox = nullptr;
QWidget *mSearchButtonBox = nullptr;
QList<QgsAttributeFormInterface *> mInterfaces;
QMap< int, QgsAttributeFormEditorWidget * > mFormEditorWidgets;
QMultiMap< int, QgsAttributeFormEditorWidget * > mFormEditorWidgets;
QList< QgsAttributeFormWidget *> mFormWidgets;
QMap<const QgsVectorLayerJoinInfo *, QgsFeature> mJoinedFeatures;
QMap<QLabel *, QgsProperty> mLabelDataDefinedProperties;
Expand Down
46 changes: 42 additions & 4 deletions tests/src/python/test_qgsattributeform.py
Expand Up @@ -16,21 +16,19 @@

from qgis.testing import start_app, unittest
from qgis.core import (
QgsFields,
QgsVectorLayer,
QgsFeature,
QgsEditorWidgetSetup,
QgsEditFormConfig,
QgsAttributeEditorElement,
QgsDefaultValue,
QgsField
)
from qgis.gui import (
QgsAttributeForm,
QgsGui,
QgsEditorWidgetWrapper,
QgsMapCanvas,
QgsAttributeEditorContext
QgsAttributeEditorContext,
QgsFilterLineEdit
)
from qgis.PyQt.QtCore import QVariant

Expand Down Expand Up @@ -119,6 +117,46 @@ def test_duplicated_widgets(self):
for field_type, value in field_types.items():
self.checkForm(field_type, value)

def test_duplicated_widgets_multiedit(self):
"""
Test multiedit with duplicated widgets
"""

field_type = 'integer'
vl = self.createLayerWithOnePoint(field_type)

# add another point
pr = vl.dataProvider()
f = QgsFeature()
assert pr.addFeatures([f])
assert vl.featureCount() == 2

assert vl.startEditing()

assert vl.changeAttributeValue(1, 0, 123)
assert vl.changeAttributeValue(2, 0, 456)

widget_type = 'TextEdit'
form = self.createFormWithDuplicateWidget(vl, field_type, widget_type)

fids = list()
for feature in vl.getFeatures():
fids.append(feature.id())

form.setMode(QgsAttributeEditorContext.MultiEditMode)
form.setMultiEditFeatureIds(fids)

for children in form.findChildren(QgsFilterLineEdit):
if children.objectName() == 'fld':
# As the values are mixed, the widget values should be empty
assert not children.text()

# After save the values should be unchanged
form.save()
featuresIterator = vl.getFeatures()
self.assertEqual(next(featuresIterator).attribute(0), 123)
self.assertEqual(next(featuresIterator).attribute(0), 456)

def test_on_update(self):
"""Test live update"""

Expand Down

0 comments on commit f469f46

Please sign in to comment.