diff --git a/python/gui/auto_generated/qgsnewdatabasetablenamewidget.sip.in b/python/gui/auto_generated/qgsnewdatabasetablenamewidget.sip.in index b3d85393add..598112e221a 100644 --- a/python/gui/auto_generated/qgsnewdatabasetablenamewidget.sip.in +++ b/python/gui/auto_generated/qgsnewdatabasetablenamewidget.sip.in @@ -20,6 +20,11 @@ The table name is validated for uniqueness and the selected data item provider, schema and table names can be retrieved with getters. +.. warning:: + + The data provider that originated the data item provider + must support the connections API + .. versionadded:: 3.14 %End @@ -35,8 +40,9 @@ getters. Constructs a new QgsNewDatabaseTableNameWidget :param browserModel: an existing browser model (typically from app), if NULL an instance will be created -:param providersFilter: optional white list of item provider names (not data providers!) that should be - shown in the widget, if not specified all providers data items with database capabilities will be shown +:param providersFilter: optional white list of data provider keys that should be + shown in the widget, if not specified all providers data items with database + capabilities will be shown :param parent: optional parent for this widget %End @@ -50,9 +56,9 @@ Returns the currently selected schema for the new table Returns the current name of the new table %End - QString dataItemProviderName(); + QString dataProviderKey(); %Docstring -Returns the currently selected data item provider name (which is NOT the data provider key!) for the new table +Returns the currently selected data item provider key %End bool isValid() const; @@ -88,6 +94,14 @@ This signal is emitted when the user enters a table name :param tableName: the name of the new table %End + void providerKeyChanged( const QString &providerKey ); +%Docstring +This signal is emitted when the selects a data provider or a schema name +that has a different data provider than the previously selected one. + +:param providerKey: the data provider key of the selected schema +%End + }; diff --git a/src/gui/qgsnewdatabasetablenamewidget.cpp b/src/gui/qgsnewdatabasetablenamewidget.cpp index 7b7443cb9d5..efd70b2f1c7 100644 --- a/src/gui/qgsnewdatabasetablenamewidget.cpp +++ b/src/gui/qgsnewdatabasetablenamewidget.cpp @@ -20,6 +20,8 @@ #include "qgsapplication.h" #include "qgsdataitemproviderregistry.h" #include "qgsdataitemprovider.h" +#include "qgsproviderregistry.h" +#include "qgsprovidermetadata.h" QgsNewDatabaseTableNameWidget::QgsNewDatabaseTableNameWidget( QgsBrowserGuiModel *browserModel, @@ -28,7 +30,6 @@ QgsNewDatabaseTableNameWidget::QgsNewDatabaseTableNameWidget( : QWidget( parent ) { - // Initalize the browser if ( ! browserModel ) { @@ -43,6 +44,8 @@ QgsNewDatabaseTableNameWidget::QgsNewDatabaseTableNameWidget( setupUi( this ); + mValidationResults->setStyleSheet( QStringLiteral( "* { font-weight: bold; color: red; }" ) ); + QStringList hiddenProviders { QStringLiteral( "special:Favorites" ), @@ -55,15 +58,26 @@ QgsNewDatabaseTableNameWidget::QgsNewDatabaseTableNameWidget( const auto providerList { QgsApplication::dataItemProviderRegistry()->providers() }; for ( const auto &provider : providerList ) { + if ( provider->dataProviderKey().isEmpty() ) + { + hiddenProviders.push_back( provider->name() ); + continue; + } + QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( provider->dataProviderKey() ) }; + if ( ! md ) + { + hiddenProviders.push_back( provider->name() ); + continue; + } if ( provider->capabilities() & QgsDataProvider::DataCapability::Database ) { - if ( ! providersFilter.isEmpty() && ! providersFilter.contains( provider->name() ) ) + if ( ! providersFilter.isEmpty() && ! providersFilter.contains( provider->dataProviderKey() ) ) { hiddenProviders.push_back( provider->name() ); } else { - mShownProviders.insert( provider->name() ); + mShownProviders.insert( provider->dataProviderKey() ); } } else @@ -98,26 +112,41 @@ QgsNewDatabaseTableNameWidget::QgsNewDatabaseTableNameWidget( const QgsDataCollectionItem *collectionItem = qobject_cast( dataItem ); if ( collectionItem ) { - if ( mShownProviders.contains( collectionItem->name() ) ) + const QString providerKey { QgsApplication::dataItemProviderRegistry()->dataProviderKey( dataItem->providerKey() ) }; + if ( mShownProviders.contains( providerKey ) ) { - if ( mDataProviderName != collectionItem->name() ) + bool validationRequired { false }; + const QString oldSchema { mSchemaName }; + + if ( mDataProviderKey != providerKey ) { mSchemaName.clear(); - mDataProviderName = collectionItem->name(); + emit providerKeyChanged( providerKey ); + mDataProviderKey = providerKey; + validate(); + } + + if ( collectionItem->layerCollection( ) ) + { + mSchemaName = collectionItem->name(); // it may be cleared + if ( oldSchema != collectionItem->name() ) + { + emit schemaNameChanged( mSchemaName ); + validationRequired = true; + } + } + + if ( validationRequired ) + { + validate(); } } - else - { - mSchemaName = collectionItem->name(); - emit schemaNameChanged( mSchemaName ); - } - validate(); } } } } ); - mValidationResults->hide(); + validate(); } @@ -131,16 +160,17 @@ QString QgsNewDatabaseTableNameWidget::table() return mTableName; } -QString QgsNewDatabaseTableNameWidget::dataItemProviderName() +QString QgsNewDatabaseTableNameWidget::dataProviderKey() { - return mDataProviderName; + return mDataProviderKey; } void QgsNewDatabaseTableNameWidget::validate() { + const bool wasValid { mIsValid }; // Check table uniqueness - mIsValid = ! mDataProviderName.isEmpty() && - mShownProviders.contains( mDataProviderName ) && + mIsValid = ! mDataProviderKey.isEmpty() && + mShownProviders.contains( mDataProviderKey ) && ! mSchemaName.isEmpty() && ! mTableName.isEmpty() && ! tableNames( ).contains( mTableName ); @@ -149,27 +179,39 @@ void QgsNewDatabaseTableNameWidget::validate() if ( ! mIsValid ) { - - if ( mTableName.isEmpty() ) + if ( mTableName.isEmpty() && mSchemaName.isEmpty() ) { - mValidationError = tr( "Enter a unique name for the new table" ); + mValidationError = tr( "Select a database schema and enter a unique name for the new table" ); + } + else if ( ! mTableName.isEmpty() && + ! mSchemaName.isEmpty() && + tableNames( ).contains( mTableName ) ) + { + mValidationError = tr( "A table named '%1' already exists" ).arg( mTableName ); } else if ( mSchemaName.isEmpty() ) { mValidationError = tr( "Select a database schema" ); } + else if ( mTableName.isEmpty() ) + { + mValidationError = tr( "Enter a unique name for the new table" ); + } else if ( tableNames( ).contains( mTableName ) ) { mValidationError = tr( "A table named '%1' already exists" ).arg( mTableName ); } else { - mValidationError = tr( "Select a schema and enter a unique name for the new table" ); + mValidationError = tr( "Select a database schema and enter a unique name for the new table" ); } } mValidationResults->setText( mValidationError ); mValidationResults->setVisible( ! mIsValid ); - emit validationChanged( mIsValid ); + if ( wasValid != mIsValid ) + { + emit validationChanged( mIsValid ); + } } QStringList QgsNewDatabaseTableNameWidget::tableNames() @@ -178,16 +220,34 @@ QStringList QgsNewDatabaseTableNameWidget::tableNames() QModelIndex index { mBrowserTreeView->currentIndex() }; if ( index.isValid() ) { - for ( int row = 0; row < mBrowserProxyModel.rowCount( ); ++row ) + QgsDataItem *dataItem { mBrowserProxyModel.dataItem( index ) }; + if ( dataItem ) { - // Column 1 contains the - index = mBrowserProxyModel.index( row, 1, index ); - if ( index.isValid() ) + const QString dataProviderKey { QgsApplication::dataItemProviderRegistry()->dataProviderKey( dataItem->providerKey() ) }; + if ( ! dataProviderKey.isEmpty() ) { - const QgsDataItem *dataItem { mBrowserProxyModel.dataItem( index ) }; - if ( dataItem ) + QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( dataProviderKey ) }; + if ( md ) { - tableNames.push_back( dataItem->name() ); + QgsDataItem *parentDataItem { dataItem->parent() }; + if ( parentDataItem ) + { + QgsAbstractProviderConnection *conn { md->findConnection( parentDataItem->name() ) }; + const QString cacheKey { conn->uri() + dataItem->name() }; + if ( mTableNamesCache.contains( cacheKey ) ) + { + tableNames = mTableNamesCache.value( cacheKey ); + } + else if ( conn && static_cast( conn ) ) + { + const auto tables { static_cast( conn )->tables( dataItem->name() ) }; + for ( const auto &tp : tables ) + { + tableNames.push_back( tp.tableName() ); + } + mTableNamesCache[ cacheKey ] = tableNames; + } + } } } } diff --git a/src/gui/qgsnewdatabasetablenamewidget.h b/src/gui/qgsnewdatabasetablenamewidget.h index caca3269490..66c32de17fc 100644 --- a/src/gui/qgsnewdatabasetablenamewidget.h +++ b/src/gui/qgsnewdatabasetablenamewidget.h @@ -34,6 +34,9 @@ * data item provider, schema and table names can be retrieved with * getters. * + * \warning The data provider that originated the data item provider + * must support the connections API + * * \since QGIS 3.14 */ class GUI_EXPORT QgsNewDatabaseTableNameWidget : public QWidget, private Ui::QgsNewDatabaseTableNameWidget @@ -46,8 +49,9 @@ class GUI_EXPORT QgsNewDatabaseTableNameWidget : public QWidget, private Ui::Qgs * Constructs a new QgsNewDatabaseTableNameWidget * * \param browserModel an existing browser model (typically from app), if NULL an instance will be created - * \param providersFilter optional white list of item provider names (not data providers!) that should be - * shown in the widget, if not specified all providers data items with database capabilities will be shown + * \param providersFilter optional white list of data provider keys that should be + * shown in the widget, if not specified all providers data items with database + * capabilities will be shown * \param parent optional parent for this widget */ explicit QgsNewDatabaseTableNameWidget( QgsBrowserGuiModel *browserModel = nullptr, @@ -65,9 +69,9 @@ class GUI_EXPORT QgsNewDatabaseTableNameWidget : public QWidget, private Ui::Qgs QString table(); /** - * Returns the currently selected data item provider name (which is NOT the data provider key!) for the new table + * Returns the currently selected data item provider key */ - QString dataItemProviderName(); + QString dataProviderKey(); /** * Returns TRUE if the widget contains a valid new table name @@ -102,6 +106,14 @@ class GUI_EXPORT QgsNewDatabaseTableNameWidget : public QWidget, private Ui::Qgs */ void tableNameChanged( const QString &tableName ); + /** + * This signal is emitted when the selects a data provider or a schema name + * that has a different data provider than the previously selected one. + * + * \param providerKey the data provider key of the selected schema + */ + void providerKeyChanged( const QString &providerKey ); + private: @@ -109,12 +121,16 @@ class GUI_EXPORT QgsNewDatabaseTableNameWidget : public QWidget, private Ui::Qgs QgsBrowserGuiModel *mBrowserModel = nullptr; void validate(); QStringList tableNames(); - QString mDataProviderName; + QString mDataProviderKey; QString mTableName; QString mSchemaName; + //! List of data provider keys of shown providers QSet mShownProviders; bool mIsValid = false; QString mValidationError; + //! Table names cache + QMap mTableNamesCache; + // For testing: friend class TestQgsNewDatabaseTableNameWidget; diff --git a/tests/src/gui/testqgsnewdatabasetablewidget.cpp b/tests/src/gui/testqgsnewdatabasetablewidget.cpp index f0115b5e351..7102196e7b8 100644 --- a/tests/src/gui/testqgsnewdatabasetablewidget.cpp +++ b/tests/src/gui/testqgsnewdatabasetablewidget.cpp @@ -30,8 +30,6 @@ class TestQgsNewDatabaseTableNameWidget: public QObject public: TestQgsNewDatabaseTableNameWidget() = default; - void testWidget(); - private slots: void initTestCase(); // will be called before the first testfunction is executed. void cleanupTestCase(); // will be called after the last testfunction was executed. @@ -74,14 +72,14 @@ void TestQgsNewDatabaseTableNameWidget::testWidgetFilters() QCOMPARE( w->mBrowserProxyModel.rowCount(), 0 ); std::unique_ptr w2 { qgis::make_unique( nullptr ) }; QVERIFY( w2->mBrowserProxyModel.rowCount() > 0 ); - std::unique_ptr w3 { qgis::make_unique( nullptr, QStringList{ "PostGIS" } ) }; + std::unique_ptr w3 { qgis::make_unique( nullptr, QStringList{ "postgres" } ) }; QVERIFY( w3->mBrowserProxyModel.rowCount() > 0 ); } void TestQgsNewDatabaseTableNameWidget::testWidgetSignals() { - std::unique_ptr w { qgis::make_unique( nullptr, QStringList{ "PostGIS" } ) }; + std::unique_ptr w { qgis::make_unique( nullptr, QStringList{ "postgres" } ) }; auto index = w->mBrowserModel->findPath( QStringLiteral( "pg:/PG_1" ) ); QVERIFY( index.isValid() ); @@ -93,6 +91,7 @@ void TestQgsNewDatabaseTableNameWidget::testWidgetSignals() QSignalSpy validationSpy( w.get(), SIGNAL( validationChanged( bool ) ) ); QSignalSpy schemaSpy( w.get(), SIGNAL( schemaNameChanged( QString ) ) ); QSignalSpy tableSpy( w.get(), SIGNAL( tableNameChanged( QString ) ) ); + QSignalSpy providerSpy( w.get(), SIGNAL( providerKeyChanged( QString ) ) ); index = w->mBrowserProxyModel.mapToSource( w->mBrowserProxyModel.index( 0, 0 ) ); QVERIFY( index.isValid() ); @@ -103,10 +102,12 @@ void TestQgsNewDatabaseTableNameWidget::testWidgetSignals() QVERIFY( ! w->isValid() ); - QCOMPARE( validationSpy.count(), 1 ); - auto arguments = validationSpy.takeLast(); - QCOMPARE( arguments.at( 0 ).toBool(), false ); + QCOMPARE( providerSpy.count(), 1 ); + QCOMPARE( tableSpy.count(), 0 ); QCOMPARE( schemaSpy.count(), 0 ); + QCOMPARE( validationSpy.count(), 0 ); + auto arguments = providerSpy.takeLast(); + QCOMPARE( arguments.at( 0 ).toString(), QString( "postgres" ) ); // Find qgis_test schema item index = w->mBrowserModel->findPath( QStringLiteral( "pg:/PG_1/qgis_test" ), Qt::MatchFlag::MatchStartsWith ); @@ -115,9 +116,10 @@ void TestQgsNewDatabaseTableNameWidget::testWidgetSignals() rect = w->mBrowserTreeView->visualRect( w->mBrowserProxyModel.mapFromSource( index ) ); QVERIFY( rect.isValid() ); QTest::mouseClick( w->mBrowserTreeView->viewport(), Qt::LeftButton, 0, rect.center() ); - QCOMPARE( validationSpy.count(), 1 ); - arguments = validationSpy.takeLast(); - QCOMPARE( arguments.at( 0 ).toBool(), false ); + + QVERIFY( ! w->isValid() ); + + QCOMPARE( validationSpy.count(), 0 ); QCOMPARE( schemaSpy.count(), 1 ); arguments = schemaSpy.takeLast(); QCOMPARE( arguments.at( 0 ).toString(), QString( "qgis_test" ) ); @@ -126,38 +128,49 @@ void TestQgsNewDatabaseTableNameWidget::testWidgetSignals() QCOMPARE( tableSpy.count(), 1 ); arguments = tableSpy.takeLast(); QCOMPARE( arguments.at( 0 ).toString(), QString( "someNewTableData" ) ); - + QCOMPARE( validationSpy.count(), 1 ); + arguments = validationSpy.takeLast(); + QCOMPARE( arguments.at( 0 ).toBool(), true ); QVERIFY( w->isValid() ); - // Test unique + // Test getters + QCOMPARE( w->table(), QString( "someNewTableData" ) ); + QCOMPARE( w->schema(), QString( "qgis_test" ) ); + QCOMPARE( w->dataProviderKey(), QString( "postgres" ) ); + + // Test unique and make it invalid again so we get a status change w->mNewTableName->setText( QStringLiteral( "someData" ) ); + QVERIFY( ! w->isValid() ); QCOMPARE( tableSpy.count(), 1 ); arguments = tableSpy.takeLast(); QCOMPARE( arguments.at( 0 ).toString(), QString( "someData" ) ); + QCOMPARE( validationSpy.count(), 1 ); + arguments = validationSpy.takeLast(); + QCOMPARE( arguments.at( 0 ).toBool(), false ); - QVERIFY( ! w->isValid() ); + // Now select another schema + index = w->mBrowserModel->findPath( QStringLiteral( "pg:/PG_1/public" ), Qt::MatchFlag::MatchStartsWith ); + QVERIFY( index.isValid() ); + w->mBrowserTreeView->scrollTo( w->mBrowserProxyModel.mapFromSource( index ) ); + rect = w->mBrowserTreeView->visualRect( w->mBrowserProxyModel.mapFromSource( index ) ); + QVERIFY( rect.isValid() ); + QTest::mouseClick( w->mBrowserTreeView->viewport(), Qt::LeftButton, 0, rect.center() ); + QCOMPARE( w->schema(), QString( "public" ) ); + QVERIFY( w->isValid() ); + QCOMPARE( validationSpy.count(), 1 ); + arguments = validationSpy.takeLast(); + QCOMPARE( arguments.at( 0 ).toBool(), true ); + QCOMPARE( schemaSpy.count(), 1 ); + arguments = schemaSpy.takeLast(); + QCOMPARE( arguments.at( 0 ).toString(), QString( "public" ) ); // Test getters QCOMPARE( w->table(), QString( "someData" ) ); - QCOMPARE( w->schema(), QString( "qgis_test" ) ); - QCOMPARE( w->dataItemProviderName(), QString( "postgres" ) ); + QCOMPARE( w->schema(), QString( "public" ) ); + QCOMPARE( w->dataProviderKey(), QString( "postgres" ) ); } - -void TestQgsNewDatabaseTableNameWidget::testWidget() -{ - QDialog d; - QVBoxLayout layout; - d.setLayout( &layout ); - std::unique_ptr w { qgis::make_unique( nullptr, QStringList{ "PostGIS" } ) }; - d.layout()->addWidget( w.get() ); - - d.exec(); - -} - - QGSTEST_MAIN( TestQgsNewDatabaseTableNameWidget ) #include "testqgsnewdatabasetablewidget.moc"