From b7f123970d955647fefb3c83c305c32205c01d28 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 24 Jun 2024 18:42:29 +0200 Subject: [PATCH 1/5] [FEATURE] [Identify tool] Present referencing relations (in addition to referenced ones), and support them on arbitrary nesting level Fixes #48776 Previously only referenced relations where presented when exploring a feature, now referenced ones are presented as well. Do that only when the user explicit unfolds a node, to avoid potential 'explosion' of the number of features in the tree in case of a huge database where all features would be connected through relations. If through relations a feature already present in an ancestor node is detected, it is omitted from the related features. Add setting parameters to disable showing referenced and referencing relations. Add a contextual menu item "Explore Feature" to be able to "re-center" the result of the identification tree on a nested feature, to limit the nesting depth. --- src/app/qgsidentifyresultsdialog.cpp | 187 ++++++++++-- src/app/qgsidentifyresultsdialog.h | 46 ++- src/ui/qgsidentifyresultsbase.ui | 22 ++ tests/src/app/CMakeLists.txt | 1 + .../src/app/testqgsidentifyresultsdialog.cpp | 270 ++++++++++++++++++ 5 files changed, 499 insertions(+), 27 deletions(-) create mode 100644 tests/src/app/testqgsidentifyresultsdialog.cpp diff --git a/src/app/qgsidentifyresultsdialog.cpp b/src/app/qgsidentifyresultsdialog.cpp index 3e4fc09ee144..4aa63d13bc0c 100644 --- a/src/app/qgsidentifyresultsdialog.cpp +++ b/src/app/qgsidentifyresultsdialog.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -107,6 +108,10 @@ constexpr int REPRESENTED_VALUE_ROLE = Qt::UserRole + 2; const QgsSettingsEntryBool *QgsIdentifyResultsDialog::settingHideNullValues = new QgsSettingsEntryBool( QStringLiteral( "hide-null-values" ), QgsSettingsTree::sTreeMap, false, QStringLiteral( "Whether to hide attributes with NULL values in the identify feature result" ) ); +const QgsSettingsEntryBool *QgsIdentifyResultsDialog::settingShowReferencingRelations = new QgsSettingsEntryBool( QStringLiteral( "show-referencing-relations" ), QgsSettingsTree::sTreeMap, true, QStringLiteral( "Whether to show referencing relations in the identify feature result" ) ); + +const QgsSettingsEntryBool *QgsIdentifyResultsDialog::settingShowReferencedRelations = new QgsSettingsEntryBool( QStringLiteral( "show-referenced-relations" ), QgsSettingsTree::sTreeMap, true, QStringLiteral( "Whether to show referenced relations in the identify feature result" ) ); + QgsIdentifyResultsWebView::QgsIdentifyResultsWebView( QWidget *parent ) : QgsWebView( parent ) { @@ -295,6 +300,14 @@ QgsIdentifyResultsFeatureItem::QgsIdentifyResultsFeatureItem( const QgsFields &f { } +QgsIdentifyResultsRelationItem::QgsIdentifyResultsRelationItem( const QStringList &strings, const QgsRelation &relation, bool isReferencedRole, const QgsFeature &topFeature ) + : QTreeWidgetItem( strings ) + , mRelation( relation ) + , mIsReferencedRole( isReferencedRole ) + , mTopFeature( topFeature ) +{ +} + void QgsIdentifyResultsWebViewItem::setHtml( const QString &html ) { mWebView->setHtml( html ); @@ -361,6 +374,8 @@ QgsIdentifyResultsDialog::QgsIdentifyResultsDialog( QgsMapCanvas *canvas, QWidge connect( mActionAutoFeatureForm, &QAction::toggled, this, &QgsIdentifyResultsDialog::mActionAutoFeatureForm_toggled ); connect( mActionHideDerivedAttributes, &QAction::toggled, this, &QgsIdentifyResultsDialog::mActionHideDerivedAttributes_toggled ); connect( mActionHideNullValues, &QAction::toggled, this, &QgsIdentifyResultsDialog::mActionHideNullValues_toggled ); + connect( mActionShowReferencingRelations, &QAction::toggled, this, &QgsIdentifyResultsDialog::mActionShowReferencingRelations_toggled ); + connect( mActionShowReferencedRelations, &QAction::toggled, this, &QgsIdentifyResultsDialog::mActionShowReferencedRelations_toggled ); mOpenFormAction->setDisabled( true ); @@ -442,6 +457,9 @@ QgsIdentifyResultsDialog::QgsIdentifyResultsDialog( QgsMapCanvas *canvas, QWidge connect( lstResults, &QTreeWidget::itemClicked, this, &QgsIdentifyResultsDialog::itemClicked ); + connect( lstResults, &QTreeWidget::itemExpanded, + this, &QgsIdentifyResultsDialog::itemExpanded ); + #if defined( HAVE_QTPRINTER ) connect( mActionPrint, &QAction::triggered, this, &QgsIdentifyResultsDialog::printCurrentItem ); #else @@ -470,6 +488,10 @@ QgsIdentifyResultsDialog::QgsIdentifyResultsDialog( QgsMapCanvas *canvas, QWidge mActionHideDerivedAttributes->setChecked( mySettings.value( QStringLiteral( "Map/hideDerivedAttributes" ), false ).toBool() ); settingsMenu->addAction( mActionHideNullValues ); mActionHideNullValues->setChecked( QgsIdentifyResultsDialog::settingHideNullValues->value() ); + settingsMenu->addAction( mActionShowReferencedRelations ); + mActionShowReferencedRelations->setChecked( QgsIdentifyResultsDialog::settingShowReferencedRelations->value() ); + settingsMenu->addAction( mActionShowReferencingRelations ); + mActionShowReferencingRelations->setChecked( QgsIdentifyResultsDialog::settingShowReferencingRelations->value() ); } @@ -580,7 +602,7 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat connect( vlayer, &QgsVectorLayer::editingStopped, this, &QgsIdentifyResultsDialog::editingToggled ); } - QgsIdentifyResultsFeatureItem *featItem = createFeatureItem( vlayer, f, derivedAttributes, true, layItem ); + QgsIdentifyResultsFeatureItem *featItem = createFeatureItem( vlayer, f, derivedAttributes, layItem ); featItem->setData( 0, Qt::UserRole + 1, mFeatures.size() ); mFeatures << f; layItem->setFirstColumnSpanned( true ); @@ -649,7 +671,28 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat highlightFeature( featItem ); } -QgsIdentifyResultsFeatureItem *QgsIdentifyResultsDialog::createFeatureItem( QgsVectorLayer *vlayer, const QgsFeature &f, const QMap &derivedAttributes, bool includeRelations, QTreeWidgetItem *parentItem ) +/** + * Given an item, returns if the provided feature matches the item or one of + * its ancestor in the tree + */ +/* static */ +bool QgsIdentifyResultsDialog::isFeatureInAncestors( QTreeWidgetItem *item, const QgsVectorLayer *vlayer, const QgsFeature &f ) +{ + for ( ; item; item = item->parent() ) + { + if ( item->data( 0, FeatureRole ).isValid() && item->parent() && vectorLayer( item->parent() ) == vlayer ) + { + const QgsFeature otherF = item->data( 0, FeatureRole ).value< QgsFeature >(); + if ( f.id() == otherF.id() ) + { + return true; + } + } + } + return false; +} + +QgsIdentifyResultsFeatureItem *QgsIdentifyResultsDialog::createFeatureItem( QgsVectorLayer *vlayer, const QgsFeature &f, const QMap &derivedAttributes, QTreeWidgetItem *parentItem ) { QgsIdentifyResultsFeatureItem *featItem = new QgsIdentifyResultsFeatureItem( vlayer->fields(), f, vlayer->crs() ); featItem->setData( 0, Qt::UserRole, FID_TO_STRING( f.id() ) ); @@ -748,6 +791,7 @@ QgsIdentifyResultsFeatureItem *QgsIdentifyResultsDialog::createFeatureItem( QgsV const QString originalValue = defVal == attrs.at( i ) ? defVal : fields.at( i ).displayString( attrs.at( i ) ); QgsTreeWidgetItem *attrItem = new QgsTreeWidgetItem( QStringList() << QString::number( i ) << originalValue ); + featItem->addChild( attrItem ); attrItem->setData( 0, Qt::DisplayRole, vlayer->attributeDisplayName( i ) ); @@ -805,8 +849,8 @@ QgsIdentifyResultsFeatureItem *QgsIdentifyResultsDialog::createFeatureItem( QgsV } } - // add entries for related items - if ( includeRelations ) + // add entries for related items coming from referenced relations + if ( QgsIdentifyResultsDialog::settingShowReferencedRelations->value() ) { const QList relations = QgsProject::instance()->relationManager()->referencedRelations( vlayer ); if ( !relations.empty() ) @@ -815,26 +859,44 @@ QgsIdentifyResultsFeatureItem *QgsIdentifyResultsDialog::createFeatureItem( QgsV { QgsFeatureIterator childIt = relation.getRelatedFeatures( f ); QgsFeature childFeature; - QgsTreeWidgetItem *relationItem = nullptr; - while ( childIt.nextFeature( childFeature ) ) + if ( childIt.nextFeature( childFeature ) && !isFeatureInAncestors( parentItem, relation.referencingLayer(), childFeature ) ) { - if ( !relationItem ) - { - relationItem = new QgsTreeWidgetItem( QStringList() << relation.name() ); - QFont italicFont; - italicFont.setItalic( true ); - relationItem->setFont( 0, italicFont ); - relationItem->setData( 0, Qt::UserRole, QVariant::fromValue( qobject_cast( relation.referencingLayer() ) ) ); - featItem->addChild( relationItem ); - } - - QgsIdentifyResultsFeatureItem *childItem = createFeatureItem( relation.referencingLayer(), childFeature, QMap(), false, relationItem ); - relationItem->addChild( childItem ); + QgsIdentifyResultsRelationItem *relationItem = new QgsIdentifyResultsRelationItem( QStringList() << relation.name(), relation, true, f ); + QFont italicFont; + italicFont.setItalic( true ); + relationItem->setFont( 0, italicFont ); + relationItem->setData( 0, Qt::UserRole, QVariant::fromValue( qobject_cast( relation.referencingLayer() ) ) ); + relationItem->setText( 0, tr( "%1 through %2 [%3]" ).arg( relation.referencingLayer()->name() ).arg( relation.name() ).arg( childIt.nextFeature( childFeature ) ? "…" : "1" ) ); + relationItem->setChildIndicatorPolicy( QTreeWidgetItem::ShowIndicator ); + featItem->addChild( relationItem ); + // setFirstColumnSpanned() to be done after addChild() to be effective + relationItem->setFirstColumnSpanned( true ); } + } + } + } - if ( relationItem ) + // add entries for related items coming from referencing relations + if ( QgsIdentifyResultsDialog::settingShowReferencingRelations->value() ) + { + const QList relations = QgsProject::instance()->relationManager()->referencingRelations( vlayer ); + if ( !relations.empty() ) + { + for ( const QgsRelation &relation : relations ) + { + QgsFeature parentFeature = relation.getReferencedFeature( f ); + if ( parentFeature.isValid() && !isFeatureInAncestors( parentItem, relation.referencedLayer(), parentFeature ) ) { - relationItem->setText( 0, QStringLiteral( "%1 [%2]" ).arg( relation.name() ).arg( relationItem->childCount() ) ); + QgsIdentifyResultsRelationItem *relationItem = new QgsIdentifyResultsRelationItem( QStringList() << relation.name(), relation, false, f ); + QFont italicFont; + italicFont.setItalic( true ); + relationItem->setFont( 0, italicFont ); + relationItem->setData( 0, Qt::UserRole, QVariant::fromValue( qobject_cast( relation.referencedLayer() ) ) ); + relationItem->setText( 0, tr( "%1 through %2 [%3]" ).arg( relation.referencedLayer()->name() ).arg( relation.name() ) .arg( 1 ) ) ; + relationItem->setChildIndicatorPolicy( QTreeWidgetItem::ShowIndicator ); + featItem->addChild( relationItem ); + // setFirstColumnSpanned() to be done after addChild() to be effective + relationItem->setFirstColumnSpanned( true ); } } } @@ -1506,7 +1568,7 @@ void QgsIdentifyResultsDialog::show() // expand all if enabled if ( mExpandNewAction->isChecked() ) { - lstResults->expandAll(); + expandAll(); } QDialog::show(); @@ -1544,6 +1606,48 @@ void QgsIdentifyResultsDialog::itemClicked( QTreeWidgetItem *item, int column ) } } +void QgsIdentifyResultsDialog::itemExpanded( QTreeWidgetItem *item ) +{ + QgsIdentifyResultsRelationItem *relationItem = dynamic_cast( item ); + if ( relationItem && item->childCount() == 0 ) + { + const QgsRelation &relation = relationItem->relation(); + const QgsFeature &feat = relationItem->topFeature(); + + if ( relationItem->isReferencedRole() ) + { + QgsFeatureIterator childIt = relation.getRelatedFeatures( feat ); + QgsFeature childFeature; + while ( childIt.nextFeature( childFeature ) ) + { + if ( !isFeatureInAncestors( relationItem, relation.referencingLayer(), childFeature ) ) + { + QgsIdentifyResultsFeatureItem *childItem = createFeatureItem( relation.referencingLayer(), childFeature, QMap(), relationItem ); + relationItem->addChild( childItem ); + } + } + + if ( relationItem->childCount() > 1 ) + { + relationItem->setText( 0, tr( "%1 through %2 [%3]" ).arg( relation.referencingLayer()->name() ).arg( relation.name() ).arg( relationItem->childCount() ) ); + } + } + else + { + QgsFeature parentFeature = relation.getReferencedFeature( feat ); + QgsIdentifyResultsFeatureItem *childItem = createFeatureItem( relation.referencedLayer(), parentFeature, QMap(), relationItem ); + relationItem->addChild( childItem ); + } + } + + if ( relationItem && item->childCount() == 1 ) + { + // Small usability: when expanding a relation that has only one related + // feature, expand the feature. + lstResults->expandItem( item->child( 0 ) ); + } +} + // Popup (create if necessary) a context menu that contains a list of // actions that can be applied to the data in the identify results // dialog box. @@ -1591,6 +1695,10 @@ void QgsIdentifyResultsDialog::contextMenuEvent( QContextMenuEvent *event ) if ( featItem->feature().isValid() ) { mActionPopup->addAction( tr( "Zoom to Feature" ), this, &QgsIdentifyResultsDialog::zoomToFeature ); + if ( vlayer && dynamic_cast( featItem->parent() ) ) + { + mActionPopup->addAction( tr( "Explore Feature" ), this, &QgsIdentifyResultsDialog::exploreFeature ); + } mActionPopup->addAction( tr( "Copy Feature" ), this, &QgsIdentifyResultsDialog::copyFeature ); if ( vlayer ) { @@ -2281,6 +2389,22 @@ void QgsIdentifyResultsDialog::zoomToFeature() mCanvas->refresh(); } +void QgsIdentifyResultsDialog::exploreFeature() +{ + QTreeWidgetItem *item = lstResults->currentItem(); + QgsVectorLayer *vlayer = QgsIdentifyResultsDialog::vectorLayer( item ); + if ( !vlayer ) + return; + + QgsIdentifyResultsFeatureItem *featItem = dynamic_cast( featureItem( item ) ); + if ( !featItem ) + return; + + const QgsFeature feat = featItem->feature(); + lstResults->clear(); + addFeature( vlayer, feat, QMap() ); +} + void QgsIdentifyResultsDialog::featureForm() { QTreeWidgetItem *item = lstResults->currentItem(); @@ -2369,7 +2493,16 @@ void QgsIdentifyResultsDialog::layerProperties( QTreeWidgetItem *item ) void QgsIdentifyResultsDialog::expandAll() { - lstResults->expandAll(); + // We can't use expandAll() as it would result in resolving nodes corresponding + // to related features, that we want the user to explicitly expand, to avoid + // creating a potential deeply nested tree. + + QTreeWidgetItemIterator it( lstResults ); + for ( ; *it ; ++it ) + { + if ( ( *it )->childCount() ) + lstResults->expandItem( *it ); + } } void QgsIdentifyResultsDialog::collapseAll() @@ -2518,6 +2651,16 @@ void QgsIdentifyResultsDialog::mActionHideNullValues_toggled( bool checked ) QgsIdentifyResultsDialog::settingHideNullValues->setValue( checked ); } +void QgsIdentifyResultsDialog::mActionShowReferencedRelations_toggled( bool checked ) +{ + QgsIdentifyResultsDialog::settingShowReferencedRelations->setValue( checked ); +} + +void QgsIdentifyResultsDialog::mActionShowReferencingRelations_toggled( bool checked ) +{ + QgsIdentifyResultsDialog::settingShowReferencingRelations->setValue( checked ); +} + void QgsIdentifyResultsDialog::mExpandNewAction_triggered( bool checked ) { QgsSettings settings; diff --git a/src/app/qgsidentifyresultsdialog.h b/src/app/qgsidentifyresultsdialog.h index 4f3c1619ae08..1408b3408339 100644 --- a/src/app/qgsidentifyresultsdialog.h +++ b/src/app/qgsidentifyresultsdialog.h @@ -28,6 +28,7 @@ #include "qgswebview.h" #include "qgsexpressioncontext.h" #include "qgsmaptoolselectionhandler.h" +#include "qgsrelation.h" #include #include @@ -86,6 +87,31 @@ class APP_EXPORT QgsIdentifyResultsFeatureItem: public QTreeWidgetItem QgsCoordinateReferenceSystem mCrs; }; +//! Tree widget item being the parent item of a referenced or referencing relation +class APP_EXPORT QgsIdentifyResultsRelationItem: public QTreeWidgetItem +{ + public: + //! Constructor + QgsIdentifyResultsRelationItem( const QStringList &strings, const QgsRelation &relation, bool isReferencedRole, const QgsFeature &topFeature ); + + //! Return the relation + const QgsRelation &relation() const { return mRelation; } + + /** + * Return true if getRelatedFeatures(mTopFeature) should be called on mRelation, + * or false if getReferencedFeature(mTopFeature) should be called. + */ + bool isReferencedRole() const { return mIsReferencedRole; } + + //! Return the feature that is the parent of this item. + const QgsFeature &topFeature() const { return mTopFeature; } + + private: + QgsRelation mRelation; + bool mIsReferencedRole; + QgsFeature mTopFeature; +}; + class APP_EXPORT QgsIdentifyResultsWebViewItem: public QObject, public QTreeWidgetItem { Q_OBJECT @@ -127,15 +153,15 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti public: /** - * Constructor - - * takes its own copy of the QgsAttributeAction so - * that it is independent of whoever created it. + * Constructor */ QgsIdentifyResultsDialog( QgsMapCanvas *canvas, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags() ); ~QgsIdentifyResultsDialog() override; static const QgsSettingsEntryBool *settingHideNullValues; + static const QgsSettingsEntryBool *settingShowReferencingRelations; + static const QgsSettingsEntryBool *settingShowReferencedRelations; //! Adds feature from vector layer void addFeature( QgsVectorLayer *layer, @@ -241,6 +267,7 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti void featureForm(); void zoomToFeature(); + void exploreFeature(); void copyAttributeValue(); void copyFeature(); void toggleFeatureSelection(); @@ -260,6 +287,8 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti /* Item in tree was clicked */ void itemClicked( QTreeWidgetItem *lvi, int column ); + void itemExpanded( QTreeWidgetItem *item ); + QgsAttributeMap retrieveAttributes( QTreeWidgetItem *item ); QVariant retrieveAttribute( QTreeWidgetItem *item ); @@ -275,6 +304,10 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti void mActionHideNullValues_toggled( bool checked ); + void mActionShowReferencingRelations_toggled( bool checked ); + + void mActionShowReferencedRelations_toggled( bool checked ); + void mExpandAction_triggered( bool checked ) { Q_UNUSED( checked ) expandAll(); } void mCollapseAction_triggered( bool checked ) { Q_UNUSED( checked ) collapseAll(); } @@ -306,7 +339,7 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti QToolButton *mSelectModeButton = nullptr; QgsMapLayer *layer( QTreeWidgetItem *item ); - QgsVectorLayer *vectorLayer( QTreeWidgetItem *item ); + static QgsVectorLayer *vectorLayer( QTreeWidgetItem *item ); QgsRasterLayer *rasterLayer( QTreeWidgetItem *item ); QgsMeshLayer *meshLayer( QTreeWidgetItem *item ); QgsVectorTileLayer *vectorTileLayer( QTreeWidgetItem *item ); @@ -341,9 +374,12 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti void setSelectionMode(); void initSelectionModes(); - QgsIdentifyResultsFeatureItem *createFeatureItem( QgsVectorLayer *vlayer, const QgsFeature &f, const QMap &derivedAttributes, bool includeRelations, QTreeWidgetItem *parentItem ); + QgsIdentifyResultsFeatureItem *createFeatureItem( QgsVectorLayer *vlayer, const QgsFeature &f, const QMap &derivedAttributes, QTreeWidgetItem *parentItem ); + + static bool isFeatureInAncestors( QTreeWidgetItem *item, const QgsVectorLayer *vlayer, const QgsFeature &f ); friend class TestQgsMapToolIdentifyAction; + friend class TestQgsIdentifyResultsDialog; }; class QgsIdentifyResultsDialogMapLayerAction : public QAction diff --git a/src/ui/qgsidentifyresultsbase.ui b/src/ui/qgsidentifyresultsbase.ui index 23591c78187f..568797d29478 100644 --- a/src/ui/qgsidentifyresultsbase.ui +++ b/src/ui/qgsidentifyresultsbase.ui @@ -383,6 +383,28 @@ Hide derived attributes from results + + + true + + + Show referencing relations + + + Show referencing relations + + + + + true + + + Show referenced relations + + + Show referenced relations + + diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index 333cd25ff0d9..1bf5581c389c 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -19,6 +19,7 @@ set(TESTS testqgsdecorationscalebar.cpp testqgsdwgimportdialog.cpp testqgsfieldcalculator.cpp + testqgsidentifyresultsdialog.cpp testqgslayerpropertiesdialogs.cpp testqgsmapcanvasdockwidget.cpp testqgsmaptooladdpart.cpp diff --git a/tests/src/app/testqgsidentifyresultsdialog.cpp b/tests/src/app/testqgsidentifyresultsdialog.cpp new file mode 100644 index 000000000000..27b8a5059917 --- /dev/null +++ b/tests/src/app/testqgsidentifyresultsdialog.cpp @@ -0,0 +1,270 @@ +/*************************************************************************** + testqgsidentifyresultsdialog.cpp + -------------------------------- + Date : 2024-06-21 + Copyright : (C) 2024 by Even Rouault + Email : even.rouault at spatialys.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 "qgstest.h" + +#include "qgisapp.h" +#include "qgsapplication.h" +#include "qgsidentifyresultsdialog.h" +#include "qgsmapcanvas.h" +#include "qgsproject.h" +#include "qgsrelation.h" +#include "qgsrelationmanager.h" +#include "qgsvectorlayer.h" + +class TestQgsIdentifyResultsDialog : public QObject +{ + Q_OBJECT + public: + TestQgsIdentifyResultsDialog() = default; + + private slots: + void initTestCase(); // will be called before the first testfunction is executed. + void cleanupTestCase(); // will be called after the last testfunction was executed. + void init(); // will be called before each testfunction is executed. + void cleanup(); // will be called after every testfunction. + + void testRelations(); + + private: + + QgsMapCanvas *mCanvas = nullptr; + QgisApp *mQgisApp = nullptr; +}; + +void TestQgsIdentifyResultsDialog::initTestCase() +{ + QgsApplication::init(); + QgsApplication::initQgis(); + // Set up the QgsSettings environment + QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) ); + QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) ); + QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) ); + + QgsApplication::showSettings(); + + // enforce C locale because the tests expect it + // (decimal separators / thousand separators) + QLocale::setDefault( QLocale::c() ); + + mQgisApp = new QgisApp(); +} + +void TestQgsIdentifyResultsDialog::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +void TestQgsIdentifyResultsDialog::init() +{ + mCanvas = new QgsMapCanvas(); +} + +void TestQgsIdentifyResultsDialog::cleanup() +{ + delete mCanvas; +} + +void TestQgsIdentifyResultsDialog::testRelations() +{ + QgsVectorLayer *layerA = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk_id:integer" ), QStringLiteral( "layerA" ), QStringLiteral( "memory" ) ); + QVERIFY( layerA->isValid() ); + QgsFeature featureA( layerA->dataProvider()->fields() ); + constexpr int PK_ID_A = 1; + constexpr int PK_ID_C = 2; + featureA.setAttribute( 0, PK_ID_A ); + layerA->dataProvider()->addFeature( featureA ); + + QgsVectorLayer *layerB = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=fk_id_to_A:integer&field=fk_id_to_C:integer&field=other_field:integer" ), QStringLiteral( "layerB" ), QStringLiteral( "memory" ) ); + QVERIFY( layerB->isValid() ); + constexpr int IDX_OTHER_FIELD = 2; + constexpr int OTHER_FIELD = 100; + { + QgsFeature featureB( layerB->dataProvider()->fields() ); + featureB.setAttribute( 0, PK_ID_A ); + featureB.setAttribute( 1, PK_ID_C ); + featureB.setAttribute( IDX_OTHER_FIELD, OTHER_FIELD ); + layerB->dataProvider()->addFeature( featureB ); + } + { + QgsFeature featureB( layerB->dataProvider()->fields() ); + featureB.setAttribute( 0, PK_ID_A ); + featureB.setAttribute( 1, PK_ID_C + 1 ); + featureB.setAttribute( IDX_OTHER_FIELD, OTHER_FIELD + 1 ); + layerB->dataProvider()->addFeature( featureB ); + } + + QgsVectorLayer *layerC = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk_id:integer" ), QStringLiteral( "layerC" ), QStringLiteral( "memory" ) ); + QVERIFY( layerC->isValid() ); + { + QgsFeature featureC( layerC->dataProvider()->fields() ); + featureC.setAttribute( 0, PK_ID_C ); + layerC->dataProvider()->addFeature( featureC ); + } + { + QgsFeature featureC( layerC->dataProvider()->fields() ); + featureC.setAttribute( 0, PK_ID_C + 1 ); + layerC->dataProvider()->addFeature( featureC ); + } + + QgsProject::instance()->layerStore()->addMapLayer( layerA, true ); + QgsProject::instance()->layerStore()->addMapLayer( layerB, true ); + QgsProject::instance()->layerStore()->addMapLayer( layerC, true ); + + QgsRelationManager *relationManager = QgsProject::instance()->relationManager(); + { + QgsRelation relation; + relation.setId( "B-A-id" ); + relation.setName( "B-A" ); + relation.setReferencingLayer( layerB->id() ); + relation.setReferencedLayer( layerA->id() ); + relation.addFieldPair( QStringLiteral( "fk_id_to_A" ), QStringLiteral( "pk_id" ) ); + + relationManager->addRelation( relation ); + } + { + QgsRelation relation; + relation.setId( "A-B-id" ); + relation.setName( "A-B" ); + relation.setReferencingLayer( layerA->id() ); + relation.setReferencedLayer( layerB->id() ); + relation.addFieldPair( QStringLiteral( "pk_id" ), QStringLiteral( "fk_id_to_A" ) ); + + relationManager->addRelation( relation ); + } + { + QgsRelation relation; + relation.setId( "B-C-id" ); + relation.setName( "B-C" ); + relation.setReferencingLayer( layerB->id() ); + relation.setReferencedLayer( layerC->id() ); + relation.addFieldPair( QStringLiteral( "fk_id_to_C" ), QStringLiteral( "pk_id" ) ); + + relationManager->addRelation( relation ); + } + + std::unique_ptr dialog = std::make_unique( mCanvas ); + dialog->addFeature( layerA, featureA, QMap< QString, QString>() ); + + QCOMPARE( dialog->lstResults->topLevelItemCount(), 1 ); + QTreeWidgetItem *topLevelItem = dialog->lstResults->topLevelItem( 0 ); + QCOMPARE( topLevelItem->childCount(), 1 ); + QgsIdentifyResultsFeatureItem *featureItem = dynamic_cast( topLevelItem->child( 0 ) ); + QVERIFY( featureItem ); + std::vector relationItems; + for ( int i = 0; i < featureItem->childCount(); ++i ) + { + QgsIdentifyResultsRelationItem *relationItem = dynamic_cast( featureItem->child( i ) ); + if ( relationItem ) + relationItems.push_back( relationItem ); + } + QCOMPARE( relationItems.size(), 2 ); + + QCOMPARE( relationItems[0]->text( 0 ), QStringLiteral( "layerB through B-A […]" ) ); + QCOMPARE( relationItems[0]->childCount(), 0 ); + QCOMPARE( relationItems[0]->childIndicatorPolicy(), QTreeWidgetItem::ShowIndicator ); + QCOMPARE( relationItems[0]->isExpanded(), false ); + + QCOMPARE( relationItems[1]->text( 0 ), QStringLiteral( "layerB through A-B [1]" ) ); + QCOMPARE( relationItems[1]->childCount(), 0 ); + QCOMPARE( relationItems[1]->childIndicatorPolicy(), QTreeWidgetItem::ShowIndicator ); + QCOMPARE( relationItems[1]->isExpanded(), false ); + + // Check referenced relation + + // Check that expandAll() doesn't result in automatic resolution of relations + dialog->expandAll(); + QCOMPARE( relationItems[0]->childCount(), 0 ); + + relationItems[0]->setExpanded( true ); + QCOMPARE( relationItems[0]->text( 0 ), QStringLiteral( "layerB through B-A [2]" ) ); + QCOMPARE( relationItems[0]->childCount(), 2 ); + + // Check that folding/unfolding after initial expansion works + relationItems[0]->setExpanded( false ); + relationItems[0]->setExpanded( true ); + QCOMPARE( relationItems[0]->text( 0 ), QStringLiteral( "layerB through B-A [2]" ) ); + QCOMPARE( relationItems[0]->childCount(), 2 ); + + { + QgsIdentifyResultsFeatureItem *relatedFeatureItem = dynamic_cast( relationItems[0]->child( 0 ) ); + QVERIFY( relatedFeatureItem ); + QVERIFY( relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); + const QgsFeature relatedFeature = relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); + QCOMPARE( relatedFeature.attribute( IDX_OTHER_FIELD ), OTHER_FIELD ); + + { + std::vector childRelationItems; + for ( int i = 0; i < relatedFeatureItem->childCount(); ++i ) + { + QgsIdentifyResultsRelationItem *relationItem = dynamic_cast( relatedFeatureItem->child( i ) ); + if ( relationItem ) + childRelationItems.push_back( relationItem ); + } + QCOMPARE( childRelationItems.size(), 1 ); + + QCOMPARE( childRelationItems[0]->childCount(), 0 ); + QCOMPARE( childRelationItems[0]->text( 0 ), QStringLiteral( "layerC through B-C [1]" ) ); + + childRelationItems[0]->setExpanded( true ); + QCOMPARE( childRelationItems[0]->childCount(), 1 ); + QCOMPARE( childRelationItems[0]->text( 0 ), QStringLiteral( "layerC through B-C [1]" ) ); + + { + QgsIdentifyResultsFeatureItem *childRelatedFeatureItem = dynamic_cast( childRelationItems[0]->child( 0 ) ); + QVERIFY( childRelatedFeatureItem ); + QVERIFY( childRelatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); + const QgsFeature relatedFeature = childRelatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); + QCOMPARE( relatedFeature.attribute( 0 ), PK_ID_C ); + + // Check that this child doesn't link back to parent feature A + std::vector childChildRelationItems; + for ( int i = 0; i < childRelatedFeatureItem->childCount(); ++i ) + { + QgsIdentifyResultsRelationItem *relationItem = dynamic_cast( childRelatedFeatureItem->child( i ) ); + if ( relationItem ) + childChildRelationItems.push_back( relationItem ); + } + QCOMPARE( childChildRelationItems.size(), 0 ); + } + } + + } + + { + QgsIdentifyResultsFeatureItem *relatedFeatureItem = dynamic_cast( relationItems[0]->child( 1 ) ); + QVERIFY( relatedFeatureItem ); + QVERIFY( relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); + const QgsFeature relatedFeature = relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); + QCOMPARE( relatedFeature.attribute( IDX_OTHER_FIELD ), OTHER_FIELD + 1 ); + } + + // Check referencing relation + relationItems[1]->setExpanded( true ); + QCOMPARE( relationItems[1]->text( 0 ), QStringLiteral( "layerB through A-B [1]" ) ); + QCOMPARE( relationItems[1]->childCount(), 1 ); + + { + QgsIdentifyResultsFeatureItem *relatedFeatureItem = dynamic_cast( relationItems[1]->child( 0 ) ); + QVERIFY( relatedFeatureItem ); + QVERIFY( relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); + const QgsFeature relatedFeature = relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); + QCOMPARE( relatedFeature.attribute( IDX_OTHER_FIELD ), OTHER_FIELD ); + } +} + + +QGSTEST_MAIN( TestQgsIdentifyResultsDialog ) +#include "testqgsidentifyresultsdialog.moc" From 56ad575af2ba5f831fa9a63634260d6ece7d867d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 24 Jun 2024 23:06:37 +0200 Subject: [PATCH 2/5] [Identify tool] Make sure to destroy nested nodes for features coming from relations --- src/app/qgsidentifyresultsdialog.cpp | 29 ++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/app/qgsidentifyresultsdialog.cpp b/src/app/qgsidentifyresultsdialog.cpp index 4aa63d13bc0c..5d257335ad10 100644 --- a/src/app/qgsidentifyresultsdialog.cpp +++ b/src/app/qgsidentifyresultsdialog.cpp @@ -871,6 +871,8 @@ QgsIdentifyResultsFeatureItem *QgsIdentifyResultsDialog::createFeatureItem( QgsV featItem->addChild( relationItem ); // setFirstColumnSpanned() to be done after addChild() to be effective relationItem->setFirstColumnSpanned( true ); + + connect( relation.referencingLayer(), &QObject::destroyed, this, &QgsIdentifyResultsDialog::layerDestroyed ); } } } @@ -897,6 +899,8 @@ QgsIdentifyResultsFeatureItem *QgsIdentifyResultsDialog::createFeatureItem( QgsV featItem->addChild( relationItem ); // setFirstColumnSpanned() to be done after addChild() to be effective relationItem->setFirstColumnSpanned( true ); + + connect( relation.referencedLayer(), &QObject::destroyed, this, &QgsIdentifyResultsDialog::layerDestroyed ); } } } @@ -2166,6 +2170,21 @@ void QgsIdentifyResultsDialog::handleCurrentItemChanged( QTreeWidgetItem *curren } } +static void deleteItemIfBelongingToLayer( QObject *senderObject, QTreeWidgetItem *item ) +{ + if ( item->data( 0, Qt::UserRole ).value() == senderObject ) + { + delete item; + } + else + { + for ( int i = item->childCount() - 1; i >= 0; i-- ) + { + deleteItemIfBelongingToLayer( senderObject, item->child( i ) ); + } + } +} + void QgsIdentifyResultsDialog::layerDestroyed() { QObject *senderObject = sender(); @@ -2184,9 +2203,15 @@ void QgsIdentifyResultsDialog::layerDestroyed() } disconnectLayer( senderObject ); - delete layerItem( senderObject ); - // remove items, starting from last + // remove items from the tree that are related to the layer + for ( int i = lstResults->topLevelItemCount() - 1; i >= 0; i-- ) + { + QTreeWidgetItem *item = lstResults->topLevelItem( i ); + deleteItemIfBelongingToLayer( senderObject, item ); + } + + // remove items from the table, starting from last for ( int i = tblResults->rowCount() - 1; i >= 0; i-- ) { QgsDebugMsgLevel( QStringLiteral( "item %1 / %2" ).arg( i ).arg( tblResults->rowCount() ), 3 ); From fca80da1d8363dbda75e573c32d603915fd686fd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 24 Jun 2024 23:14:41 +0200 Subject: [PATCH 3/5] tests/src/app/: merge testqgsmaptoolidentifyaction.cpp and testqgsidentifyresultsdialog.cpp into testqgsidentify.cpp --- src/app/qgsidentifyresultsdialog.h | 3 +- src/app/qgsmaptoolidentifyaction.h | 2 +- tests/src/app/CMakeLists.txt | 3 +- ...identifyaction.cpp => testqgsidentify.cpp} | 241 ++++++++++++++-- .../src/app/testqgsidentifyresultsdialog.cpp | 270 ------------------ 5 files changed, 218 insertions(+), 301 deletions(-) rename tests/src/app/{testqgsmaptoolidentifyaction.cpp => testqgsidentify.cpp} (83%) delete mode 100644 tests/src/app/testqgsidentifyresultsdialog.cpp diff --git a/src/app/qgsidentifyresultsdialog.h b/src/app/qgsidentifyresultsdialog.h index 1408b3408339..aef05544478c 100644 --- a/src/app/qgsidentifyresultsdialog.h +++ b/src/app/qgsidentifyresultsdialog.h @@ -378,8 +378,7 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti static bool isFeatureInAncestors( QTreeWidgetItem *item, const QgsVectorLayer *vlayer, const QgsFeature &f ); - friend class TestQgsMapToolIdentifyAction; - friend class TestQgsIdentifyResultsDialog; + friend class TestQgsIdentify; }; class QgsIdentifyResultsDialogMapLayerAction : public QAction diff --git a/src/app/qgsmaptoolidentifyaction.h b/src/app/qgsmaptoolidentifyaction.h index b375cc6b0c54..20c332951dab 100644 --- a/src/app/qgsmaptoolidentifyaction.h +++ b/src/app/qgsmaptoolidentifyaction.h @@ -96,7 +96,7 @@ class APP_EXPORT QgsMapToolIdentifyAction : public QgsMapToolIdentify Qgis::AreaUnit displayAreaUnits() const override; void setClickContextScope( const QgsPointXY &point ); - friend class TestQgsMapToolIdentifyAction; + friend class TestQgsIdentify; }; #endif diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index 1bf5581c389c..1cedc22e87e2 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -19,7 +19,7 @@ set(TESTS testqgsdecorationscalebar.cpp testqgsdwgimportdialog.cpp testqgsfieldcalculator.cpp - testqgsidentifyresultsdialog.cpp + testqgsidentify.cpp testqgslayerpropertiesdialogs.cpp testqgsmapcanvasdockwidget.cpp testqgsmaptooladdpart.cpp @@ -27,7 +27,6 @@ set(TESTS testqgsmaptooladdring.cpp testqgsmaptooldeletering.cpp testqgsmaptooleditannotation.cpp - testqgsmaptoolidentifyaction.cpp testqgsmaptoollabel.cpp testqgsmaptoolselect.cpp testqgsmaptoolreshape.cpp diff --git a/tests/src/app/testqgsmaptoolidentifyaction.cpp b/tests/src/app/testqgsidentify.cpp similarity index 83% rename from tests/src/app/testqgsmaptoolidentifyaction.cpp rename to tests/src/app/testqgsidentify.cpp index dcdbc4706407..553add644800 100644 --- a/tests/src/app/testqgsmaptoolidentifyaction.cpp +++ b/tests/src/app/testqgsidentify.cpp @@ -42,11 +42,11 @@ #include "cpl_conv.h" -class TestQgsMapToolIdentifyAction : public QObject +class TestQgsIdentify : public QObject { Q_OBJECT public: - TestQgsMapToolIdentifyAction() = default; + TestQgsIdentify() = default; private slots: void initTestCase(); // will be called before the first testfunction is executed. @@ -65,6 +65,7 @@ class TestQgsMapToolIdentifyAction : public QObject void identifyInvalidPolygons(); // test selecting invalid polygons void clickxy(); // test if click_x and click_y variables are propagated void closestPoint(); + void testRelations(); private: void doAction(); @@ -101,7 +102,7 @@ class TestQgsMapToolIdentifyAction : public QObject } }; -void TestQgsMapToolIdentifyAction::initTestCase() +void TestQgsIdentify::initTestCase() { QgsApplication::init(); QgsApplication::initQgis(); @@ -119,22 +120,22 @@ void TestQgsMapToolIdentifyAction::initTestCase() mQgisApp = new QgisApp(); } -void TestQgsMapToolIdentifyAction::cleanupTestCase() +void TestQgsIdentify::cleanupTestCase() { QgsApplication::exitQgis(); } -void TestQgsMapToolIdentifyAction::init() +void TestQgsIdentify::init() { canvas = new QgsMapCanvas(); } -void TestQgsMapToolIdentifyAction::cleanup() +void TestQgsIdentify::cleanup() { delete canvas; } -void TestQgsMapToolIdentifyAction::doAction() +void TestQgsIdentify::doAction() { bool ok = false; const int clickxOk = 2484588; @@ -175,7 +176,7 @@ void TestQgsMapToolIdentifyAction::doAction() mIdentifyAction->identifyMenu()->close(); } -void TestQgsMapToolIdentifyAction::clickxy() +void TestQgsIdentify::clickxy() { // create temp layer std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:3111" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) ); @@ -216,11 +217,11 @@ void TestQgsMapToolIdentifyAction::clickxy() QgsMapMouseEvent mapReleases( nullptr, &releases ); // simulate a click on the corresponding action - QTimer::singleShot( 2000, this, &TestQgsMapToolIdentifyAction::doAction ); + QTimer::singleShot( 2000, this, &TestQgsIdentify::doAction ); mIdentifyAction->canvasReleaseEvent( &mapReleases ); } -void TestQgsMapToolIdentifyAction::closestPoint() +void TestQgsIdentify::closestPoint() { QgsSettings s; s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), true ); @@ -325,7 +326,7 @@ void TestQgsMapToolIdentifyAction::closestPoint() QCOMPARE( result.at( 0 ).mDerivedAttributes[tr( "Closest Y" )], QStringLiteral( "2399800.000" ) ); } -void TestQgsMapToolIdentifyAction::lengthCalculation() +void TestQgsIdentify::lengthCalculation() { QgsSettings s; s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), true ); @@ -449,7 +450,7 @@ void TestQgsMapToolIdentifyAction::lengthCalculation() } -void TestQgsMapToolIdentifyAction::perimeterCalculation() +void TestQgsIdentify::perimeterCalculation() { QgsSettings s; s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), true ); @@ -526,7 +527,7 @@ void TestQgsMapToolIdentifyAction::perimeterCalculation() QGSCOMPARENEAR( perimeter, 128282, 0.1 ); } -void TestQgsMapToolIdentifyAction::areaCalculation() +void TestQgsIdentify::areaCalculation() { QgsSettings s; s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), true ); @@ -606,7 +607,7 @@ void TestQgsMapToolIdentifyAction::areaCalculation() } // private -QList TestQgsMapToolIdentifyAction::testIdentifyRaster( QgsRasterLayer *layer, double xGeoref, double yGeoref, bool roundToCanvasPixels ) +QList TestQgsIdentify::testIdentifyRaster( QgsRasterLayer *layer, double xGeoref, double yGeoref, bool roundToCanvasPixels ) { std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) ); const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( xGeoref, yGeoref ); @@ -622,7 +623,7 @@ QList TestQgsMapToolIdentifyAction::testIden } // private -QList TestQgsMapToolIdentifyAction::testIdentifyMesh( QgsMeshLayer *layer, double xGeoref, double yGeoref ) +QList TestQgsIdentify::testIdentifyMesh( QgsMeshLayer *layer, double xGeoref, double yGeoref ) { std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) ); const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( xGeoref, yGeoref ); @@ -636,7 +637,7 @@ QList TestQgsMapToolIdentifyAction::testIden // private QList -TestQgsMapToolIdentifyAction::testIdentifyVector( QgsVectorLayer *layer, double xGeoref, double yGeoref ) +TestQgsIdentify::testIdentifyVector( QgsVectorLayer *layer, double xGeoref, double yGeoref ) { std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) ); const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( xGeoref, yGeoref ); @@ -649,7 +650,7 @@ TestQgsMapToolIdentifyAction::testIdentifyVector( QgsVectorLayer *layer, double // private QList -TestQgsMapToolIdentifyAction::testIdentifyVectorTile( QgsVectorTileLayer *layer, double xGeoref, double yGeoref ) +TestQgsIdentify::testIdentifyVectorTile( QgsVectorTileLayer *layer, double xGeoref, double yGeoref ) { std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) ); const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( xGeoref, yGeoref ); @@ -660,7 +661,7 @@ TestQgsMapToolIdentifyAction::testIdentifyVectorTile( QgsVectorTileLayer *layer, return result; } -void TestQgsMapToolIdentifyAction::identifyRasterTemporal() +void TestQgsIdentify::identifyRasterTemporal() { //create a temporary layer const QString raster = QStringLiteral( TEST_DATA_DIR ) + "/raster/test.asc"; @@ -687,7 +688,7 @@ void TestQgsMapToolIdentifyAction::identifyRasterTemporal() QCOMPARE( testIdentifyRaster( tempLayer.get(), 0.5, 0.5 ).at( 0 ).mAttributes[QStringLiteral( "Band 1" )], QString( "-999.9" ) ); } -void TestQgsMapToolIdentifyAction::identifyRasterFloat32() +void TestQgsIdentify::identifyRasterFloat32() { //create a temporary layer const QString raster = QStringLiteral( TEST_DATA_DIR ) + "/raster/test.asc"; @@ -722,7 +723,7 @@ void TestQgsMapToolIdentifyAction::identifyRasterFloat32() QCOMPARE( testIdentifyRaster( tempLayer.get(), 6.5, 0.5 ).at( 0 ).mAttributes[QStringLiteral( "Band 1" )], QString( "1.234568" ) ); // in .asc file : 1.2345678901234 } -void TestQgsMapToolIdentifyAction::identifyRasterFloat64() +void TestQgsIdentify::identifyRasterFloat64() { //create a temporary layer const QString raster = QStringLiteral( TEST_DATA_DIR ) + "/raster/test.asc"; @@ -746,7 +747,7 @@ void TestQgsMapToolIdentifyAction::identifyRasterFloat64() QCOMPARE( testIdentifyRaster( tempLayer.get(), 6.5, 0.5 ).at( 0 ).mAttributes[QStringLiteral( "Band 1" )], QString( "1.2345678901234" ) ); } -void TestQgsMapToolIdentifyAction::identifyRasterDerivedAttributes() +void TestQgsIdentify::identifyRasterDerivedAttributes() { const QString raster = QStringLiteral( TEST_DATA_DIR ) + "/raster/dem.tif"; std::unique_ptr< QgsRasterLayer> tempLayer( new QgsRasterLayer( raster ) ); @@ -819,7 +820,7 @@ void TestQgsMapToolIdentifyAction::identifyRasterDerivedAttributes() QCOMPARE( results[0].mDerivedAttributes[QStringLiteral( "Row (0-based)" )], QString( "141" ) ); } -void TestQgsMapToolIdentifyAction::identifyMesh() +void TestQgsIdentify::identifyMesh() { //create a temporary layer const QString mesh = QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle.2dm"; @@ -922,7 +923,7 @@ void TestQgsMapToolIdentifyAction::identifyMesh() QCOMPARE( results[0].mDerivedAttributes[ QStringLiteral( "Vector y-component" )], QStringLiteral( "2.4" ) ); } -void TestQgsMapToolIdentifyAction::identifyVectorTile() +void TestQgsIdentify::identifyVectorTile() { //create a temporary layer const QString vtPath = QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/vector_tile/{z}-{x}-{y}.pbf" ); @@ -951,7 +952,7 @@ void TestQgsMapToolIdentifyAction::identifyVectorTile() delete tempLayer; } -void TestQgsMapToolIdentifyAction::identifyInvalidPolygons() +void TestQgsIdentify::identifyInvalidPolygons() { //create a temporary layer std::unique_ptr< QgsVectorLayer > memoryLayer( new QgsVectorLayer( QStringLiteral( "Polygon?field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) ); @@ -978,9 +979,197 @@ void TestQgsMapToolIdentifyAction::identifyInvalidPolygons() } +void TestQgsIdentify::testRelations() +{ + QgsVectorLayer *layerA = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk_id:integer" ), QStringLiteral( "layerA" ), QStringLiteral( "memory" ) ); + QVERIFY( layerA->isValid() ); + QgsFeature featureA( layerA->dataProvider()->fields() ); + constexpr int PK_ID_A = 1; + constexpr int PK_ID_C = 2; + featureA.setAttribute( 0, PK_ID_A ); + layerA->dataProvider()->addFeature( featureA ); + + QgsVectorLayer *layerB = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=fk_id_to_A:integer&field=fk_id_to_C:integer&field=other_field:integer" ), QStringLiteral( "layerB" ), QStringLiteral( "memory" ) ); + QVERIFY( layerB->isValid() ); + constexpr int IDX_OTHER_FIELD = 2; + constexpr int OTHER_FIELD = 100; + { + QgsFeature featureB( layerB->dataProvider()->fields() ); + featureB.setAttribute( 0, PK_ID_A ); + featureB.setAttribute( 1, PK_ID_C ); + featureB.setAttribute( IDX_OTHER_FIELD, OTHER_FIELD ); + layerB->dataProvider()->addFeature( featureB ); + } + { + QgsFeature featureB( layerB->dataProvider()->fields() ); + featureB.setAttribute( 0, PK_ID_A ); + featureB.setAttribute( 1, PK_ID_C + 1 ); + featureB.setAttribute( IDX_OTHER_FIELD, OTHER_FIELD + 1 ); + layerB->dataProvider()->addFeature( featureB ); + } + + QgsVectorLayer *layerC = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk_id:integer" ), QStringLiteral( "layerC" ), QStringLiteral( "memory" ) ); + QVERIFY( layerC->isValid() ); + { + QgsFeature featureC( layerC->dataProvider()->fields() ); + featureC.setAttribute( 0, PK_ID_C ); + layerC->dataProvider()->addFeature( featureC ); + } + { + QgsFeature featureC( layerC->dataProvider()->fields() ); + featureC.setAttribute( 0, PK_ID_C + 1 ); + layerC->dataProvider()->addFeature( featureC ); + } + + QgsProject::instance()->layerStore()->addMapLayer( layerA, true ); + QgsProject::instance()->layerStore()->addMapLayer( layerB, true ); + QgsProject::instance()->layerStore()->addMapLayer( layerC, true ); + + QgsRelationManager *relationManager = QgsProject::instance()->relationManager(); + { + QgsRelation relation; + relation.setId( "B-A-id" ); + relation.setName( "B-A" ); + relation.setReferencingLayer( layerB->id() ); + relation.setReferencedLayer( layerA->id() ); + relation.addFieldPair( QStringLiteral( "fk_id_to_A" ), QStringLiteral( "pk_id" ) ); + + relationManager->addRelation( relation ); + } + { + QgsRelation relation; + relation.setId( "A-B-id" ); + relation.setName( "A-B" ); + relation.setReferencingLayer( layerA->id() ); + relation.setReferencedLayer( layerB->id() ); + relation.addFieldPair( QStringLiteral( "pk_id" ), QStringLiteral( "fk_id_to_A" ) ); + + relationManager->addRelation( relation ); + } + { + QgsRelation relation; + relation.setId( "B-C-id" ); + relation.setName( "B-C" ); + relation.setReferencingLayer( layerB->id() ); + relation.setReferencedLayer( layerC->id() ); + relation.addFieldPair( QStringLiteral( "fk_id_to_C" ), QStringLiteral( "pk_id" ) ); + + relationManager->addRelation( relation ); + } + + std::unique_ptr dialog = std::make_unique( canvas ); + dialog->addFeature( layerA, featureA, QMap< QString, QString>() ); + + QCOMPARE( dialog->lstResults->topLevelItemCount(), 1 ); + QTreeWidgetItem *topLevelItem = dialog->lstResults->topLevelItem( 0 ); + QCOMPARE( topLevelItem->childCount(), 1 ); + QgsIdentifyResultsFeatureItem *featureItem = dynamic_cast( topLevelItem->child( 0 ) ); + QVERIFY( featureItem ); + std::vector relationItems; + for ( int i = 0; i < featureItem->childCount(); ++i ) + { + QgsIdentifyResultsRelationItem *relationItem = dynamic_cast( featureItem->child( i ) ); + if ( relationItem ) + relationItems.push_back( relationItem ); + } + QCOMPARE( relationItems.size(), 2 ); + + QCOMPARE( relationItems[0]->text( 0 ), QStringLiteral( "layerB through B-A […]" ) ); + QCOMPARE( relationItems[0]->childCount(), 0 ); + QCOMPARE( relationItems[0]->childIndicatorPolicy(), QTreeWidgetItem::ShowIndicator ); + QCOMPARE( relationItems[0]->isExpanded(), false ); + + QCOMPARE( relationItems[1]->text( 0 ), QStringLiteral( "layerB through A-B [1]" ) ); + QCOMPARE( relationItems[1]->childCount(), 0 ); + QCOMPARE( relationItems[1]->childIndicatorPolicy(), QTreeWidgetItem::ShowIndicator ); + QCOMPARE( relationItems[1]->isExpanded(), false ); + + // Check referenced relation + + // Check that expandAll() doesn't result in automatic resolution of relations + dialog->expandAll(); + QCOMPARE( relationItems[0]->childCount(), 0 ); + + relationItems[0]->setExpanded( true ); + QCOMPARE( relationItems[0]->text( 0 ), QStringLiteral( "layerB through B-A [2]" ) ); + QCOMPARE( relationItems[0]->childCount(), 2 ); + + // Check that folding/unfolding after initial expansion works + relationItems[0]->setExpanded( false ); + relationItems[0]->setExpanded( true ); + QCOMPARE( relationItems[0]->text( 0 ), QStringLiteral( "layerB through B-A [2]" ) ); + QCOMPARE( relationItems[0]->childCount(), 2 ); + + { + QgsIdentifyResultsFeatureItem *relatedFeatureItem = dynamic_cast( relationItems[0]->child( 0 ) ); + QVERIFY( relatedFeatureItem ); + QVERIFY( relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); + const QgsFeature relatedFeature = relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); + QCOMPARE( relatedFeature.attribute( IDX_OTHER_FIELD ), OTHER_FIELD ); + + { + std::vector childRelationItems; + for ( int i = 0; i < relatedFeatureItem->childCount(); ++i ) + { + QgsIdentifyResultsRelationItem *relationItem = dynamic_cast( relatedFeatureItem->child( i ) ); + if ( relationItem ) + childRelationItems.push_back( relationItem ); + } + QCOMPARE( childRelationItems.size(), 1 ); + + QCOMPARE( childRelationItems[0]->childCount(), 0 ); + QCOMPARE( childRelationItems[0]->text( 0 ), QStringLiteral( "layerC through B-C [1]" ) ); + + childRelationItems[0]->setExpanded( true ); + QCOMPARE( childRelationItems[0]->childCount(), 1 ); + QCOMPARE( childRelationItems[0]->text( 0 ), QStringLiteral( "layerC through B-C [1]" ) ); + + { + QgsIdentifyResultsFeatureItem *childRelatedFeatureItem = dynamic_cast( childRelationItems[0]->child( 0 ) ); + QVERIFY( childRelatedFeatureItem ); + QVERIFY( childRelatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); + const QgsFeature relatedFeature = childRelatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); + QCOMPARE( relatedFeature.attribute( 0 ), PK_ID_C ); + + // Check that this child doesn't link back to parent feature A + std::vector childChildRelationItems; + for ( int i = 0; i < childRelatedFeatureItem->childCount(); ++i ) + { + QgsIdentifyResultsRelationItem *relationItem = dynamic_cast( childRelatedFeatureItem->child( i ) ); + if ( relationItem ) + childChildRelationItems.push_back( relationItem ); + } + QCOMPARE( childChildRelationItems.size(), 0 ); + } + } + + } + + { + QgsIdentifyResultsFeatureItem *relatedFeatureItem = dynamic_cast( relationItems[0]->child( 1 ) ); + QVERIFY( relatedFeatureItem ); + QVERIFY( relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); + const QgsFeature relatedFeature = relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); + QCOMPARE( relatedFeature.attribute( IDX_OTHER_FIELD ), OTHER_FIELD + 1 ); + } + + // Check referencing relation + relationItems[1]->setExpanded( true ); + QCOMPARE( relationItems[1]->text( 0 ), QStringLiteral( "layerB through A-B [1]" ) ); + QCOMPARE( relationItems[1]->childCount(), 1 ); + + { + QgsIdentifyResultsFeatureItem *relatedFeatureItem = dynamic_cast( relationItems[1]->child( 0 ) ); + QVERIFY( relatedFeatureItem ); + QVERIFY( relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); + const QgsFeature relatedFeature = relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); + QCOMPARE( relatedFeature.attribute( IDX_OTHER_FIELD ), OTHER_FIELD ); + } +} + -QGSTEST_MAIN( TestQgsMapToolIdentifyAction ) -#include "testqgsmaptoolidentifyaction.moc" +QGSTEST_MAIN( TestQgsIdentify ) +#include "testqgsidentify.moc" diff --git a/tests/src/app/testqgsidentifyresultsdialog.cpp b/tests/src/app/testqgsidentifyresultsdialog.cpp deleted file mode 100644 index 27b8a5059917..000000000000 --- a/tests/src/app/testqgsidentifyresultsdialog.cpp +++ /dev/null @@ -1,270 +0,0 @@ -/*************************************************************************** - testqgsidentifyresultsdialog.cpp - -------------------------------- - Date : 2024-06-21 - Copyright : (C) 2024 by Even Rouault - Email : even.rouault at spatialys.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 "qgstest.h" - -#include "qgisapp.h" -#include "qgsapplication.h" -#include "qgsidentifyresultsdialog.h" -#include "qgsmapcanvas.h" -#include "qgsproject.h" -#include "qgsrelation.h" -#include "qgsrelationmanager.h" -#include "qgsvectorlayer.h" - -class TestQgsIdentifyResultsDialog : public QObject -{ - Q_OBJECT - public: - TestQgsIdentifyResultsDialog() = default; - - private slots: - void initTestCase(); // will be called before the first testfunction is executed. - void cleanupTestCase(); // will be called after the last testfunction was executed. - void init(); // will be called before each testfunction is executed. - void cleanup(); // will be called after every testfunction. - - void testRelations(); - - private: - - QgsMapCanvas *mCanvas = nullptr; - QgisApp *mQgisApp = nullptr; -}; - -void TestQgsIdentifyResultsDialog::initTestCase() -{ - QgsApplication::init(); - QgsApplication::initQgis(); - // Set up the QgsSettings environment - QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) ); - QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) ); - QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) ); - - QgsApplication::showSettings(); - - // enforce C locale because the tests expect it - // (decimal separators / thousand separators) - QLocale::setDefault( QLocale::c() ); - - mQgisApp = new QgisApp(); -} - -void TestQgsIdentifyResultsDialog::cleanupTestCase() -{ - QgsApplication::exitQgis(); -} - -void TestQgsIdentifyResultsDialog::init() -{ - mCanvas = new QgsMapCanvas(); -} - -void TestQgsIdentifyResultsDialog::cleanup() -{ - delete mCanvas; -} - -void TestQgsIdentifyResultsDialog::testRelations() -{ - QgsVectorLayer *layerA = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk_id:integer" ), QStringLiteral( "layerA" ), QStringLiteral( "memory" ) ); - QVERIFY( layerA->isValid() ); - QgsFeature featureA( layerA->dataProvider()->fields() ); - constexpr int PK_ID_A = 1; - constexpr int PK_ID_C = 2; - featureA.setAttribute( 0, PK_ID_A ); - layerA->dataProvider()->addFeature( featureA ); - - QgsVectorLayer *layerB = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=fk_id_to_A:integer&field=fk_id_to_C:integer&field=other_field:integer" ), QStringLiteral( "layerB" ), QStringLiteral( "memory" ) ); - QVERIFY( layerB->isValid() ); - constexpr int IDX_OTHER_FIELD = 2; - constexpr int OTHER_FIELD = 100; - { - QgsFeature featureB( layerB->dataProvider()->fields() ); - featureB.setAttribute( 0, PK_ID_A ); - featureB.setAttribute( 1, PK_ID_C ); - featureB.setAttribute( IDX_OTHER_FIELD, OTHER_FIELD ); - layerB->dataProvider()->addFeature( featureB ); - } - { - QgsFeature featureB( layerB->dataProvider()->fields() ); - featureB.setAttribute( 0, PK_ID_A ); - featureB.setAttribute( 1, PK_ID_C + 1 ); - featureB.setAttribute( IDX_OTHER_FIELD, OTHER_FIELD + 1 ); - layerB->dataProvider()->addFeature( featureB ); - } - - QgsVectorLayer *layerC = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk_id:integer" ), QStringLiteral( "layerC" ), QStringLiteral( "memory" ) ); - QVERIFY( layerC->isValid() ); - { - QgsFeature featureC( layerC->dataProvider()->fields() ); - featureC.setAttribute( 0, PK_ID_C ); - layerC->dataProvider()->addFeature( featureC ); - } - { - QgsFeature featureC( layerC->dataProvider()->fields() ); - featureC.setAttribute( 0, PK_ID_C + 1 ); - layerC->dataProvider()->addFeature( featureC ); - } - - QgsProject::instance()->layerStore()->addMapLayer( layerA, true ); - QgsProject::instance()->layerStore()->addMapLayer( layerB, true ); - QgsProject::instance()->layerStore()->addMapLayer( layerC, true ); - - QgsRelationManager *relationManager = QgsProject::instance()->relationManager(); - { - QgsRelation relation; - relation.setId( "B-A-id" ); - relation.setName( "B-A" ); - relation.setReferencingLayer( layerB->id() ); - relation.setReferencedLayer( layerA->id() ); - relation.addFieldPair( QStringLiteral( "fk_id_to_A" ), QStringLiteral( "pk_id" ) ); - - relationManager->addRelation( relation ); - } - { - QgsRelation relation; - relation.setId( "A-B-id" ); - relation.setName( "A-B" ); - relation.setReferencingLayer( layerA->id() ); - relation.setReferencedLayer( layerB->id() ); - relation.addFieldPair( QStringLiteral( "pk_id" ), QStringLiteral( "fk_id_to_A" ) ); - - relationManager->addRelation( relation ); - } - { - QgsRelation relation; - relation.setId( "B-C-id" ); - relation.setName( "B-C" ); - relation.setReferencingLayer( layerB->id() ); - relation.setReferencedLayer( layerC->id() ); - relation.addFieldPair( QStringLiteral( "fk_id_to_C" ), QStringLiteral( "pk_id" ) ); - - relationManager->addRelation( relation ); - } - - std::unique_ptr dialog = std::make_unique( mCanvas ); - dialog->addFeature( layerA, featureA, QMap< QString, QString>() ); - - QCOMPARE( dialog->lstResults->topLevelItemCount(), 1 ); - QTreeWidgetItem *topLevelItem = dialog->lstResults->topLevelItem( 0 ); - QCOMPARE( topLevelItem->childCount(), 1 ); - QgsIdentifyResultsFeatureItem *featureItem = dynamic_cast( topLevelItem->child( 0 ) ); - QVERIFY( featureItem ); - std::vector relationItems; - for ( int i = 0; i < featureItem->childCount(); ++i ) - { - QgsIdentifyResultsRelationItem *relationItem = dynamic_cast( featureItem->child( i ) ); - if ( relationItem ) - relationItems.push_back( relationItem ); - } - QCOMPARE( relationItems.size(), 2 ); - - QCOMPARE( relationItems[0]->text( 0 ), QStringLiteral( "layerB through B-A […]" ) ); - QCOMPARE( relationItems[0]->childCount(), 0 ); - QCOMPARE( relationItems[0]->childIndicatorPolicy(), QTreeWidgetItem::ShowIndicator ); - QCOMPARE( relationItems[0]->isExpanded(), false ); - - QCOMPARE( relationItems[1]->text( 0 ), QStringLiteral( "layerB through A-B [1]" ) ); - QCOMPARE( relationItems[1]->childCount(), 0 ); - QCOMPARE( relationItems[1]->childIndicatorPolicy(), QTreeWidgetItem::ShowIndicator ); - QCOMPARE( relationItems[1]->isExpanded(), false ); - - // Check referenced relation - - // Check that expandAll() doesn't result in automatic resolution of relations - dialog->expandAll(); - QCOMPARE( relationItems[0]->childCount(), 0 ); - - relationItems[0]->setExpanded( true ); - QCOMPARE( relationItems[0]->text( 0 ), QStringLiteral( "layerB through B-A [2]" ) ); - QCOMPARE( relationItems[0]->childCount(), 2 ); - - // Check that folding/unfolding after initial expansion works - relationItems[0]->setExpanded( false ); - relationItems[0]->setExpanded( true ); - QCOMPARE( relationItems[0]->text( 0 ), QStringLiteral( "layerB through B-A [2]" ) ); - QCOMPARE( relationItems[0]->childCount(), 2 ); - - { - QgsIdentifyResultsFeatureItem *relatedFeatureItem = dynamic_cast( relationItems[0]->child( 0 ) ); - QVERIFY( relatedFeatureItem ); - QVERIFY( relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); - const QgsFeature relatedFeature = relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); - QCOMPARE( relatedFeature.attribute( IDX_OTHER_FIELD ), OTHER_FIELD ); - - { - std::vector childRelationItems; - for ( int i = 0; i < relatedFeatureItem->childCount(); ++i ) - { - QgsIdentifyResultsRelationItem *relationItem = dynamic_cast( relatedFeatureItem->child( i ) ); - if ( relationItem ) - childRelationItems.push_back( relationItem ); - } - QCOMPARE( childRelationItems.size(), 1 ); - - QCOMPARE( childRelationItems[0]->childCount(), 0 ); - QCOMPARE( childRelationItems[0]->text( 0 ), QStringLiteral( "layerC through B-C [1]" ) ); - - childRelationItems[0]->setExpanded( true ); - QCOMPARE( childRelationItems[0]->childCount(), 1 ); - QCOMPARE( childRelationItems[0]->text( 0 ), QStringLiteral( "layerC through B-C [1]" ) ); - - { - QgsIdentifyResultsFeatureItem *childRelatedFeatureItem = dynamic_cast( childRelationItems[0]->child( 0 ) ); - QVERIFY( childRelatedFeatureItem ); - QVERIFY( childRelatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); - const QgsFeature relatedFeature = childRelatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); - QCOMPARE( relatedFeature.attribute( 0 ), PK_ID_C ); - - // Check that this child doesn't link back to parent feature A - std::vector childChildRelationItems; - for ( int i = 0; i < childRelatedFeatureItem->childCount(); ++i ) - { - QgsIdentifyResultsRelationItem *relationItem = dynamic_cast( childRelatedFeatureItem->child( i ) ); - if ( relationItem ) - childChildRelationItems.push_back( relationItem ); - } - QCOMPARE( childChildRelationItems.size(), 0 ); - } - } - - } - - { - QgsIdentifyResultsFeatureItem *relatedFeatureItem = dynamic_cast( relationItems[0]->child( 1 ) ); - QVERIFY( relatedFeatureItem ); - QVERIFY( relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); - const QgsFeature relatedFeature = relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); - QCOMPARE( relatedFeature.attribute( IDX_OTHER_FIELD ), OTHER_FIELD + 1 ); - } - - // Check referencing relation - relationItems[1]->setExpanded( true ); - QCOMPARE( relationItems[1]->text( 0 ), QStringLiteral( "layerB through A-B [1]" ) ); - QCOMPARE( relationItems[1]->childCount(), 1 ); - - { - QgsIdentifyResultsFeatureItem *relatedFeatureItem = dynamic_cast( relationItems[1]->child( 0 ) ); - QVERIFY( relatedFeatureItem ); - QVERIFY( relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).isValid() ); - const QgsFeature relatedFeature = relatedFeatureItem->data( 0, QgsIdentifyResultsDialog::FeatureRole ).value< QgsFeature >(); - QCOMPARE( relatedFeature.attribute( IDX_OTHER_FIELD ), OTHER_FIELD ); - } -} - - -QGSTEST_MAIN( TestQgsIdentifyResultsDialog ) -#include "testqgsidentifyresultsdialog.moc" From 077a4c1a9b20543d4016285590b4dae2b26d78cb Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 24 Jun 2024 23:21:58 +0200 Subject: [PATCH 4/5] [Identify tool] Change to single setting for referenced and referencing relations --- src/app/qgsidentifyresultsdialog.cpp | 26 ++++++++------------------ src/app/qgsidentifyresultsdialog.h | 7 ++----- src/ui/qgsidentifyresultsbase.ui | 17 +++-------------- 3 files changed, 13 insertions(+), 37 deletions(-) diff --git a/src/app/qgsidentifyresultsdialog.cpp b/src/app/qgsidentifyresultsdialog.cpp index 5d257335ad10..cd052efefb8d 100644 --- a/src/app/qgsidentifyresultsdialog.cpp +++ b/src/app/qgsidentifyresultsdialog.cpp @@ -108,9 +108,7 @@ constexpr int REPRESENTED_VALUE_ROLE = Qt::UserRole + 2; const QgsSettingsEntryBool *QgsIdentifyResultsDialog::settingHideNullValues = new QgsSettingsEntryBool( QStringLiteral( "hide-null-values" ), QgsSettingsTree::sTreeMap, false, QStringLiteral( "Whether to hide attributes with NULL values in the identify feature result" ) ); -const QgsSettingsEntryBool *QgsIdentifyResultsDialog::settingShowReferencingRelations = new QgsSettingsEntryBool( QStringLiteral( "show-referencing-relations" ), QgsSettingsTree::sTreeMap, true, QStringLiteral( "Whether to show referencing relations in the identify feature result" ) ); - -const QgsSettingsEntryBool *QgsIdentifyResultsDialog::settingShowReferencedRelations = new QgsSettingsEntryBool( QStringLiteral( "show-referenced-relations" ), QgsSettingsTree::sTreeMap, true, QStringLiteral( "Whether to show referenced relations in the identify feature result" ) ); +const QgsSettingsEntryBool *QgsIdentifyResultsDialog::settingShowRelations = new QgsSettingsEntryBool( QStringLiteral( "show-relations" ), QgsSettingsTree::sTreeMap, true, QStringLiteral( "Whether to show relations in the identify feature result" ) ); QgsIdentifyResultsWebView::QgsIdentifyResultsWebView( QWidget *parent ) : QgsWebView( parent ) @@ -374,8 +372,7 @@ QgsIdentifyResultsDialog::QgsIdentifyResultsDialog( QgsMapCanvas *canvas, QWidge connect( mActionAutoFeatureForm, &QAction::toggled, this, &QgsIdentifyResultsDialog::mActionAutoFeatureForm_toggled ); connect( mActionHideDerivedAttributes, &QAction::toggled, this, &QgsIdentifyResultsDialog::mActionHideDerivedAttributes_toggled ); connect( mActionHideNullValues, &QAction::toggled, this, &QgsIdentifyResultsDialog::mActionHideNullValues_toggled ); - connect( mActionShowReferencingRelations, &QAction::toggled, this, &QgsIdentifyResultsDialog::mActionShowReferencingRelations_toggled ); - connect( mActionShowReferencedRelations, &QAction::toggled, this, &QgsIdentifyResultsDialog::mActionShowReferencedRelations_toggled ); + connect( mActionShowRelations, &QAction::toggled, this, &QgsIdentifyResultsDialog::mActionShowRelations_toggled ); mOpenFormAction->setDisabled( true ); @@ -488,10 +485,8 @@ QgsIdentifyResultsDialog::QgsIdentifyResultsDialog( QgsMapCanvas *canvas, QWidge mActionHideDerivedAttributes->setChecked( mySettings.value( QStringLiteral( "Map/hideDerivedAttributes" ), false ).toBool() ); settingsMenu->addAction( mActionHideNullValues ); mActionHideNullValues->setChecked( QgsIdentifyResultsDialog::settingHideNullValues->value() ); - settingsMenu->addAction( mActionShowReferencedRelations ); - mActionShowReferencedRelations->setChecked( QgsIdentifyResultsDialog::settingShowReferencedRelations->value() ); - settingsMenu->addAction( mActionShowReferencingRelations ); - mActionShowReferencingRelations->setChecked( QgsIdentifyResultsDialog::settingShowReferencingRelations->value() ); + settingsMenu->addAction( mActionShowRelations ); + mActionShowRelations->setChecked( QgsIdentifyResultsDialog::settingShowRelations->value() ); } @@ -850,7 +845,7 @@ QgsIdentifyResultsFeatureItem *QgsIdentifyResultsDialog::createFeatureItem( QgsV } // add entries for related items coming from referenced relations - if ( QgsIdentifyResultsDialog::settingShowReferencedRelations->value() ) + if ( QgsIdentifyResultsDialog::settingShowRelations->value() ) { const QList relations = QgsProject::instance()->relationManager()->referencedRelations( vlayer ); if ( !relations.empty() ) @@ -879,7 +874,7 @@ QgsIdentifyResultsFeatureItem *QgsIdentifyResultsDialog::createFeatureItem( QgsV } // add entries for related items coming from referencing relations - if ( QgsIdentifyResultsDialog::settingShowReferencingRelations->value() ) + if ( QgsIdentifyResultsDialog::settingShowRelations->value() ) { const QList relations = QgsProject::instance()->relationManager()->referencingRelations( vlayer ); if ( !relations.empty() ) @@ -2676,14 +2671,9 @@ void QgsIdentifyResultsDialog::mActionHideNullValues_toggled( bool checked ) QgsIdentifyResultsDialog::settingHideNullValues->setValue( checked ); } -void QgsIdentifyResultsDialog::mActionShowReferencedRelations_toggled( bool checked ) -{ - QgsIdentifyResultsDialog::settingShowReferencedRelations->setValue( checked ); -} - -void QgsIdentifyResultsDialog::mActionShowReferencingRelations_toggled( bool checked ) +void QgsIdentifyResultsDialog::mActionShowRelations_toggled( bool checked ) { - QgsIdentifyResultsDialog::settingShowReferencingRelations->setValue( checked ); + QgsIdentifyResultsDialog::settingShowRelations->setValue( checked ); } void QgsIdentifyResultsDialog::mExpandNewAction_triggered( bool checked ) diff --git a/src/app/qgsidentifyresultsdialog.h b/src/app/qgsidentifyresultsdialog.h index aef05544478c..742487d33f59 100644 --- a/src/app/qgsidentifyresultsdialog.h +++ b/src/app/qgsidentifyresultsdialog.h @@ -160,8 +160,7 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti ~QgsIdentifyResultsDialog() override; static const QgsSettingsEntryBool *settingHideNullValues; - static const QgsSettingsEntryBool *settingShowReferencingRelations; - static const QgsSettingsEntryBool *settingShowReferencedRelations; + static const QgsSettingsEntryBool *settingShowRelations; //! Adds feature from vector layer void addFeature( QgsVectorLayer *layer, @@ -304,9 +303,7 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti void mActionHideNullValues_toggled( bool checked ); - void mActionShowReferencingRelations_toggled( bool checked ); - - void mActionShowReferencedRelations_toggled( bool checked ); + void mActionShowRelations_toggled( bool checked ); void mExpandAction_triggered( bool checked ) { Q_UNUSED( checked ) expandAll(); } void mCollapseAction_triggered( bool checked ) { Q_UNUSED( checked ) collapseAll(); } diff --git a/src/ui/qgsidentifyresultsbase.ui b/src/ui/qgsidentifyresultsbase.ui index 568797d29478..c9d379693e7b 100644 --- a/src/ui/qgsidentifyresultsbase.ui +++ b/src/ui/qgsidentifyresultsbase.ui @@ -383,26 +383,15 @@ Hide derived attributes from results - + true - Show referencing relations + Show Relations - Show referencing relations - - - - - true - - - Show referenced relations - - - Show referenced relations + Show Relations From 76a0c7f6aa0192f954e67dd902127195239b17ae Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 24 Jun 2024 23:23:38 +0200 Subject: [PATCH 5/5] [Identify tool] Rename 'Explore Feature' action to 'Identify Feature' --- src/app/qgsidentifyresultsdialog.cpp | 4 ++-- src/app/qgsidentifyresultsdialog.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/qgsidentifyresultsdialog.cpp b/src/app/qgsidentifyresultsdialog.cpp index cd052efefb8d..e6e0b0023009 100644 --- a/src/app/qgsidentifyresultsdialog.cpp +++ b/src/app/qgsidentifyresultsdialog.cpp @@ -1696,7 +1696,7 @@ void QgsIdentifyResultsDialog::contextMenuEvent( QContextMenuEvent *event ) mActionPopup->addAction( tr( "Zoom to Feature" ), this, &QgsIdentifyResultsDialog::zoomToFeature ); if ( vlayer && dynamic_cast( featItem->parent() ) ) { - mActionPopup->addAction( tr( "Explore Feature" ), this, &QgsIdentifyResultsDialog::exploreFeature ); + mActionPopup->addAction( tr( "Identify Feature" ), this, &QgsIdentifyResultsDialog::identifyFeature ); } mActionPopup->addAction( tr( "Copy Feature" ), this, &QgsIdentifyResultsDialog::copyFeature ); if ( vlayer ) @@ -2409,7 +2409,7 @@ void QgsIdentifyResultsDialog::zoomToFeature() mCanvas->refresh(); } -void QgsIdentifyResultsDialog::exploreFeature() +void QgsIdentifyResultsDialog::identifyFeature() { QTreeWidgetItem *item = lstResults->currentItem(); QgsVectorLayer *vlayer = QgsIdentifyResultsDialog::vectorLayer( item ); diff --git a/src/app/qgsidentifyresultsdialog.h b/src/app/qgsidentifyresultsdialog.h index 742487d33f59..c9e05db22d7d 100644 --- a/src/app/qgsidentifyresultsdialog.h +++ b/src/app/qgsidentifyresultsdialog.h @@ -266,7 +266,7 @@ class APP_EXPORT QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdenti void featureForm(); void zoomToFeature(); - void exploreFeature(); + void identifyFeature(); void copyAttributeValue(); void copyFeature(); void toggleFeatureSelection();