From 55845ee702d0588a2439b2d4d9cd8f0fcd22d020 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 13 Mar 2020 17:12:40 +1000 Subject: [PATCH] Widget wrapper for database table parameter --- .../processing/qgsprocessingguiregistry.cpp | 1 + .../qgsprocessingwidgetwrapperimpl.cpp | 303 ++++++++++++++++++ .../qgsprocessingwidgetwrapperimpl.h | 73 +++++ src/gui/qgsdatabasetablecombobox.cpp | 45 ++- src/gui/qgsdatabasetablecombobox.h | 1 + tests/src/gui/testprocessinggui.cpp | 211 ++++++++++++ 6 files changed, 619 insertions(+), 15 deletions(-) diff --git a/src/gui/processing/qgsprocessingguiregistry.cpp b/src/gui/processing/qgsprocessingguiregistry.cpp index 440e3a3cfc16..523b744765e1 100644 --- a/src/gui/processing/qgsprocessingguiregistry.cpp +++ b/src/gui/processing/qgsprocessingguiregistry.cpp @@ -49,6 +49,7 @@ QgsProcessingGuiRegistry::QgsProcessingGuiRegistry() addParameterWidgetFactory( new QgsProcessingDateTimeWidgetWrapper() ); addParameterWidgetFactory( new QgsProcessingProviderConnectionWidgetWrapper() ); addParameterWidgetFactory( new QgsProcessingDatabaseSchemaWidgetWrapper() ); + addParameterWidgetFactory( new QgsProcessingDatabaseTableWidgetWrapper() ); } QgsProcessingGuiRegistry::~QgsProcessingGuiRegistry() diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp index 73efd5a677f7..96f43f0a4f9a 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp @@ -50,6 +50,7 @@ #include "qgsdatetimeedit.h" #include "qgsproviderconnectioncombobox.h" #include "qgsdatabaseschemacombobox.h" +#include "qgsdatabasetablecombobox.h" #include #include #include @@ -4284,6 +4285,308 @@ void QgsProcessingDatabaseSchemaWidgetWrapper::postInitialize( const QList( definition ); + + QVBoxLayout *vlayout = new QVBoxLayout(); + vlayout->setMargin( 0 ); + vlayout->setContentsMargins( 0, 0, 0, 0 ); + + mConnectionParamComboBox = new QComboBox(); + mSchemaParamComboBox = new QComboBox(); + QString initialConnection; + QString initialSchema; + if ( tableParam ) + { + initialConnection = tableParam->parentConnectionParameterName(); + initialSchema = tableParam->parentSchemaParameterName(); + } + + if ( widgetContext.model() ) + { + // populate combo box with other model input choices + const QMap components = widgetContext.model()->parameterComponents(); + for ( auto it = components.constBegin(); it != components.constEnd(); ++it ) + { + if ( definition && it->parameterName() == definition->name() ) + continue; + + if ( dynamic_cast< const QgsProcessingParameterProviderConnection * >( widgetContext.model()->parameterDefinition( it->parameterName() ) ) ) + { + mConnectionParamComboBox->addItem( it->parameterName(), it->parameterName() ); + if ( !initialConnection.isEmpty() && initialConnection == it->parameterName() ) + { + mConnectionParamComboBox->setCurrentIndex( mConnectionParamComboBox->count() - 1 ); + } + } + else if ( dynamic_cast< const QgsProcessingParameterDatabaseSchema * >( widgetContext.model()->parameterDefinition( it->parameterName() ) ) ) + { + mSchemaParamComboBox->addItem( it->parameterName(), it->parameterName() ); + if ( !initialConnection.isEmpty() && initialConnection == it->parameterName() ) + { + mSchemaParamComboBox->setCurrentIndex( mSchemaParamComboBox->count() - 1 ); + } + } + } + } + + if ( mConnectionParamComboBox->count() == 0 && !initialConnection.isEmpty() ) + { + // if no candidates found, we just add the existing one as a placeholder + mConnectionParamComboBox->addItem( initialConnection, initialConnection ); + mConnectionParamComboBox->setCurrentIndex( mConnectionParamComboBox->count() - 1 ); + } + + if ( mSchemaParamComboBox->count() == 0 && !initialSchema.isEmpty() ) + { + // if no candidates found, we just add the existing one as a placeholder + mSchemaParamComboBox->addItem( initialSchema, initialSchema ); + mSchemaParamComboBox->setCurrentIndex( mSchemaParamComboBox->count() - 1 ); + } + + vlayout->addWidget( new QLabel( tr( "Provider connection parameter" ) ) ); + vlayout->addWidget( mConnectionParamComboBox ); + + vlayout->addWidget( new QLabel( tr( "Database schema parameter" ) ) ); + vlayout->addWidget( mSchemaParamComboBox ); + + vlayout->addWidget( new QLabel( tr( "Default value" ) ) ); + + mDefaultEdit = new QLineEdit(); + vlayout->addWidget( mDefaultEdit ); + setLayout( vlayout ); + + if ( tableParam ) + { + mDefaultEdit->setText( tableParam->defaultValue().toString() ); + } +} + +QgsProcessingParameterDefinition *QgsProcessingDatabaseTableParameterDefinitionWidget::createParameter( const QString &name, const QString &description, QgsProcessingParameterDefinition::Flags flags ) const +{ + QVariant defaultVal; + if ( mDefaultEdit->text().isEmpty() ) + defaultVal = QVariant(); + else + defaultVal = mDefaultEdit->text(); + auto param = qgis::make_unique< QgsProcessingParameterDatabaseTable>( name, description, + mConnectionParamComboBox->currentData().toString(), + mSchemaParamComboBox->currentData().toString(), + defaultVal ); + param->setFlags( flags ); + return param.release(); +} + + +QgsProcessingDatabaseTableWidgetWrapper::QgsProcessingDatabaseTableWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent ) + : QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent ) +{ + +} + +QWidget *QgsProcessingDatabaseTableWidgetWrapper::createWidget() +{ + const QgsProcessingParameterDatabaseTable *tableParam = dynamic_cast< const QgsProcessingParameterDatabaseTable *>( parameterDefinition() ); + + mTableComboBox = new QgsDatabaseTableComboBox( QString(), QString() ); + if ( tableParam->flags() & QgsProcessingParameterDefinition::FlagOptional ) + mTableComboBox->setAllowEmptyTable( true ); + + switch ( type() ) + { + case QgsProcessingGui::Standard: + case QgsProcessingGui::Batch: + break; + case QgsProcessingGui::Modeler: + mTableComboBox->comboBox()->setEditable( true ); + break; + } + + mTableComboBox->setToolTip( parameterDefinition()->toolTip() ); + connect( mTableComboBox->comboBox(), &QComboBox::currentTextChanged, this, [ = ]( const QString & ) + { + if ( mBlockSignals ) + return; + + emit widgetValueHasChanged( this ); + } ); + + return mTableComboBox; +} + +QgsProcessingAbstractParameterDefinitionWidget *QgsProcessingDatabaseTableWidgetWrapper::createParameterDefinitionWidget( QgsProcessingContext &context, const QgsProcessingParameterWidgetContext &widgetContext, const QgsProcessingParameterDefinition *definition, const QgsProcessingAlgorithm *algorithm ) +{ + return new QgsProcessingDatabaseTableParameterDefinitionWidget( context, widgetContext, definition, algorithm ); +} + +void QgsProcessingDatabaseTableWidgetWrapper::setParentConnectionWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *parentWrapper ) +{ + // evaluate value to connection + QgsProcessingContext *context = nullptr; + std::unique_ptr< QgsProcessingContext > tmpContext; + if ( mProcessingContextGenerator ) + context = mProcessingContextGenerator->processingContext(); + + if ( !context ) + { + tmpContext = qgis::make_unique< QgsProcessingContext >(); + context = tmpContext.get(); + } + + QVariant value = parentWrapper->parameterValue(); + mConnection = QgsProcessingParameters::parameterAsConnectionName( parentWrapper->parameterDefinition(), value, *context ); + mProvider = dynamic_cast< const QgsProcessingParameterProviderConnection * >( parentWrapper->parameterDefinition() )->providerId(); + if ( mTableComboBox && !mSchema.isEmpty() && !mConnection.isEmpty() ) + { + mTableComboBox->setSchema( mSchema ); + mTableComboBox->setConnectionName( mConnection, mProvider ); + + const QgsProcessingParameterDatabaseTable *tableParam = static_cast< const QgsProcessingParameterDatabaseTable * >( parameterDefinition() ); + if ( tableParam->defaultValue().isValid() ) + setWidgetValue( parameterDefinition()->defaultValue(), *context ); + } +} + +void QgsProcessingDatabaseTableWidgetWrapper::setParentSchemaWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *parentWrapper ) +{ + // evaluate value to schema + QgsProcessingContext *context = nullptr; + std::unique_ptr< QgsProcessingContext > tmpContext; + if ( mProcessingContextGenerator ) + context = mProcessingContextGenerator->processingContext(); + + if ( !context ) + { + tmpContext = qgis::make_unique< QgsProcessingContext >(); + context = tmpContext.get(); + } + + QVariant value = parentWrapper->parameterValue(); + mSchema = QgsProcessingParameters::parameterAsSchema( parentWrapper->parameterDefinition(), value, *context ); + + if ( mTableComboBox && !mSchema.isEmpty() && !mConnection.isEmpty() ) + { + mTableComboBox->setSchema( mSchema ); + mTableComboBox->setConnectionName( mConnection, mProvider ); + + const QgsProcessingParameterDatabaseTable *tableParam = static_cast< const QgsProcessingParameterDatabaseTable * >( parameterDefinition() ); + if ( tableParam->defaultValue().isValid() ) + setWidgetValue( parameterDefinition()->defaultValue(), *context ); + } + +} + +void QgsProcessingDatabaseTableWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context ) +{ + const QString v = QgsProcessingParameters::parameterAsDatabaseTableName( parameterDefinition(), value, context ); + + if ( !value.isValid() ) + mTableComboBox->comboBox()->setCurrentIndex( -1 ); + else + { + if ( mTableComboBox->comboBox()->isEditable() ) + { + const QString prev = mTableComboBox->comboBox()->currentText(); + mBlockSignals++; + mTableComboBox->setTable( v ); + mTableComboBox->comboBox()->setCurrentText( v ); + + mBlockSignals--; + if ( prev != v ) + emit widgetValueHasChanged( this ); + } + else + mTableComboBox->setTable( v ); + } +} + +QVariant QgsProcessingDatabaseTableWidgetWrapper::widgetValue() const +{ + if ( mTableComboBox ) + if ( mTableComboBox->comboBox()->isEditable() ) + return mTableComboBox->comboBox()->currentText().isEmpty() ? QVariant() : QVariant( mTableComboBox->comboBox()->currentText() ); + else + return mTableComboBox->currentTable().isEmpty() ? QVariant() : QVariant( mTableComboBox->currentTable() ); + else + return QVariant(); +} + +QStringList QgsProcessingDatabaseTableWidgetWrapper::compatibleParameterTypes() const +{ + return QStringList() + << QgsProcessingParameterProviderConnection::typeName() + << QgsProcessingParameterString::typeName() + << QgsProcessingParameterExpression::typeName(); +} + +QStringList QgsProcessingDatabaseTableWidgetWrapper::compatibleOutputTypes() const +{ + return QStringList() + << QgsProcessingOutputString::typeName(); +} + +QList QgsProcessingDatabaseTableWidgetWrapper::compatibleDataTypes() const +{ + return QList< int >(); +} + +QString QgsProcessingDatabaseTableWidgetWrapper::modelerExpressionFormatString() const +{ + return tr( "database table name as a string value" ); +} + +QString QgsProcessingDatabaseTableWidgetWrapper::parameterType() const +{ + return QgsProcessingParameterDatabaseTable::typeName(); +} + +QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingDatabaseTableWidgetWrapper::createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) +{ + return new QgsProcessingDatabaseTableWidgetWrapper( parameter, type ); +} + +void QgsProcessingDatabaseTableWidgetWrapper::postInitialize( const QList &wrappers ) +{ + QgsAbstractProcessingParameterWidgetWrapper::postInitialize( wrappers ); + switch ( type() ) + { + case QgsProcessingGui::Standard: + case QgsProcessingGui::Batch: + { + for ( const QgsAbstractProcessingParameterWidgetWrapper *wrapper : wrappers ) + { + if ( wrapper->parameterDefinition()->name() == static_cast< const QgsProcessingParameterDatabaseTable * >( parameterDefinition() )->parentConnectionParameterName() ) + { + setParentConnectionWrapperValue( wrapper ); + connect( wrapper, &QgsAbstractProcessingParameterWidgetWrapper::widgetValueHasChanged, this, [ = ] + { + setParentConnectionWrapperValue( wrapper ); + } ); + } + else if ( wrapper->parameterDefinition()->name() == static_cast< const QgsProcessingParameterDatabaseTable * >( parameterDefinition() )->parentSchemaParameterName() ) + { + setParentSchemaWrapperValue( wrapper ); + connect( wrapper, &QgsAbstractProcessingParameterWidgetWrapper::widgetValueHasChanged, this, [ = ] + { + setParentSchemaWrapperValue( wrapper ); + } ); + } + } + break; + } + + case QgsProcessingGui::Modeler: + break; + } +} + + ///@endcond PRIVATE diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h index 9d797e4857e6..eec7e8586e48 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h @@ -56,6 +56,7 @@ class QgsDateEdit; class QgsTimeEdit; class QgsProviderConnectionComboBox; class QgsDatabaseSchemaComboBox; +class QgsDatabaseTableComboBox; ///@cond PRIVATE @@ -1285,6 +1286,78 @@ class GUI_EXPORT QgsProcessingDatabaseSchemaWidgetWrapper : public QgsAbstractPr friend class TestProcessingGui; }; + + + + +class GUI_EXPORT QgsProcessingDatabaseTableParameterDefinitionWidget : public QgsProcessingAbstractParameterDefinitionWidget +{ + Q_OBJECT + public: + + QgsProcessingDatabaseTableParameterDefinitionWidget( QgsProcessingContext &context, + const QgsProcessingParameterWidgetContext &widgetContext, + const QgsProcessingParameterDefinition *definition = nullptr, + const QgsProcessingAlgorithm *algorithm = nullptr, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + QgsProcessingParameterDefinition *createParameter( const QString &name, const QString &description, QgsProcessingParameterDefinition::Flags flags ) const override; + + private: + + QComboBox *mConnectionParamComboBox = nullptr; + QComboBox *mSchemaParamComboBox = nullptr; + QLineEdit *mDefaultEdit = nullptr; + +}; + +class GUI_EXPORT QgsProcessingDatabaseTableWidgetWrapper : public QgsAbstractProcessingParameterWidgetWrapper, public QgsProcessingParameterWidgetFactoryInterface +{ + Q_OBJECT + + public: + + QgsProcessingDatabaseTableWidgetWrapper( const QgsProcessingParameterDefinition *parameter = nullptr, + QgsProcessingGui::WidgetType type = QgsProcessingGui::Standard, QWidget *parent = nullptr ); + + // QgsProcessingParameterWidgetFactoryInterface + QString parameterType() const override; + QgsAbstractProcessingParameterWidgetWrapper *createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) override; + void postInitialize( const QList< QgsAbstractProcessingParameterWidgetWrapper * > &wrappers ) override; + + + // QgsProcessingParameterWidgetWrapper interface + QWidget *createWidget() override SIP_FACTORY; + QgsProcessingAbstractParameterDefinitionWidget *createParameterDefinitionWidget( + QgsProcessingContext &context, + const QgsProcessingParameterWidgetContext &widgetContext, + const QgsProcessingParameterDefinition *definition = nullptr, + const QgsProcessingAlgorithm *algorithm = nullptr ) override; + + public slots: + void setParentConnectionWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *parentWrapper ); + void setParentSchemaWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *parentWrapper ); + + protected: + + void setWidgetValue( const QVariant &value, QgsProcessingContext &context ) override; + QVariant widgetValue() const override; + + QStringList compatibleParameterTypes() const override; + + QStringList compatibleOutputTypes() const override; + + QList< int > compatibleDataTypes() const override; + QString modelerExpressionFormatString() const override; + + private: + + QgsDatabaseTableComboBox *mTableComboBox = nullptr; + int mBlockSignals = 0; + QString mConnection; + QString mProvider; + QString mSchema; + + friend class TestProcessingGui; +}; ///@endcond PRIVATE #endif // QGSPROCESSINGWIDGETWRAPPERIMPL_H diff --git a/src/gui/qgsdatabasetablecombobox.cpp b/src/gui/qgsdatabasetablecombobox.cpp index 27e0649c7ef1..bce03ce75ebf 100644 --- a/src/gui/qgsdatabasetablecombobox.cpp +++ b/src/gui/qgsdatabasetablecombobox.cpp @@ -26,7 +26,8 @@ QgsDatabaseTableComboBox::QgsDatabaseTableComboBox( const QString &provider, con , mConnection( connection ) , mSchema( schema ) { - mModel = new QgsDatabaseTableModel( provider, connection, schema, this ); + if ( !provider.isEmpty() && !connection.isEmpty() ) + mModel = new QgsDatabaseTableModel( provider, connection, schema, this ); init(); } @@ -40,12 +41,14 @@ QgsDatabaseTableComboBox::QgsDatabaseTableComboBox( QgsAbstractDatabaseProviderC void QgsDatabaseTableComboBox::setAllowEmptyTable( bool allowEmpty ) { - mModel->setAllowEmptyTable( allowEmpty ); + mAllowEmpty = allowEmpty; + if ( mModel ) + mModel->setAllowEmptyTable( allowEmpty ); } bool QgsDatabaseTableComboBox::allowEmptyTable() const { - return mModel->allowEmptyTable(); + return mAllowEmpty; } void QgsDatabaseTableComboBox::init() @@ -53,7 +56,8 @@ void QgsDatabaseTableComboBox::init() mComboBox = new QComboBox(); mSortModel = new QgsDatabaseTableComboBoxSortModel( this ); - mSortModel->setSourceModel( mModel ); + if ( mModel ) + mSortModel->setSourceModel( mModel ); mSortModel->setSortRole( Qt::DisplayRole ); mSortModel->setSortLocaleAware( true ); mSortModel->setSortCaseSensitivity( Qt::CaseInsensitive ); @@ -87,7 +91,7 @@ void QgsDatabaseTableComboBox::setTable( const QString &table, const QString &sc if ( table.isEmpty() ) { - if ( mModel->allowEmptyTable() ) + if ( mAllowEmpty ) mComboBox->setCurrentIndex( 0 ); else mComboBox->setCurrentIndex( -1 ); @@ -113,6 +117,9 @@ void QgsDatabaseTableComboBox::setTable( const QString &table, const QString &sc void QgsDatabaseTableComboBox::setConnectionName( const QString &connection, const QString &provider ) { + if ( provider.isEmpty() && mConnection == connection ) + return; + if ( !provider.isEmpty() ) mProvider = provider; @@ -122,29 +129,37 @@ void QgsDatabaseTableComboBox::setConnectionName( const QString &connection, con const QString oldSchema = currentSchema(); QgsDatabaseTableModel *oldModel = mModel; mModel = new QgsDatabaseTableModel( mProvider, mConnection, mSchema, this ); - mModel->setAllowEmptyTable( oldModel->allowEmptyTable() ); + mModel->setAllowEmptyTable( mAllowEmpty ); mSortModel->setSourceModel( mModel ); - oldModel->deleteLater(); + if ( oldModel ) + oldModel->deleteLater(); if ( currentTable() != oldTable || currentSchema() != oldSchema ) setTable( oldTable, oldSchema ); } void QgsDatabaseTableComboBox::setSchema( const QString &schema ) { - const QString oldTable = currentTable(); - QgsDatabaseTableModel *oldModel = mModel; + if ( schema == mSchema ) + return; mSchema = schema; - mModel = new QgsDatabaseTableModel( mProvider, mConnection, mSchema, this ); - mSortModel->setSourceModel( mModel ); - oldModel->deleteLater(); - setTable( oldTable ); + + if ( !mProvider.isEmpty() && !mConnection.isEmpty() ) + { + const QString oldTable = currentTable(); + QgsDatabaseTableModel *oldModel = mModel; + mModel = new QgsDatabaseTableModel( mProvider, mConnection, mSchema, this ); + mSortModel->setSourceModel( mModel ); + oldModel->deleteLater(); + setTable( oldTable ); + } } void QgsDatabaseTableComboBox::refreshTables() { const QString oldSchema = currentSchema(); const QString oldTable = currentTable(); - mModel->refresh(); + if ( mModel ) + mModel->refresh(); setTable( oldTable, oldSchema ); } @@ -178,7 +193,7 @@ void QgsDatabaseTableComboBox::indexChanged( int i ) void QgsDatabaseTableComboBox::rowsChanged() { - if ( mComboBox->count() == 1 || ( mModel->allowEmptyTable() && mComboBox->count() == 2 && mComboBox->currentIndex() == 1 ) ) + if ( mComboBox->count() == 1 || ( mAllowEmpty && mComboBox->count() == 2 && mComboBox->currentIndex() == 1 ) ) { //currently selected connection item has changed emit tableChanged( currentTable(), currentSchema() ); diff --git a/src/gui/qgsdatabasetablecombobox.h b/src/gui/qgsdatabasetablecombobox.h index a7eb0121f1da..953999952f48 100644 --- a/src/gui/qgsdatabasetablecombobox.h +++ b/src/gui/qgsdatabasetablecombobox.h @@ -136,6 +136,7 @@ class GUI_EXPORT QgsDatabaseTableComboBox : public QWidget private: void init(); + bool mAllowEmpty = false; QString mProvider; QString mConnection; QString mSchema; diff --git a/tests/src/gui/testprocessinggui.cpp b/tests/src/gui/testprocessinggui.cpp index d9837b03a08f..a98342e2b538 100644 --- a/tests/src/gui/testprocessinggui.cpp +++ b/tests/src/gui/testprocessinggui.cpp @@ -73,6 +73,7 @@ #include "qgsprovidermetadata.h" #include "qgsproviderconnectioncombobox.h" #include "qgsdatabaseschemacombobox.h" +#include "qgsdatabasetablecombobox.h" class TestParamType : public QgsProcessingParameterDefinition { @@ -207,6 +208,7 @@ class TestProcessingGui : public QObject void testDateTimeWrapper(); void testProviderConnectionWrapper(); void testDatabaseSchemaWrapper(); + void testDatabaseTableWrapper(); private: @@ -4944,6 +4946,215 @@ void TestProcessingGui::testDatabaseSchemaWrapper() #endif } +void TestProcessingGui::testDatabaseTableWrapper() +{ +#ifdef ENABLE_PGTEST + // register some connections + QgsProviderMetadata *md = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "postgres" ) ); + + QString dbConn = getenv( "QGIS_PGTEST_DB" ); + if ( dbConn.isEmpty() ) + { + dbConn = "service=\"qgis_test\""; + } + QgsAbstractProviderConnection *conn = md->createConnection( QStringLiteral( "%1 sslmode=disable" ).arg( dbConn ), QVariantMap() ); + md->saveConnection( conn, QStringLiteral( "aa" ) ); + + const QList tables = dynamic_cast( conn )->tables( QStringLiteral( "qgis_test" ) ); + QStringList tableNames; + for ( const QgsAbstractDatabaseProviderConnection::TableProperty &prop : tables ) + tableNames << prop.tableName(); + + QVERIFY( !tableNames.isEmpty() ); + + auto testWrapper = [&tableNames]( QgsProcessingGui::WidgetType type ) + { + QgsProcessingParameterProviderConnection connParam( QStringLiteral( "conn" ), QStringLiteral( "connection" ), QStringLiteral( "postgres" ), true ); + TestLayerWrapper connWrapper( &connParam ); + QgsProcessingParameterDatabaseSchema schemaParam( QStringLiteral( "schema" ), QStringLiteral( "schema" ), QStringLiteral( "connection" ), true ); + TestLayerWrapper schemaWrapper( &schemaParam ); + + QgsProcessingParameterDatabaseTable param( QStringLiteral( "table" ), QStringLiteral( "table" ), QStringLiteral( "conn" ), QStringLiteral( "schema" ), QVariant(), false ); + + QgsProcessingDatabaseTableWidgetWrapper wrapper( ¶m, type ); + + QgsProcessingContext context; + QWidget *w = wrapper.createWrappedWidget( context ); + // no connection associated yet + QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->count(), 0 ); + + // Set the parent widget connection value + connWrapper.setWidgetValue( QStringLiteral( "aa" ), context ); + wrapper.setParentConnectionWrapperValue( &connWrapper ); + schemaWrapper.setWidgetValue( QStringLiteral( "qgis_test" ), context ); + wrapper.setParentSchemaWrapperValue( &schemaWrapper ); + + // now we should have tables available + QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->count(), tableNames.count() ); + + QSignalSpy spy( &wrapper, &QgsProcessingDatabaseTableWidgetWrapper::widgetValueHasChanged ); + wrapper.setWidgetValue( QStringLiteral( "someData" ), context ); + QCOMPARE( spy.count(), 1 ); + QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "someData" ) ); + QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->currentTable(), QStringLiteral( "someData" ) ); + wrapper.setWidgetValue( QStringLiteral( "some_poly_data" ), context ); + QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "some_poly_data" ) ); + QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->currentTable(), QStringLiteral( "some_poly_data" ) ); + QCOMPARE( spy.count(), 2 ); + wrapper.setWidgetValue( QStringLiteral( "some_poly_data" ), context ); + QCOMPARE( spy.count(), 2 ); + + switch ( type ) + { + case QgsProcessingGui::Standard: + case QgsProcessingGui::Batch: + { + // batch or standard mode, only valid tables can be set! + // not valid + wrapper.setWidgetValue( QStringLiteral( "cc" ), context ); + QCOMPARE( spy.count(), 3 ); + QVERIFY( !wrapper.widgetValue().isValid() ); + QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentIndex(), -1 ); + break; + + } + case QgsProcessingGui::Modeler: + // invalid tables permitted + wrapper.setWidgetValue( QStringLiteral( "cc" ), context ); + QCOMPARE( spy.count(), 3 ); + QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "cc" ) ); + QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "cc" ) ); + wrapper.setWidgetValue( QStringLiteral( "someData" ), context ); + QCOMPARE( spy.count(), 4 ); + QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "someData" ) ); + QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "someData" ) ); + break; + } + + // make sure things are ok if connection is changed back to nothing + connWrapper.setWidgetValue( QVariant(), context ); + wrapper.setParentConnectionWrapperValue( &connWrapper ); + + switch ( type ) + { + case QgsProcessingGui::Standard: + case QgsProcessingGui::Batch: + { + QCOMPARE( spy.count(), 3 ); + break; + } + + case QgsProcessingGui::Modeler: + QCOMPARE( spy.count(), 5 ); + break; + } + QVERIFY( !wrapper.widgetValue().isValid() ); + + wrapper.setWidgetValue( QStringLiteral( "some_poly_data" ), context ); + switch ( type ) + { + case QgsProcessingGui::Standard: + case QgsProcessingGui::Batch: + { + QVERIFY( !wrapper.widgetValue().isValid() ); + break; + } + + case QgsProcessingGui::Modeler: + // invalid tables permitted + QCOMPARE( spy.count(), 6 ); + QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "some_poly_data" ) ); + QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "some_poly_data" ) ); + + break; + } + delete w; + + + connWrapper.setWidgetValue( QStringLiteral( "aa" ), context ); + + // optional + QgsProcessingParameterDatabaseTable param2( QStringLiteral( "table" ), QStringLiteral( "table" ), QStringLiteral( "conn" ), QStringLiteral( "schema" ), QVariant(), true ); + QgsProcessingDatabaseTableWidgetWrapper wrapper3( ¶m2, type ); + w = wrapper3.createWrappedWidget( context ); + + wrapper3.setParentConnectionWrapperValue( &connWrapper ); + wrapper3.setParentSchemaWrapperValue( &schemaWrapper ); + + QSignalSpy spy3( &wrapper3, &QgsProcessingDatabaseTableWidgetWrapper::widgetValueHasChanged ); + wrapper3.setWidgetValue( QStringLiteral( "someData" ), context ); + QCOMPARE( spy3.count(), 1 ); + QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "someData" ) ); + QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper3.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "someData" ) ); + wrapper3.setWidgetValue( QStringLiteral( "some_poly_data" ), context ); + QCOMPARE( spy3.count(), 2 ); + QCOMPARE( wrapper3.widgetValue().toString(), QStringLiteral( "some_poly_data" ) ); + QCOMPARE( static_cast< QgsDatabaseTableComboBox * >( wrapper3.wrappedWidget() )->comboBox()->currentText(), QStringLiteral( "some_poly_data" ) ); + wrapper3.setWidgetValue( QVariant(), context ); + QCOMPARE( spy3.count(), 3 ); + QVERIFY( !wrapper3.widgetValue().isValid() ); + + delete w; + QLabel *l = wrapper.createWrappedLabel(); + if ( wrapper.type() != QgsProcessingGui::Batch ) + { + QVERIFY( l ); + QCOMPARE( l->text(), QStringLiteral( "table" ) ); + QCOMPARE( l->toolTip(), param.toolTip() ); + delete l; + } + else + { + QVERIFY( !l ); + } + }; + + // standard wrapper + testWrapper( QgsProcessingGui::Standard ); + + // batch wrapper + testWrapper( QgsProcessingGui::Batch ); + + // modeler wrapper + testWrapper( QgsProcessingGui::Modeler ); + + // config widget + QgsProcessingParameterWidgetContext widgetContext; + QgsProcessingContext context; + std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = qgis::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databasetable" ), context, widgetContext ); + std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) ); + QCOMPARE( def->name(), QStringLiteral( "param_name" ) ); + QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory + QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) ); + QVERIFY( !static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->defaultValue().isValid() ); + + // using a parameter definition as initial values + QgsProcessingParameterDatabaseTable tableParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "connparam" ), QStringLiteral( "schemaparam" ), QStringLiteral( "aaa" ), false ); + widget = qgis::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databasetable" ), context, widgetContext, &tableParam ); + def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) ); + QCOMPARE( def->name(), QStringLiteral( "param_name" ) ); + QCOMPARE( def->description(), QStringLiteral( "test desc" ) ); + QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); + QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) ); + QCOMPARE( static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->defaultValue().toString(), QStringLiteral( "aaa" ) ); + QCOMPARE( static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->parentConnectionParameterName(), QStringLiteral( "connparam" ) ); + QCOMPARE( static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->parentSchemaParameterName(), QStringLiteral( "schemaparam" ) ); + tableParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional ); + tableParam.setDefaultValue( QStringLiteral( "xxx" ) ); + widget = qgis::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databasetable" ), context, widgetContext, &tableParam ); + def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) ); + QCOMPARE( def->name(), QStringLiteral( "param_name" ) ); + QCOMPARE( def->description(), QStringLiteral( "test desc" ) ); + QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional ); + QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ); + QCOMPARE( static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->defaultValue().toString(), QStringLiteral( "xxx" ) ); + tableParam.setDefaultValue( QVariant() ); + widget = qgis::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "databasetable" ), context, widgetContext, &tableParam ); + def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) ); + QVERIFY( !static_cast< QgsProcessingParameterDatabaseTable * >( def.get() )->defaultValue().isValid() ); +#endif +} + void TestProcessingGui::cleanupTempDir() { QDir tmpDir = QDir( mTempDir );