Merge pull request #4795 from pblottiere/dynamicform

[FEATURE] Dynamic form for joined fields
This commit is contained in:
Matthias Kuhn 2017-07-07 08:22:18 +02:00 committed by GitHub
commit 632eca6079
20 changed files with 438 additions and 26 deletions

View File

@ -99,6 +99,24 @@ Quick way to test if there is any join at all
:rtype: list of int
%End
QList<const QgsVectorLayerJoinInfo *> joinsWhereFieldIsId( const QgsField &field ) const;
%Docstring
Returns joins where the field of a target layer is considered as an id.
\param field the field of a target layer
:return: a list of vector joins
.. versionadded:: 3.0
:rtype: list of const QgsVectorLayerJoinInfo
%End
QgsFeature joinedFeatureOf( const QgsVectorLayerJoinInfo *info, const QgsFeature &feature ) const;
%Docstring
Returns the joined feature corresponding to the feature.
\param info the vector join information
\param feature the feature of the target layer
.. versionadded:: 3.0
:rtype: QgsFeature
%End
QgsVectorLayerJoinBuffer *clone() const /Factory/;
%Docstring
.. versionadded:: 2.6

View File

@ -84,6 +84,30 @@ Returns whether values from the joined layer should be cached in memory to speed
:rtype: bool
%End
bool isDynamicFormEnabled() const;
%Docstring
Returns whether the form has to be dynamically updated with joined fields
when a feature is being created in the target layer.
.. versionadded:: 3.0
:rtype: bool
%End
void setDynamicFormEnabled( bool enabled );
%Docstring
Sets whether the form has to be dynamically updated with joined fields
when a feature is being created in the target layer.
.. versionadded:: 3.0
%End
QString prefixedFieldName( const QgsField &field ) const;
%Docstring
Returns the prefixed name of the field.
\param field the field
:return: the prefixed name of the field
.. versionadded:: 3.0
:rtype: str
%End
bool operator==( const QgsVectorLayerJoinInfo &other ) const;
void setJoinFieldNamesSubset( QStringList *fieldNamesSubset /Transfer/ );
@ -108,6 +132,7 @@ Returns whether values from the joined layer should be cached in memory to speed
};

View File

@ -156,6 +156,13 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
:rtype: str
%End
virtual void setHint( const QString &hintText );
%Docstring
Add a hint text on the widget
\param hintText The hint text to display
.. versionadded:: 3.0
%End
signals:
void valueChanged( const QVariant &value );

View File

@ -167,7 +167,7 @@ class QgsAttributeForm : QWidget
public slots:
void changeAttribute( const QString &field, const QVariant &value );
void changeAttribute( const QString &field, const QVariant &value, const QString &hintText = QString() );
%Docstring
Call this to change the content of a given attribute. Will update the editor(s) related to this field.

View File

@ -42,6 +42,8 @@ QgsJoinDialog::QgsJoinDialog( QgsVectorLayer *layer, QList<QgsMapLayer *> alread
mTargetFieldComboBox->setLayer( mLayer );
mDynamicFormCheckBox->setToolTip( tr( "This option allows values of the joined fields to be automatically reloaded when the \"Target Field\" is changed" ) );
mJoinLayerComboBox->setFilters( QgsMapLayerProxyModel::VectorLayer );
mJoinLayerComboBox->setExceptedLayerList( alreadyJoinedLayers );
connect( mJoinLayerComboBox, &QgsMapLayerComboBox::layerChanged, mJoinFieldComboBox, &QgsFieldComboBox::setLayer );
@ -73,6 +75,7 @@ void QgsJoinDialog::setJoinInfo( const QgsVectorLayerJoinInfo &joinInfo )
mJoinFieldComboBox->setField( joinInfo.joinFieldName() );
mTargetFieldComboBox->setField( joinInfo.targetFieldName() );
mCacheInMemoryCheckBox->setChecked( joinInfo.isUsingMemoryCache() );
mDynamicFormCheckBox->setChecked( joinInfo.isDynamicFormEnabled() );
if ( joinInfo.prefix().isNull() )
{
mUseCustomPrefix->setChecked( false );
@ -110,6 +113,7 @@ QgsVectorLayerJoinInfo QgsJoinDialog::joinInfo() const
info.setJoinFieldName( mJoinFieldComboBox->currentField() );
info.setTargetFieldName( mTargetFieldComboBox->currentField() );
info.setUsingMemoryCache( mCacheInMemoryCheckBox->isChecked() );
info.setDynamicFormEnabled( mDynamicFormCheckBox->isChecked() );
if ( mUseCustomPrefix->isChecked() )
info.setPrefix( mCustomPrefix->text() );

View File

@ -1235,16 +1235,21 @@ void QgsVectorLayerProperties::addJoinToTreeWidget( const QgsVectorLayerJoinInfo
joinItem->setText( 3, QChar( 0x2714 ) );
}
joinItem->setText( 4, join.prefix() );
if ( join.isDynamicFormEnabled() )
{
joinItem->setText( 4, QChar( 0x2714 ) );
}
joinItem->setText( 5, join.prefix() );
const QStringList *list = join.joinFieldNamesSubset();
if ( list )
{
joinItem->setText( 5, QStringLiteral( "%1" ).arg( list->count() ) );
joinItem->setText( 6, QStringLiteral( "%1" ).arg( list->count() ) );
}
else
{
joinItem->setText( 5, tr( "all" ) );
joinItem->setText( 6, tr( "all" ) );
}
if ( insertIndex >= 0 )
@ -1255,7 +1260,7 @@ void QgsVectorLayerProperties::addJoinToTreeWidget( const QgsVectorLayerJoinInfo
{
mJoinTreeWidget->addTopLevelItem( joinItem );
}
for ( int c = 0; c < 5; c++ )
for ( int c = 0; c < 6; c++ )
{
mJoinTreeWidget->resizeColumnToContents( c );
}

View File

@ -286,6 +286,7 @@ SET(QGIS_CORE_SRCS
qgsvectorlayerfeatureiterator.cpp
qgsvectorlayerexporter.cpp
qgsvectorlayerjoinbuffer.cpp
qgsvectorlayerjoininfo.cpp
qgsvectorlayerlabeling.cpp
qgsvectorlayerlabelprovider.cpp
qgsvectorlayerrenderer.cpp

View File

@ -275,6 +275,7 @@ void QgsVectorLayerJoinBuffer::writeXml( QDomNode &layer_node, QDomDocument &doc
joinElem.setAttribute( QStringLiteral( "joinFieldName" ), joinIt->joinFieldName() );
joinElem.setAttribute( QStringLiteral( "memoryCache" ), joinIt->isUsingMemoryCache() );
joinElem.setAttribute( QStringLiteral( "dynamicForm" ), joinIt->isDynamicFormEnabled() );
if ( joinIt->joinFieldNamesSubset() )
{
@ -315,6 +316,7 @@ void QgsVectorLayerJoinBuffer::readXml( const QDomNode &layer_node )
info.setJoinLayerId( infoElem.attribute( QStringLiteral( "joinLayerId" ) ) );
info.setTargetFieldName( infoElem.attribute( QStringLiteral( "targetFieldName" ) ) );
info.setUsingMemoryCache( infoElem.attribute( QStringLiteral( "memoryCache" ) ).toInt() );
info.setDynamicFormEnabled( infoElem.attribute( QStringLiteral( "dynamicForm" ) ).toInt() );
QDomElement subsetElem = infoElem.firstChildElement( QStringLiteral( "joinFieldsSubset" ) );
if ( !subsetElem.isNull() )
@ -392,6 +394,44 @@ const QgsVectorLayerJoinInfo *QgsVectorLayerJoinBuffer::joinForFieldIndex( int i
return &( mVectorJoins[sourceJoinIndex] );
}
QList<const QgsVectorLayerJoinInfo *> QgsVectorLayerJoinBuffer::joinsWhereFieldIsId( const QgsField &field ) const
{
QList<const QgsVectorLayerJoinInfo *> infos;
Q_FOREACH ( const QgsVectorLayerJoinInfo &info, mVectorJoins )
{
if ( infos.contains( &info ) )
continue;
if ( info.targetFieldName() == field.name() )
infos.append( &info );
}
return infos;
}
QgsFeature QgsVectorLayerJoinBuffer::joinedFeatureOf( const QgsVectorLayerJoinInfo *info, const QgsFeature &feature ) const
{
QgsFeature joinedFeature;
if ( info->joinLayer() )
{
const QVariant targetValue = feature.attribute( info->targetFieldName() );
QString fieldRef = QgsExpression::quotedColumnRef( info->joinFieldName() );
QString quotedVal = QgsExpression::quotedValue( targetValue.toString() );
const QString filter = QStringLiteral( "%1 = %2" ).arg( fieldRef, quotedVal );
QgsFeatureRequest request;
request.setFilterExpression( filter );
request.setLimit( 1 );
QgsFeatureIterator it = info->joinLayer()->getFeatures( request );
it.nextFeature( joinedFeature );
}
return joinedFeature;
}
QgsVectorLayerJoinBuffer *QgsVectorLayerJoinBuffer::clone() const
{
QgsVectorLayerJoinBuffer *cloned = new QgsVectorLayerJoinBuffer( mLayer );

View File

@ -84,6 +84,20 @@ class CORE_EXPORT QgsVectorLayerJoinBuffer : public QObject
//! \since QGIS 2.6
static QVector<int> joinSubsetIndices( QgsVectorLayer *joinLayer, const QStringList &joinFieldsSubset );
/** Returns joins where the field of a target layer is considered as an id.
* \param field the field of a target layer
* \returns a list of vector joins
* \since QGIS3.0
*/
QList<const QgsVectorLayerJoinInfo *> joinsWhereFieldIsId( const QgsField &field ) const;
/** Returns the joined feature corresponding to the feature.
* \param info the vector join information
* \param feature the feature of the target layer
* \since QGIS 3.0
*/
QgsFeature joinedFeatureOf( const QgsVectorLayerJoinInfo *info, const QgsFeature &feature ) const;
//! Create a copy of the join buffer
//! \since QGIS 2.6
QgsVectorLayerJoinBuffer *clone() const SIP_FACTORY;

View File

@ -0,0 +1,35 @@
/***************************************************************************
qgsvectorlayerjoininfo.cpp
--------------------------
begin : Jun 29, 2017
copyright : (C) 2017 by Paul Blottiere
email : paul.blottiere@oslandia.com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsvectorlayerjoininfo.h"
QString QgsVectorLayerJoinInfo::prefixedFieldName( const QgsField &f ) const
{
QString name;
if ( joinLayer() )
{
if ( prefix().isNull() )
name = joinLayer()->name() + '_';
else
name = prefix();
name += f.name();
}
return name;
}

View File

@ -68,6 +68,25 @@ class CORE_EXPORT QgsVectorLayerJoinInfo
//! Returns whether values from the joined layer should be cached in memory to speed up lookups
bool isUsingMemoryCache() const { return mMemoryCache; }
/** Returns whether the form has to be dynamically updated with joined fields
* when a feature is being created in the target layer.
* \since QGIS 3.0
*/
bool isDynamicFormEnabled() const { return mDynamicForm; }
/** Sets whether the form has to be dynamically updated with joined fields
* when a feature is being created in the target layer.
* \since QGIS 3.0
*/
void setDynamicFormEnabled( bool enabled ) { mDynamicForm = enabled; }
/** Returns the prefixed name of the field.
* \param field the field
* \returns the prefixed name of the field
* \since QGIS 3.0
*/
QString prefixedFieldName( const QgsField &field ) const;
bool operator==( const QgsVectorLayerJoinInfo &other ) const
{
return mTargetFieldName == other.mTargetFieldName &&
@ -113,6 +132,8 @@ class CORE_EXPORT QgsVectorLayerJoinInfo
//! True if the cached join attributes need to be updated
bool cacheDirty;
bool mDynamicForm;
//! Cache for joined attributes to provide fast lookup (size is 0 if no memory caching)
QHash< QString, QgsAttributes> cachedAttributes;

View File

@ -211,3 +211,8 @@ bool QgsEditorWidgetWrapper::isInTable( const QWidget *parent )
if ( qobject_cast<const QTableView *>( parent ) ) return true;
return isInTable( parent->parentWidget() );
}
void QgsEditorWidgetWrapper::setHint( const QString &hintText )
{
widget()->setToolTip( hintText );
}

View File

@ -164,6 +164,13 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
*/
QString constraintFailureReason() const;
/**
* Add a hint text on the widget
* \param hintText The hint text to display
* \since QGIS 3.0
*/
virtual void setHint( const QString &hintText );
signals:
/**

View File

@ -240,3 +240,16 @@ void QgsTextEditWrapper::setWidgetValue( const QVariant &val )
if ( mLineEdit )
mLineEdit->setText( v );
}
void QgsTextEditWrapper::setHint( const QString &hintText )
{
if ( hintText.isNull() )
mPlaceholderText = mPlaceholderTextBackup;
else
{
mPlaceholderTextBackup = mPlaceholderText;
mPlaceholderText = hintText;
}
mLineEdit->setPlaceholderText( mPlaceholderText );
}

View File

@ -47,6 +47,13 @@ class GUI_EXPORT QgsTextEditWrapper : public QgsEditorWidgetWrapper
QVariant value() const override;
void showIndeterminateState() override;
/**
* Add a hint text on the widget
* \param hintText The hint text to display
* \since QGIS 3.0
*/
void setHint( const QString &hintText ) override;
protected:
QWidget *createWidget( QWidget *parent ) override;
void initWidget( QWidget *editor ) override;
@ -66,6 +73,7 @@ class GUI_EXPORT QgsTextEditWrapper : public QgsEditorWidgetWrapper
QPalette mReadOnlyPalette;
QPalette mWritablePalette;
QString mPlaceholderText;
QString mPlaceholderTextBackup;
void setWidgetValue( const QVariant &value );
};

View File

@ -33,6 +33,8 @@
#include "qgssettings.h"
#include "qgsscrollarea.h"
#include "qgsgui.h"
#include "qgsvectorlayerjoinbuffer.h"
#include "qgsvectorlayerutils.h"
#include <QDir>
#include <QTextStream>
@ -221,7 +223,7 @@ void QgsAttributeForm::setMode( QgsAttributeForm::Mode mode )
emit modeChanged( mMode );
}
void QgsAttributeForm::changeAttribute( const QString &field, const QVariant &value )
void QgsAttributeForm::changeAttribute( const QString &field, const QVariant &value, const QString &hintText )
{
Q_FOREACH ( QgsWidgetWrapper *ww, mWidgets )
{
@ -229,6 +231,7 @@ void QgsAttributeForm::changeAttribute( const QString &field, const QVariant &va
if ( eww && eww->field().name() == field )
{
eww->setValue( value );
eww->setHint( hintText );
}
}
}
@ -659,6 +662,9 @@ void QgsAttributeForm::onAttributeChanged( const QVariant &value )
{
emit attributeChanged( eww->field().name(), value );
}
updateJoinedFields( *eww );
break;
}
case MultiEditMode:
@ -1930,3 +1936,58 @@ void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext *expres
isVisible = newVisibility;
}
}
void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww )
{
QgsFeature formFeature;
QgsField field = eww.layer()->fields().field( eww.fieldIdx() );
QList<const QgsVectorLayerJoinInfo *> infos = eww.layer()->joinBuffer()->joinsWhereFieldIsId( field );
if ( infos.count() == 0 || !currentFormFeature( formFeature ) )
return;
const QString hint = tr( "No feature joined" );
Q_FOREACH ( const QgsVectorLayerJoinInfo *info, infos )
{
if ( !info->isDynamicFormEnabled() )
continue;
QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature );
QStringList *subsetFields = info->joinFieldNamesSubset();
if ( subsetFields )
{
Q_FOREACH ( const QString &field, *subsetFields )
{
QString prefixedName = info->prefixedFieldName( field );
QVariant val;
QString hintText = hint;
if ( joinFeature.isValid() )
{
val = joinFeature.attribute( field );
hintText.clear();
}
changeAttribute( prefixedName, val, hintText );
}
}
else
{
Q_FOREACH ( const QgsField &field, joinFeature.fields() )
{
QString prefixedName = info->prefixedFieldName( field );
QVariant val;
QString hintText = hint;
if ( joinFeature.isValid() )
{
val = joinFeature.attribute( field.name() );
hintText.clear();
}
changeAttribute( prefixedName, val, hintText );
}
}
}
}

View File

@ -209,7 +209,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
* \param field The field to change
* \param value The new value
*/
void changeAttribute( const QString &field, const QVariant &value );
void changeAttribute( const QString &field, const QVariant &value, const QString &hintText = QString() );
/**
* Update all editors to correspond to a different feature.
@ -273,6 +273,8 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
void initPython();
void updateJoinedFields( const QgsEditorWidgetWrapper &eww );
struct WidgetInfo
{
WidgetInfo()

View File

@ -44,10 +44,10 @@
<item row="2" column="1">
<widget class="QgsFieldComboBox" name="mTargetFieldComboBox"/>
</item>
<item row="5" column="0" colspan="2">
<item row="6" column="0" colspan="2">
<widget class="QgsCollapsibleGroupBox" name="mUseJoinFieldsSubset">
<property name="title">
<string>Choose which fields are joined</string>
<string>Choose which fields are &amp;joined</string>
</property>
<property name="checkable">
<bool>true</bool>
@ -65,10 +65,10 @@
</layout>
</widget>
</item>
<item row="6" column="0" colspan="2">
<item row="7" column="0" colspan="2">
<widget class="QgsCollapsibleGroupBox" name="mUseCustomPrefix">
<property name="title">
<string>Custom field name prefix</string>
<string>Custom field &amp;name prefix</string>
</property>
<property name="checkable">
<bool>true</bool>
@ -86,7 +86,7 @@
</layout>
</widget>
</item>
<item row="7" column="0">
<item row="8" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -116,7 +116,7 @@
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<item row="9" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -126,6 +126,13 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="mDynamicFormCheckBox">
<property name="text">
<string>Dynamic form</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>

View File

@ -303,7 +303,7 @@
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
<number>9</number>
</property>
<widget class="QWidget" name="mOptsPage_Information">
<layout class="QVBoxLayout" name="verticalLayout_5">
@ -379,8 +379,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>639</width>
<height>592</height>
<width>653</width>
<height>542</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
@ -831,7 +831,7 @@ border-radius: 2px;</string>
<x>0</x>
<y>0</y>
<width>653</width>
<height>536</height>
<height>542</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
@ -1069,7 +1069,7 @@ border-radius: 2px;</string>
<x>0</x>
<y>0</y>
<width>653</width>
<height>536</height>
<height>542</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_18">
@ -1170,7 +1170,7 @@ border-radius: 2px;</string>
<x>0</x>
<y>0</y>
<width>653</width>
<height>536</height>
<height>542</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_20">
@ -1229,8 +1229,8 @@ border-radius: 2px;</string>
<rect>
<x>0</x>
<y>0</y>
<width>671</width>
<height>522</height>
<width>653</width>
<height>542</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_32">
@ -1249,7 +1249,7 @@ border-radius: 2px;</string>
<item>
<widget class="QgsCollapsibleGroupBox" name="mScaleVisibilityGroupBox">
<property name="title">
<string>Scale dependent visibility</string>
<string>Scale dependen&amp;t visibility</string>
</property>
<property name="checkable">
<bool>true</bool>
@ -1268,7 +1268,7 @@ border-radius: 2px;</string>
<item>
<widget class="QGroupBox" name="mSimplifyDrawingGroupBox">
<property name="title">
<string>Simplify geometry</string>
<string>Simplify &amp;geometry</string>
</property>
<property name="checkable">
<bool>true</bool>
@ -1590,7 +1590,7 @@ border-radius: 2px;</string>
<x>0</x>
<y>0</y>
<width>653</width>
<height>536</height>
<height>542</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_21">
@ -1656,7 +1656,7 @@ border-radius: 2px;</string>
<x>0</x>
<y>0</y>
<width>653</width>
<height>536</height>
<height>542</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_23">
@ -1675,7 +1675,7 @@ border-radius: 2px;</string>
<item>
<widget class="QTreeWidget" name="mJoinTreeWidget">
<property name="columnCount">
<number>6</number>
<number>7</number>
</property>
<column>
<property name="text">
@ -1697,6 +1697,11 @@ border-radius: 2px;</string>
<string>Memory cache</string>
</property>
</column>
<column>
<property name="text">
<string>Dynamic form</string>
</property>
</column>
<column>
<property name="text">
<string>Prefix</string>

View File

@ -24,6 +24,7 @@
#include <qgsvectorlayer.h>
#include "qgsvectordataprovider.h"
#include <qgsfeature.h>
#include <qgsvectorlayerjoininfo.h>
#include "qgsgui.h"
class TestQgsAttributeForm : public QObject
@ -41,6 +42,7 @@ class TestQgsAttributeForm : public QObject
void testFieldConstraint();
void testFieldMultiConstraints();
void testOKButtonStatus();
void testDynamicForm();
};
void TestQgsAttributeForm::initTestCase()
@ -306,5 +308,137 @@ void TestQgsAttributeForm::testOKButtonStatus()
QVERIFY( !okButton->isEnabled() );
}
void TestQgsAttributeForm::testDynamicForm()
{
// make temporary layers
QString defA = QStringLiteral( "Point?field=id_a:integer" );
QgsVectorLayer *layerA = new QgsVectorLayer( defA, QStringLiteral( "layerA" ), QStringLiteral( "memory" ) );
QString defB = QStringLiteral( "Point?field=id_b:integer&field=col0:integer" );
QgsVectorLayer *layerB = new QgsVectorLayer( defB, QStringLiteral( "layerB" ), QStringLiteral( "memory" ) );
QString defC = QStringLiteral( "Point?field=id_c:integer&field=col0:integer" );
QgsVectorLayer *layerC = new QgsVectorLayer( defC, QStringLiteral( "layerC" ), QStringLiteral( "memory" ) );
// join configuration
QgsVectorLayerJoinInfo infoJoinAB;
infoJoinAB.setTargetFieldName( "id_a" );
infoJoinAB.setJoinLayer( layerB );
infoJoinAB.setJoinFieldName( "id_b" );
infoJoinAB.setDynamicFormEnabled( true );
layerA->addJoin( infoJoinAB );
QgsVectorLayerJoinInfo infoJoinAC;
infoJoinAC.setTargetFieldName( "id_a" );
infoJoinAC.setJoinLayer( layerC );
infoJoinAC.setJoinFieldName( "id_c" );
infoJoinAC.setDynamicFormEnabled( true );
layerA->addJoin( infoJoinAC );
// add features for main layer
QgsFeature ftA( layerA->fields() );
ftA.setAttribute( QStringLiteral( "id_a" ), 0 );
layerA->startEditing();
layerA->addFeature( ftA );
layerA->commitChanges();
// add features for joined layers
QgsFeature ft0B( layerB->fields() );
ft0B.setAttribute( QStringLiteral( "id_b" ), 30 );
ft0B.setAttribute( QStringLiteral( "col0" ), 10 );
layerB->startEditing();
layerB->addFeature( ft0B );
layerB->commitChanges();
QgsFeature ft1B( layerB->fields() );
ft1B.setAttribute( QStringLiteral( "id_b" ), 31 );
ft1B.setAttribute( QStringLiteral( "col0" ), 11 );
layerB->startEditing();
layerB->addFeature( ft1B );
layerB->commitChanges();
QgsFeature ft0C( layerC->fields() );
ft0C.setAttribute( QStringLiteral( "id_c" ), 32 );
ft0C.setAttribute( QStringLiteral( "col0" ), 12 );
layerC->startEditing();
layerC->addFeature( ft0C );
layerC->commitChanges();
QgsFeature ft1C( layerC->fields() );
ft1C.setAttribute( QStringLiteral( "id_c" ), 31 );
ft1C.setAttribute( QStringLiteral( "col0" ), 13 );
layerC->startEditing();
layerC->addFeature( ft1C );
layerC->commitChanges();
// build a form with feature A
QgsAttributeForm form( layerA );
form.setMode( QgsAttributeForm::AddFeatureMode );
form.setFeature( ftA );
// test that there's no joined feature by default
QgsEditorWidgetWrapper *ww = nullptr;
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
QCOMPARE( ww->field().name(), QString( "layerB_col0" ) );
QCOMPARE( ww->value(), QVariant( QVariant::Int ) );
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
QCOMPARE( ww->field().name(), QString( "layerC_col0" ) );
QCOMPARE( ww->value(), QVariant( QVariant::Int ) );
// change layerA join id field to join with layerB
form.changeAttribute( "id_a", QVariant( 30 ) );
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
QCOMPARE( ww->field().name(), QString( "id_a" ) );
QCOMPARE( ww->value(), QVariant( 30 ) );
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
QCOMPARE( ww->field().name(), QString( "layerB_col0" ) );
QCOMPARE( ww->value(), QVariant( 10 ) );
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
QCOMPARE( ww->field().name(), QString( "layerC_col0" ) );
QCOMPARE( ww->value(), QVariant( QVariant::Int ) );
// change layerA join id field to join with layerC
form.changeAttribute( "id_a", QVariant( 32 ) );
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
QCOMPARE( ww->field().name(), QString( "id_a" ) );
QCOMPARE( ww->value(), QVariant( 32 ) );
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
QCOMPARE( ww->field().name(), QString( "layerB_col0" ) );
QCOMPARE( ww->value(), QVariant( QVariant::Int ) );
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
QCOMPARE( ww->field().name(), QString( "layerC_col0" ) );
QCOMPARE( ww->value(), QVariant( 12 ) );
// change layerA join id field to join with layerA and layerC
form.changeAttribute( "id_a", QVariant( 31 ) );
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
QCOMPARE( ww->field().name(), QString( "id_a" ) );
QCOMPARE( ww->value(), QVariant( 31 ) );
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
QCOMPARE( ww->field().name(), QString( "layerB_col0" ) );
QCOMPARE( ww->value(), QVariant( 11 ) );
ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
QCOMPARE( ww->field().name(), QString( "layerC_col0" ) );
QCOMPARE( ww->value(), QVariant( 13 ) );
// clean
delete layerA;
delete layerB;
delete layerC;
}
QGSTEST_MAIN( TestQgsAttributeForm )
#include "testqgsattributeform.moc"