/*************************************************************************** testprocesinggui.cpp --------------------------- begin : April 2018 copyright : (C) 2018 by Matthias Kuhn email : matthias@opengis.ch ***************************************************************************/ /*************************************************************************** * * * 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 #include #include #include #include #include #include #include #include "qgstest.h" #include "qgsgui.h" #include "qgsprocessingguiregistry.h" #include "qgsprocessingregistry.h" #include "qgsprocessingalgorithm.h" #include "qgsprocessingalgorithmconfigurationwidget.h" #include "qgsprocessingwidgetwrapper.h" #include "qgsprocessingwidgetwrapperimpl.h" #include "qgsprocessingmodelerparameterwidget.h" #include "qgsnativealgorithms.h" #include "processing/models/qgsprocessingmodelalgorithm.h" #include "qgsxmlutils.h" #include "qgspropertyoverridebutton.h" class TestParamType : public QgsProcessingParameterDefinition { public: TestParamType( const QString &type, const QString &name, const QVariant &defaultValue = QVariant() ) : QgsProcessingParameterDefinition( name, name, defaultValue ) , mType( type ) {} QString mType; QgsProcessingParameterDefinition *clone() const override { return new TestParamType( mType, name() ); } QString type() const override { return mType; } QString valueAsPythonString( const QVariant &, QgsProcessingContext & ) const override { return QString(); } QString asScriptCode() const override { return QString(); } }; class TestWidgetWrapper : public QgsAbstractProcessingParameterWidgetWrapper { public: TestWidgetWrapper( const QgsProcessingParameterDefinition *parameter = nullptr, QgsProcessingGui::WidgetType type = QgsProcessingGui::Standard ) : QgsAbstractProcessingParameterWidgetWrapper( parameter, type ) {} QWidget *createWidget() override { return nullptr; } QLabel *createLabel() override { return nullptr; } void setWidgetValue( const QVariant &, QgsProcessingContext & ) override { } QVariant widgetValue() const override { return QVariant(); } }; class TestWidgetFactory : public QgsProcessingParameterWidgetFactoryInterface { public: TestWidgetFactory( const QString &type ) : type( type ) {} QString type; QString parameterType() const override { return type; } QgsAbstractProcessingParameterWidgetWrapper *createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) override { return new TestWidgetWrapper( parameter, type ); } QStringList compatibleParameterTypes() const override { return QStringList(); } QStringList compatibleOutputTypes() const override { return QStringList(); } QList< int > compatibleDataTypes() const override { return QList(); } }; class TestProcessingGui : public QObject { Q_OBJECT public: TestProcessingGui() = 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 testSetGetConfig(); void testFilterAlgorithmConfig(); void testWrapperFactoryRegistry(); void testWrapperGeneral(); void testWrapperDynamic(); void testModelerWrapper(); void testBooleanWrapper(); void testStringWrapper(); }; void TestProcessingGui::initTestCase() { QgsApplication::init(); QgsApplication::initQgis(); QgsApplication::processingRegistry()->addProvider( new QgsNativeAlgorithms( QgsApplication::processingRegistry() ) ); } void TestProcessingGui::cleanupTestCase() { QgsApplication::exitQgis(); } void TestProcessingGui::init() { } void TestProcessingGui::cleanup() { } void TestProcessingGui::testSetGetConfig() { const QList< const QgsProcessingAlgorithm * > algorithms = QgsApplication::instance()->processingRegistry()->algorithms(); // Find all defined widgets for native algorithms // and get the default configuration (that is, we create a widget // and get the configuration it returns without being modified in any way) // We then set and get this configuration and validate that it matches the original one. for ( const QgsProcessingAlgorithm *algorithm : algorithms ) { std::unique_ptr configWidget( QgsGui::instance()->processingGuiRegistry()->algorithmConfigurationWidget( algorithm ) ); if ( configWidget ) { const QVariantMap defaultConfig = configWidget->configuration(); configWidget->setConfiguration( defaultConfig ); const QVariantMap defaultControlConfig = configWidget->configuration(); QDomDocument defaultConfigDoc; QDomDocument defaultConfigControlDoc; QgsXmlUtils::writeVariant( defaultConfig, defaultConfigDoc ); QgsXmlUtils::writeVariant( defaultControlConfig, defaultConfigControlDoc ); QCOMPARE( defaultConfigDoc.toString(), defaultConfigControlDoc.toString() ); } } } void TestProcessingGui::testFilterAlgorithmConfig() { const QgsProcessingAlgorithm *algorithm = QgsApplication::instance()->processingRegistry()->algorithmById( QStringLiteral( "native:filter" ) ); std::unique_ptr configWidget( QgsGui::instance()->processingGuiRegistry()->algorithmConfigurationWidget( algorithm ) ); QVariantMap config; QVariantList outputs; QVariantMap output; output.insert( QStringLiteral( "name" ), QStringLiteral( "test" ) ); output.insert( QStringLiteral( "expression" ), QStringLiteral( "I am an expression" ) ); output.insert( QStringLiteral( "isModelOutput" ), true ); outputs.append( output ); config.insert( QStringLiteral( "outputs" ), outputs ); configWidget->setConfiguration( config ); QVariantMap configControl = configWidget->configuration(); QDomDocument configDoc; QDomDocument configControlDoc; QgsXmlUtils::writeVariant( config, configDoc ); QgsXmlUtils::writeVariant( configControl, configControlDoc ); QCOMPARE( configDoc.toString(), configControlDoc.toString() ); } void TestProcessingGui::testWrapperFactoryRegistry() { QgsProcessingGuiRegistry registry; TestParamType numParam( QStringLiteral( "num" ), QStringLiteral( "num" ) ); TestParamType stringParam( QStringLiteral( "str" ), QStringLiteral( "str" ) ); QVERIFY( !registry.createParameterWidgetWrapper( &numParam, QgsProcessingGui::Standard ) ); TestWidgetFactory *factory = new TestWidgetFactory( QStringLiteral( "str" ) ); QVERIFY( registry.addParameterWidgetFactory( factory ) ); // duplicate type not allowed TestWidgetFactory *factory2 = new TestWidgetFactory( QStringLiteral( "str" ) ); QVERIFY( !registry.addParameterWidgetFactory( factory2 ) ); delete factory2; QgsAbstractProcessingParameterWidgetWrapper *wrapper = registry.createParameterWidgetWrapper( &numParam, QgsProcessingGui::Standard ); QVERIFY( !wrapper ); wrapper = registry.createParameterWidgetWrapper( &stringParam, QgsProcessingGui::Standard ); QVERIFY( wrapper ); QCOMPARE( wrapper->parameterDefinition()->type(), QStringLiteral( "str" ) ); delete wrapper; TestWidgetFactory *factory3 = new TestWidgetFactory( QStringLiteral( "num" ) ); QVERIFY( registry.addParameterWidgetFactory( factory3 ) ); wrapper = registry.createParameterWidgetWrapper( &numParam, QgsProcessingGui::Standard ); QVERIFY( wrapper ); QCOMPARE( wrapper->parameterDefinition()->type(), QStringLiteral( "num" ) ); delete wrapper; // removing registry.removeParameterWidgetFactory( nullptr ); TestWidgetFactory *factory4 = new TestWidgetFactory( QStringLiteral( "xxxx" ) ); registry.removeParameterWidgetFactory( factory4 ); registry.removeParameterWidgetFactory( factory ); wrapper = registry.createParameterWidgetWrapper( &stringParam, QgsProcessingGui::Standard ); QVERIFY( !wrapper ); wrapper = registry.createParameterWidgetWrapper( &numParam, QgsProcessingGui::Standard ); QVERIFY( wrapper ); QCOMPARE( wrapper->parameterDefinition()->type(), QStringLiteral( "num" ) ); delete wrapper; } void TestProcessingGui::testWrapperGeneral() { TestParamType param( QStringLiteral( "boolean" ), QStringLiteral( "bool" ) ); QgsProcessingBooleanWidgetWrapper wrapper( ¶m ); QCOMPARE( wrapper.type(), QgsProcessingGui::Standard ); QgsProcessingBooleanWidgetWrapper wrapper2( ¶m, QgsProcessingGui::Batch ); QCOMPARE( wrapper2.type(), QgsProcessingGui::Batch ); QCOMPARE( wrapper2.parameterDefinition()->name(), QStringLiteral( "bool" ) ); QgsProcessingBooleanWidgetWrapper wrapperModeler( ¶m, QgsProcessingGui::Modeler ); QCOMPARE( wrapperModeler.type(), QgsProcessingGui::Modeler ); QgsProcessingContext context; QVERIFY( !wrapper2.wrappedWidget() ); QWidget *w = wrapper2.createWrappedWidget( context ); QVERIFY( w ); QCOMPARE( wrapper2.wrappedWidget(), w ); delete w; QVERIFY( !wrapper2.wrappedLabel() ); QLabel *l = wrapper2.createWrappedLabel(); QCOMPARE( wrapper2.wrappedLabel(), l ); delete l; l = wrapperModeler.createWrappedLabel(); QVERIFY( l ); QCOMPARE( wrapperModeler.wrappedLabel(), l ); delete l; // check that created widget starts with default value param = TestParamType( QStringLiteral( "boolean" ), QStringLiteral( "bool" ), true ); QgsProcessingBooleanWidgetWrapper trueDefault( ¶m ); w = trueDefault.createWrappedWidget( context ); QVERIFY( trueDefault.widgetValue().toBool() ); delete w; param = TestParamType( QStringLiteral( "boolean" ), QStringLiteral( "bool" ), false ); QgsProcessingBooleanWidgetWrapper falseDefault( ¶m ); w = falseDefault.createWrappedWidget( context ); QVERIFY( !falseDefault.widgetValue().toBool() ); delete w; } class TestProcessingContextGenerator : public QgsProcessingContextGenerator { public: TestProcessingContextGenerator( QgsProcessingContext &context ) : mContext( context ) {} QgsProcessingContext *processingContext() override { return &mContext; } QgsProcessingContext &mContext; }; void TestProcessingGui::testWrapperDynamic() { const QgsProcessingAlgorithm *centroidAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:centroids" ) ); const QgsProcessingParameterDefinition *layerDef = centroidAlg->parameterDefinition( QStringLiteral( "INPUT" ) ); const QgsProcessingParameterDefinition *allPartsDef = centroidAlg->parameterDefinition( QStringLiteral( "ALL_PARTS" ) ); QgsProcessingBooleanWidgetWrapper inputWrapper( layerDef, QgsProcessingGui::Standard ); QgsProcessingBooleanWidgetWrapper allPartsWrapper( allPartsDef, QgsProcessingGui::Standard ); QgsProcessingContext context; std::unique_ptr< QWidget > allPartsWidget( allPartsWrapper.createWrappedWidget( context ) ); // dynamic parameter, so property button should be created QVERIFY( allPartsWrapper.mPropertyButton.data() != nullptr ); std::unique_ptr< QWidget > inputWidget( inputWrapper.createWrappedWidget( context ) ); // not dynamic parameter, so property button should be NOT created QVERIFY( inputWrapper.mPropertyButton.data() == nullptr ); // set dynamic parameter to dynamic value allPartsWrapper.setParameterValue( QgsProperty::fromExpression( QStringLiteral( "1+2" ) ), context ); QCOMPARE( allPartsWrapper.parameterValue().value< QgsProperty >().expressionString(), QStringLiteral( "1+2" ) ); // not dynamic value allPartsWrapper.setParameterValue( true, context ); QCOMPARE( allPartsWrapper.parameterValue().toBool(), true ); allPartsWrapper.setParameterValue( false, context ); QCOMPARE( allPartsWrapper.parameterValue().toBool(), false ); // project layer QgsProject p; QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) ); p.addMapLayer( vl ); QVERIFY( !allPartsWrapper.mPropertyButton->vectorLayer() ); allPartsWrapper.setDynamicParentLayerParameter( QVariant::fromValue( vl ) ); QCOMPARE( allPartsWrapper.mPropertyButton->vectorLayer(), vl ); // should not be owned by wrapper QVERIFY( !allPartsWrapper.mDynamicLayer.get() ); allPartsWrapper.setDynamicParentLayerParameter( QVariant() ); QVERIFY( !allPartsWrapper.mPropertyButton->vectorLayer() ); allPartsWrapper.setDynamicParentLayerParameter( vl->id() ); QVERIFY( !allPartsWrapper.mPropertyButton->vectorLayer() ); QVERIFY( !allPartsWrapper.mDynamicLayer.get() ); // with project layer context.setProject( &p ); TestProcessingContextGenerator generator( context ); allPartsWrapper.registerProcessingContextGenerator( &generator ); allPartsWrapper.setDynamicParentLayerParameter( vl->id() ); QCOMPARE( allPartsWrapper.mPropertyButton->vectorLayer(), vl ); QVERIFY( !allPartsWrapper.mDynamicLayer.get() ); // non-project layer QString pointFileName = TEST_DATA_DIR + QStringLiteral( "/points.shp" ); allPartsWrapper.setDynamicParentLayerParameter( pointFileName ); QCOMPARE( allPartsWrapper.mPropertyButton->vectorLayer()->publicSource(), pointFileName ); // must be owned by wrapper, or layer may be deleted while still required by wrapper QCOMPARE( allPartsWrapper.mDynamicLayer->publicSource(), pointFileName ); } void TestProcessingGui::testModelerWrapper() { // make a little model QgsProcessingModelAlgorithm model( QStringLiteral( "test" ), QStringLiteral( "testGroup" ) ); QMap algs; QgsProcessingModelChildAlgorithm a1( "native:buffer" ); a1.setDescription( QStringLiteral( "alg1" ) ); a1.setChildId( QStringLiteral( "alg1" ) ); QgsProcessingModelChildAlgorithm a2; a2.setDescription( QStringLiteral( "alg2" ) ); a2.setChildId( QStringLiteral( "alg2" ) ); QgsProcessingModelChildAlgorithm a3( QStringLiteral( "native:buffer" ) ); a3.setDescription( QStringLiteral( "alg3" ) ); a3.setChildId( QStringLiteral( "alg3" ) ); algs.insert( QStringLiteral( "alg1" ), a1 ); algs.insert( QStringLiteral( "alg2" ), a2 ); algs.insert( QStringLiteral( "alg3" ), a3 ); model.setChildAlgorithms( algs ); QMap pComponents; QgsProcessingModelParameter pc1; pc1.setParameterName( QStringLiteral( "my_param" ) ); pComponents.insert( QStringLiteral( "my_param" ), pc1 ); model.setParameterComponents( pComponents ); QgsProcessingModelParameter bool1( "p1" ); model.addModelParameter( new QgsProcessingParameterBoolean( "p1", "desc" ), bool1 ); QgsProcessingModelParameter testParam( "p2" ); model.addModelParameter( new TestParamType( "test_type", "p2" ), testParam ); // try to create a parameter widget, no factories registered QgsProcessingGuiRegistry registry; QgsProcessingContext context; QVERIFY( !registry.createModelerParameterWidget( &model, QStringLiteral( "a" ), model.parameterDefinition( "p2" ), context ) ); // register factory TestWidgetFactory *factory = new TestWidgetFactory( QStringLiteral( "test_type" ) ); QVERIFY( registry.addParameterWidgetFactory( factory ) ); QgsProcessingModelerParameterWidget *w = registry.createModelerParameterWidget( &model, QStringLiteral( "a" ), model.parameterDefinition( "p2" ), context ); QVERIFY( w ); delete w; // widget tests w = new QgsProcessingModelerParameterWidget( &model, "alg1", model.parameterDefinition( "p1" ), context ); QCOMPARE( w->parameterDefinition()->name(), QStringLiteral( "p1" ) ); QLabel *l = w->createLabel(); QVERIFY( l ); QCOMPARE( l->text(), QStringLiteral( "desc" ) ); QCOMPARE( l->toolTip(), w->parameterDefinition()->toolTip() ); delete l; // static value w->setWidgetValue( QgsProcessingModelChildParameterSource::fromStaticValue( true ) ); QCOMPARE( w->value().source(), QgsProcessingModelChildParameterSource::StaticValue ); QCOMPARE( w->value().staticValue().toBool(), true ); w->setWidgetValue( QgsProcessingModelChildParameterSource::fromStaticValue( false ) ); QCOMPARE( w->value().source(), QgsProcessingModelChildParameterSource::StaticValue ); QCOMPARE( w->value().staticValue().toBool(), false ); QCOMPARE( w->mStackedWidget->currentIndex(), 0 ); QCOMPARE( w->mSourceButton->toolTip(), QStringLiteral( "Value" ) ); // expression value w->setWidgetValue( QgsProcessingModelChildParameterSource::fromExpression( QStringLiteral( "1+2" ) ) ); QCOMPARE( w->value().source(), QgsProcessingModelChildParameterSource::Expression ); QCOMPARE( w->value().expression(), QStringLiteral( "1+2" ) ); QCOMPARE( w->mStackedWidget->currentIndex(), 1 ); QCOMPARE( w->mSourceButton->toolTip(), QStringLiteral( "Pre-calculated Value" ) ); // model input - should fail, because we haven't populated sources yet, and so have no compatible sources w->setWidgetValue( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "p1" ) ) ); QCOMPARE( w->value().source(), QgsProcessingModelChildParameterSource::ModelParameter ); QVERIFY( w->value().parameterName().isEmpty() ); QCOMPARE( w->mStackedWidget->currentIndex(), 2 ); QCOMPARE( w->mSourceButton->toolTip(), QStringLiteral( "Model Input" ) ); // alg output - should fail, because we haven't populated sources yet, and so have no compatible sources w->setWidgetValue( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg3" ), QStringLiteral( "OUTPUT" ) ) ); QCOMPARE( w->value().source(), QgsProcessingModelChildParameterSource::ChildOutput ); QVERIFY( w->value().outputChildId().isEmpty() ); QCOMPARE( w->mStackedWidget->currentIndex(), 3 ); QCOMPARE( w->mSourceButton->toolTip(), QStringLiteral( "Algorithm Output" ) ); // populate sources and re-try w->populateSources( QStringList() << QStringLiteral( "boolean" ), QStringList() << QStringLiteral( "outputVector" ), QList() ); // model input w->setWidgetValue( QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "p1" ) ) ); QCOMPARE( w->value().source(), QgsProcessingModelChildParameterSource::ModelParameter ); QCOMPARE( w->value().parameterName(), QStringLiteral( "p1" ) ); // alg output w->setWidgetValue( QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "alg3" ), QStringLiteral( "OUTPUT" ) ) ); QCOMPARE( w->value().source(), QgsProcessingModelChildParameterSource::ChildOutput ); QCOMPARE( w->value().outputChildId(), QStringLiteral( "alg3" ) ); QCOMPARE( w->value().outputName(), QStringLiteral( "OUTPUT" ) ); delete w; } void TestProcessingGui::testBooleanWrapper() { TestParamType param( QStringLiteral( "boolean" ), QStringLiteral( "bool" ) ); // standard wrapper QgsProcessingBooleanWidgetWrapper wrapper( ¶m ); QSignalSpy spy( &wrapper, &QgsProcessingBooleanWidgetWrapper::widgetValueHasChanged ); QgsProcessingContext context; QWidget *w = wrapper.createWrappedWidget( context ); wrapper.setWidgetValue( true, context ); QCOMPARE( spy.count(), 1 ); QVERIFY( wrapper.widgetValue().toBool() ); QVERIFY( static_cast< QCheckBox * >( wrapper.wrappedWidget() )->isChecked() ); wrapper.setWidgetValue( false, context ); QCOMPARE( spy.count(), 2 ); QVERIFY( !wrapper.widgetValue().toBool() ); QVERIFY( !static_cast< QCheckBox * >( wrapper.wrappedWidget() )->isChecked() ); // should be no label in standard mode QVERIFY( !wrapper.createWrappedLabel() ); QCOMPARE( static_cast< QCheckBox * >( wrapper.wrappedWidget() )->text(), QStringLiteral( "bool" ) ); // check signal static_cast< QCheckBox * >( wrapper.wrappedWidget() )->setChecked( true ); QCOMPARE( spy.count(), 3 ); static_cast< QCheckBox * >( wrapper.wrappedWidget() )->setChecked( false ); QCOMPARE( spy.count(), 4 ); delete w; // batch wrapper QgsProcessingBooleanWidgetWrapper wrapperB( ¶m, QgsProcessingGui::Batch ); w = wrapperB.createWrappedWidget( context ); QSignalSpy spy2( &wrapperB, &QgsProcessingBooleanWidgetWrapper::widgetValueHasChanged ); wrapperB.setWidgetValue( true, context ); QCOMPARE( spy2.count(), 1 ); QVERIFY( wrapperB.widgetValue().toBool() ); QVERIFY( static_cast< QComboBox * >( wrapperB.wrappedWidget() )->currentData().toBool() ); wrapperB.setWidgetValue( false, context ); QCOMPARE( spy2.count(), 2 ); QVERIFY( !wrapperB.widgetValue().toBool() ); QVERIFY( !static_cast< QComboBox * >( wrapperB.wrappedWidget() )->currentData().toBool() ); // check signal static_cast< QComboBox * >( w )->setCurrentIndex( 0 ); QCOMPARE( spy2.count(), 3 ); static_cast< QComboBox * >( w )->setCurrentIndex( 1 ); QCOMPARE( spy2.count(), 4 ); // should be no label in batch mode QVERIFY( !wrapperB.createWrappedLabel() ); delete w; // modeler wrapper QgsProcessingBooleanWidgetWrapper wrapperM( ¶m, QgsProcessingGui::Modeler ); w = wrapperM.createWrappedWidget( context ); QSignalSpy spy3( &wrapperM, &QgsProcessingBooleanWidgetWrapper::widgetValueHasChanged ); wrapperM.setWidgetValue( true, context ); QVERIFY( wrapperM.widgetValue().toBool() ); QCOMPARE( spy3.count(), 1 ); QVERIFY( static_cast< QComboBox * >( wrapperM.wrappedWidget() )->currentData().toBool() ); wrapperM.setWidgetValue( false, context ); QVERIFY( !wrapperM.widgetValue().toBool() ); QCOMPARE( spy3.count(), 2 ); QVERIFY( !static_cast< QComboBox * >( wrapperM.wrappedWidget() )->currentData().toBool() ); // check signal static_cast< QComboBox * >( w )->setCurrentIndex( 0 ); QCOMPARE( spy3.count(), 3 ); static_cast< QComboBox * >( w )->setCurrentIndex( 1 ); QCOMPARE( spy3.count(), 4 ); // should be a label in modeler mode QLabel *l = wrapperM.createWrappedLabel(); QVERIFY( l ); QCOMPARE( l->text(), QStringLiteral( "bool" ) ); QCOMPARE( l->toolTip(), param.toolTip() ); delete w; delete l; } void TestProcessingGui::testStringWrapper() { QgsProcessingParameterString param( QStringLiteral( "string" ), QStringLiteral( "string" ) ); // standard wrapper QgsProcessingStringWidgetWrapper wrapper( ¶m ); QgsProcessingContext context; QWidget *w = wrapper.createWrappedWidget( context ); QSignalSpy spy( &wrapper, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged ); wrapper.setWidgetValue( QStringLiteral( "a" ), context ); QCOMPARE( spy.count(), 1 ); QCOMPARE( wrapper.widgetValue().toString(), QStringLiteral( "a" ) ); QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text(), QStringLiteral( "a" ) ); wrapper.setWidgetValue( QString(), context ); QCOMPARE( spy.count(), 2 ); QVERIFY( wrapper.widgetValue().toString().isEmpty() ); QVERIFY( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text().isEmpty() ); QLabel *l = wrapper.createWrappedLabel(); QVERIFY( l ); QCOMPARE( l->text(), QStringLiteral( "string" ) ); QCOMPARE( l->toolTip(), param.toolTip() ); delete l; // check signal static_cast< QLineEdit * >( wrapper.wrappedWidget() )->setText( QStringLiteral( "b" ) ); QCOMPARE( spy.count(), 3 ); static_cast< QLineEdit * >( wrapper.wrappedWidget() )->clear(); QCOMPARE( spy.count(), 4 ); delete w; // batch wrapper QgsProcessingStringWidgetWrapper wrapperB( ¶m, QgsProcessingGui::Batch ); w = wrapperB.createWrappedWidget( context ); QSignalSpy spy2( &wrapperB, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged ); wrapperB.setWidgetValue( QStringLiteral( "a" ), context ); QCOMPARE( spy2.count(), 1 ); QCOMPARE( wrapperB.widgetValue().toString(), QStringLiteral( "a" ) ); QCOMPARE( static_cast< QLineEdit * >( wrapperB.wrappedWidget() )->text(), QStringLiteral( "a" ) ); wrapperB.setWidgetValue( QString(), context ); QCOMPARE( spy2.count(), 2 ); QVERIFY( wrapperB.widgetValue().toString().isEmpty() ); QVERIFY( static_cast< QLineEdit * >( wrapperB.wrappedWidget() )->text().isEmpty() ); // check signal static_cast< QLineEdit * >( w )->setText( QStringLiteral( "x" ) ); QCOMPARE( spy2.count(), 3 ); static_cast< QLineEdit * >( w )->clear(); QCOMPARE( spy2.count(), 4 ); // should be no label in batch mode QVERIFY( !wrapperB.createWrappedLabel() ); delete w; // modeler wrapper QgsProcessingStringWidgetWrapper wrapperM( ¶m, QgsProcessingGui::Modeler ); w = wrapperM.createWrappedWidget( context ); QSignalSpy spy3( &wrapperM, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged ); wrapperM.setWidgetValue( QStringLiteral( "a" ), context ); QCOMPARE( wrapperM.widgetValue().toString(), QStringLiteral( "a" ) ); QCOMPARE( spy3.count(), 1 ); QCOMPARE( static_cast< QLineEdit * >( wrapperM.wrappedWidget() )->text(), QStringLiteral( "a" ) ); wrapperM.setWidgetValue( QString(), context ); QVERIFY( wrapperM.widgetValue().toString().isEmpty() ); QCOMPARE( spy3.count(), 2 ); QVERIFY( static_cast< QLineEdit * >( wrapperM.wrappedWidget() )->text().isEmpty() ); // check signal static_cast< QLineEdit * >( w )->setText( QStringLiteral( "x" ) ); QCOMPARE( spy3.count(), 3 ); static_cast< QLineEdit * >( w )->clear(); QCOMPARE( spy3.count(), 4 ); // should be a label in modeler mode l = wrapperM.createWrappedLabel(); QVERIFY( l ); QCOMPARE( l->text(), QStringLiteral( "string" ) ); QCOMPARE( l->toolTip(), param.toolTip() ); delete w; delete l; // // multiline parameter // param = QgsProcessingParameterString( QStringLiteral( "string" ), QStringLiteral( "string" ), QVariant(), true ); // standard wrapper QgsProcessingStringWidgetWrapper wrapperMultiLine( ¶m ); w = wrapperMultiLine.createWrappedWidget( context ); QSignalSpy spy4( &wrapperMultiLine, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged ); wrapperMultiLine.setWidgetValue( QStringLiteral( "a" ), context ); QCOMPARE( spy4.count(), 1 ); QCOMPARE( wrapperMultiLine.widgetValue().toString(), QStringLiteral( "a" ) ); QCOMPARE( static_cast< QPlainTextEdit * >( wrapperMultiLine.wrappedWidget() )->toPlainText(), QStringLiteral( "a" ) ); wrapperMultiLine.setWidgetValue( QString(), context ); QCOMPARE( spy4.count(), 2 ); QVERIFY( wrapperMultiLine.widgetValue().toString().isEmpty() ); QVERIFY( static_cast< QPlainTextEdit * >( wrapperMultiLine.wrappedWidget() )->toPlainText().isEmpty() ); l = wrapper.createWrappedLabel(); QVERIFY( l ); QCOMPARE( l->text(), QStringLiteral( "string" ) ); QCOMPARE( l->toolTip(), param.toolTip() ); delete l; // check signal static_cast< QPlainTextEdit * >( wrapperMultiLine.wrappedWidget() )->setPlainText( QStringLiteral( "b" ) ); QCOMPARE( spy4.count(), 3 ); static_cast< QPlainTextEdit * >( wrapperMultiLine.wrappedWidget() )->clear(); QCOMPARE( spy4.count(), 4 ); delete w; // batch wrapper - should still be a line edit QgsProcessingStringWidgetWrapper wrapperMultiLineB( ¶m, QgsProcessingGui::Batch ); w = wrapperMultiLineB.createWrappedWidget( context ); QSignalSpy spy5( &wrapperMultiLineB, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged ); wrapperMultiLineB.setWidgetValue( QStringLiteral( "a" ), context ); QCOMPARE( spy5.count(), 1 ); QCOMPARE( wrapperMultiLineB.widgetValue().toString(), QStringLiteral( "a" ) ); QCOMPARE( static_cast< QLineEdit * >( wrapperMultiLineB.wrappedWidget() )->text(), QStringLiteral( "a" ) ); wrapperMultiLineB.setWidgetValue( QString(), context ); QCOMPARE( spy5.count(), 2 ); QVERIFY( wrapperMultiLineB.widgetValue().toString().isEmpty() ); QVERIFY( static_cast< QLineEdit * >( wrapperMultiLineB.wrappedWidget() )->text().isEmpty() ); // check signal static_cast< QLineEdit * >( w )->setText( QStringLiteral( "x" ) ); QCOMPARE( spy5.count(), 3 ); static_cast< QLineEdit * >( w )->clear(); QCOMPARE( spy5.count(), 4 ); // should be no label in batch mode QVERIFY( !wrapperB.createWrappedLabel() ); delete w; // modeler wrapper QgsProcessingStringWidgetWrapper wrapperMultiLineM( ¶m, QgsProcessingGui::Modeler ); w = wrapperMultiLineM.createWrappedWidget( context ); QSignalSpy spy6( &wrapperMultiLineM, &QgsProcessingStringWidgetWrapper::widgetValueHasChanged ); wrapperMultiLineM.setWidgetValue( QStringLiteral( "a" ), context ); QCOMPARE( wrapperMultiLineM.widgetValue().toString(), QStringLiteral( "a" ) ); QCOMPARE( spy6.count(), 1 ); QCOMPARE( static_cast< QPlainTextEdit * >( wrapperMultiLineM.wrappedWidget() )->toPlainText(), QStringLiteral( "a" ) ); wrapperMultiLineM.setWidgetValue( QString(), context ); QVERIFY( wrapperMultiLineM.widgetValue().toString().isEmpty() ); QCOMPARE( spy6.count(), 2 ); QVERIFY( static_cast< QPlainTextEdit * >( wrapperMultiLineM.wrappedWidget() )->toPlainText().isEmpty() ); // check signal static_cast< QPlainTextEdit * >( w )->setPlainText( QStringLiteral( "x" ) ); QCOMPARE( spy6.count(), 3 ); static_cast< QPlainTextEdit * >( w )->clear(); QCOMPARE( spy6.count(), 4 ); // should be a label in modeler mode l = wrapperMultiLineM.createWrappedLabel(); QVERIFY( l ); QCOMPARE( l->text(), QStringLiteral( "string" ) ); QCOMPARE( l->toolTip(), param.toolTip() ); delete w; delete l; } QGSTEST_MAIN( TestProcessingGui ) #include "testprocessinggui.moc"