diff --git a/python/core/qgsvectorlayerjoinbuffer.sip b/python/core/qgsvectorlayerjoinbuffer.sip index f9e1b147364..bae1a284915 100644 --- a/python/core/qgsvectorlayerjoinbuffer.sip +++ b/python/core/qgsvectorlayerjoinbuffer.sip @@ -99,6 +99,24 @@ Quick way to test if there is any join at all :rtype: list of int %End + QList 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 diff --git a/python/core/qgsvectorlayerjoininfo.sip b/python/core/qgsvectorlayerjoininfo.sip index c2b72a18ca3..bd5290956a4 100644 --- a/python/core/qgsvectorlayerjoininfo.sip +++ b/python/core/qgsvectorlayerjoininfo.sip @@ -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 + }; diff --git a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip index 999abb79804..c30b487e1c1 100644 --- a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip +++ b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip @@ -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 ); diff --git a/python/gui/qgsattributeform.sip b/python/gui/qgsattributeform.sip index 9c88045f75b..f73c193fc68 100644 --- a/python/gui/qgsattributeform.sip +++ b/python/gui/qgsattributeform.sip @@ -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. diff --git a/src/app/qgsjoindialog.cpp b/src/app/qgsjoindialog.cpp index 823e3062cc0..5e4f0c7ee30 100644 --- a/src/app/qgsjoindialog.cpp +++ b/src/app/qgsjoindialog.cpp @@ -42,6 +42,8 @@ QgsJoinDialog::QgsJoinDialog( QgsVectorLayer *layer, QList 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() ); diff --git a/src/app/qgsvectorlayerproperties.cpp b/src/app/qgsvectorlayerproperties.cpp index 42ada715baa..2d01809716e 100644 --- a/src/app/qgsvectorlayerproperties.cpp +++ b/src/app/qgsvectorlayerproperties.cpp @@ -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 ); } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fe20a9870db..eddc25eed96 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -286,6 +286,7 @@ SET(QGIS_CORE_SRCS qgsvectorlayerfeatureiterator.cpp qgsvectorlayerexporter.cpp qgsvectorlayerjoinbuffer.cpp + qgsvectorlayerjoininfo.cpp qgsvectorlayerlabeling.cpp qgsvectorlayerlabelprovider.cpp qgsvectorlayerrenderer.cpp diff --git a/src/core/qgsvectorlayerjoinbuffer.cpp b/src/core/qgsvectorlayerjoinbuffer.cpp index d7a9e506bfb..ba778caac0e 100644 --- a/src/core/qgsvectorlayerjoinbuffer.cpp +++ b/src/core/qgsvectorlayerjoinbuffer.cpp @@ -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 QgsVectorLayerJoinBuffer::joinsWhereFieldIsId( const QgsField &field ) const +{ + QList 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 ); diff --git a/src/core/qgsvectorlayerjoinbuffer.h b/src/core/qgsvectorlayerjoinbuffer.h index ce5f389c68e..bcd33118fb4 100644 --- a/src/core/qgsvectorlayerjoinbuffer.h +++ b/src/core/qgsvectorlayerjoinbuffer.h @@ -84,6 +84,20 @@ class CORE_EXPORT QgsVectorLayerJoinBuffer : public QObject //! \since QGIS 2.6 static QVector 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 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; diff --git a/src/core/qgsvectorlayerjoininfo.cpp b/src/core/qgsvectorlayerjoininfo.cpp new file mode 100644 index 00000000000..0d77c39c871 --- /dev/null +++ b/src/core/qgsvectorlayerjoininfo.cpp @@ -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; +} diff --git a/src/core/qgsvectorlayerjoininfo.h b/src/core/qgsvectorlayerjoininfo.h index c196bf78c84..15e5901ee3e 100644 --- a/src/core/qgsvectorlayerjoininfo.h +++ b/src/core/qgsvectorlayerjoininfo.h @@ -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; diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index f09d76d99e0..8d8fcf8a791 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -211,3 +211,8 @@ bool QgsEditorWidgetWrapper::isInTable( const QWidget *parent ) if ( qobject_cast( parent ) ) return true; return isInTable( parent->parentWidget() ); } + +void QgsEditorWidgetWrapper::setHint( const QString &hintText ) +{ + widget()->setToolTip( hintText ); +} diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h index a26398c2d30..e77b0d9ef64 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h @@ -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: /** diff --git a/src/gui/editorwidgets/qgstexteditwrapper.cpp b/src/gui/editorwidgets/qgstexteditwrapper.cpp index 9d3c5506b70..811a4411ddc 100644 --- a/src/gui/editorwidgets/qgstexteditwrapper.cpp +++ b/src/gui/editorwidgets/qgstexteditwrapper.cpp @@ -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 ); +} diff --git a/src/gui/editorwidgets/qgstexteditwrapper.h b/src/gui/editorwidgets/qgstexteditwrapper.h index 51cddd75ae6..fabb7bb0c44 100644 --- a/src/gui/editorwidgets/qgstexteditwrapper.h +++ b/src/gui/editorwidgets/qgstexteditwrapper.h @@ -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 ); }; diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index 34e93bf0387..b53c7167259 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -33,6 +33,8 @@ #include "qgssettings.h" #include "qgsscrollarea.h" #include "qgsgui.h" +#include "qgsvectorlayerjoinbuffer.h" +#include "qgsvectorlayerutils.h" #include #include @@ -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 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 ); + } + } + } +} diff --git a/src/gui/qgsattributeform.h b/src/gui/qgsattributeform.h index 59f6fe61bf1..405535d8272 100644 --- a/src/gui/qgsattributeform.h +++ b/src/gui/qgsattributeform.h @@ -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() diff --git a/src/ui/qgsjoindialogbase.ui b/src/ui/qgsjoindialogbase.ui index d4ccaa8cdac..67651740a4c 100644 --- a/src/ui/qgsjoindialogbase.ui +++ b/src/ui/qgsjoindialogbase.ui @@ -44,10 +44,10 @@ - + - Choose which fields are joined + Choose which fields are &joined true @@ -65,10 +65,10 @@ - + - Custom field name prefix + Custom field &name prefix true @@ -86,7 +86,7 @@ - + Qt::Vertical @@ -116,7 +116,7 @@ - + Qt::Horizontal @@ -126,6 +126,13 @@ + + + + Dynamic form + + + diff --git a/src/ui/qgsvectorlayerpropertiesbase.ui b/src/ui/qgsvectorlayerpropertiesbase.ui index bce78df2474..ad1f95c6b89 100755 --- a/src/ui/qgsvectorlayerpropertiesbase.ui +++ b/src/ui/qgsvectorlayerpropertiesbase.ui @@ -303,7 +303,7 @@ - 0 + 9 @@ -379,8 +379,8 @@ 0 0 - 639 - 592 + 653 + 542 @@ -831,7 +831,7 @@ border-radius: 2px; 0 0 653 - 536 + 542 @@ -1069,7 +1069,7 @@ border-radius: 2px; 0 0 653 - 536 + 542 @@ -1170,7 +1170,7 @@ border-radius: 2px; 0 0 653 - 536 + 542 @@ -1229,8 +1229,8 @@ border-radius: 2px; 0 0 - 671 - 522 + 653 + 542 @@ -1249,7 +1249,7 @@ border-radius: 2px; - Scale dependent visibility + Scale dependen&t visibility true @@ -1268,7 +1268,7 @@ border-radius: 2px; - Simplify geometry + Simplify &geometry true @@ -1590,7 +1590,7 @@ border-radius: 2px; 0 0 653 - 536 + 542 @@ -1656,7 +1656,7 @@ border-radius: 2px; 0 0 653 - 536 + 542 @@ -1675,7 +1675,7 @@ border-radius: 2px; - 6 + 7 @@ -1697,6 +1697,11 @@ border-radius: 2px; Memory cache + + + Dynamic form + + Prefix diff --git a/tests/src/gui/testqgsattributeform.cpp b/tests/src/gui/testqgsattributeform.cpp index 75d4d4ab28f..ddbc939ca25 100644 --- a/tests/src/gui/testqgsattributeform.cpp +++ b/tests/src/gui/testqgsattributeform.cpp @@ -24,6 +24,7 @@ #include #include "qgsvectordataprovider.h" #include +#include #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( form.mWidgets[1] ); + QCOMPARE( ww->field().name(), QString( "layerB_col0" ) ); + QCOMPARE( ww->value(), QVariant( QVariant::Int ) ); + + ww = qobject_cast( 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( form.mWidgets[0] ); + QCOMPARE( ww->field().name(), QString( "id_a" ) ); + QCOMPARE( ww->value(), QVariant( 30 ) ); + + ww = qobject_cast( form.mWidgets[1] ); + QCOMPARE( ww->field().name(), QString( "layerB_col0" ) ); + QCOMPARE( ww->value(), QVariant( 10 ) ); + + ww = qobject_cast( 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( form.mWidgets[0] ); + QCOMPARE( ww->field().name(), QString( "id_a" ) ); + QCOMPARE( ww->value(), QVariant( 32 ) ); + + ww = qobject_cast( form.mWidgets[1] ); + QCOMPARE( ww->field().name(), QString( "layerB_col0" ) ); + QCOMPARE( ww->value(), QVariant( QVariant::Int ) ); + + ww = qobject_cast( 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( form.mWidgets[0] ); + QCOMPARE( ww->field().name(), QString( "id_a" ) ); + QCOMPARE( ww->value(), QVariant( 31 ) ); + + ww = qobject_cast( form.mWidgets[1] ); + QCOMPARE( ww->field().name(), QString( "layerB_col0" ) ); + QCOMPARE( ww->value(), QVariant( 11 ) ); + + ww = qobject_cast( 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"